3 @brief Utility routines for translating JSON into SQL.
11 #include "opensrf/utils.h"
12 #include "opensrf/log.h"
13 #include "opensrf/osrf_application.h"
14 #include "openils/oils_utils.h"
15 #include "openils/oils_sql.h"
17 // The next four macros are OR'd together as needed to form a set
18 // of bitflags. SUBCOMBO enables an extra pair of parentheses when
19 // nesting one UNION, INTERSECT or EXCEPT inside another.
20 // SUBSELECT tells us we're in a subquery, so don't add the
21 // terminal semicolon yet.
24 #define DISABLE_I18N 2
25 #define SELECT_DISTINCT 1
30 struct ClassInfoStruct;
31 typedef struct ClassInfoStruct ClassInfo;
33 #define ALIAS_STORE_SIZE 16
34 #define CLASS_NAME_STORE_SIZE 16
36 struct ClassInfoStruct {
40 osrfHash* class_def; // Points into IDL
41 osrfHash* fields; // Points into IDL
42 osrfHash* links; // Points into IDL
44 // The remaining members are private and internal. Client code should not
45 // access them directly.
47 ClassInfo* next; // Supports linked list of joined classes
48 int in_use; // boolean
50 // We usually store the alias and class name in the following arrays, and
51 // point the corresponding pointers at them. When the string is too big
52 // for the array (which will probably never happen in practice), we strdup it.
54 char alias_store[ ALIAS_STORE_SIZE + 1 ];
55 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
58 struct QueryFrameStruct;
59 typedef struct QueryFrameStruct QueryFrame;
61 struct QueryFrameStruct {
63 ClassInfo* join_list; // linked list of classes joined to the core class
64 QueryFrame* next; // implements stack as linked list
65 int in_use; // boolean
68 static int timeout_needs_resetting;
69 static time_t time_next_reset;
71 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
73 static void setXactId( osrfMethodContext* ctx );
74 static inline const char* getXactId( osrfMethodContext* ctx );
75 static inline void clearXactId( osrfMethodContext* ctx );
77 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
78 jsonObject* where_hash, jsonObject* query_hash, int* err );
79 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
80 static jsonObject* oilsMakeJSONFromResult( dbi_result );
82 static char* searchSimplePredicate ( const char* op, const char* class_alias,
83 osrfHash* field, const jsonObject* node );
84 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
85 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject* );
86 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*,
88 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
89 static char* searchINPredicate ( const char*, osrfHash*,
90 jsonObject*, const char*, osrfMethodContext* );
91 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
92 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
93 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo*, int, osrfMethodContext* );
94 static char* buildSELECT( const jsonObject*, jsonObject* rest_of_query,
95 osrfHash* meta, osrfMethodContext* ctx );
96 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array );
98 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
100 char* SELECT ( osrfMethodContext*, jsonObject*, const jsonObject*, const jsonObject*,
101 const jsonObject*, const jsonObject*, const jsonObject*, const jsonObject*, int );
103 static osrfStringArray* getPermLocationCache( osrfMethodContext*, const char* );
104 static void setPermLocationCache( osrfMethodContext*, const char*, osrfStringArray* );
106 void userDataFree( void* );
107 static void sessionDataFree( char*, void* );
108 static void pcacheFree( char*, void* );
109 static int obj_is_true( const jsonObject* obj );
110 static const char* json_type( int code );
111 static const char* get_primitive( osrfHash* field );
112 static const char* get_datatype( osrfHash* field );
113 static void pop_query_frame( void );
114 static void push_query_frame( void );
115 static int add_query_core( const char* alias, const char* class_name );
116 static inline ClassInfo* search_alias( const char* target );
117 static ClassInfo* search_all_alias( const char* target );
118 static ClassInfo* add_joined_class( const char* alias, const char* classname );
119 static void clear_query_stack( void );
121 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
122 static int verifyObjectPCRUD( osrfMethodContext*, osrfHash*, const jsonObject*, int );
123 static const char* org_tree_root( osrfMethodContext* ctx );
124 static jsonObject* single_hash( const char* key, const char* value );
126 static int child_initialized = 0; /* boolean */
128 static dbi_conn writehandle; /* our MASTER db connection */
129 static dbi_conn dbhandle; /* our CURRENT db connection */
130 //static osrfHash * readHandles;
132 // The following points to the top of a stack of QueryFrames. It's a little
133 // confusing because the top level of the query is at the bottom of the stack.
134 static QueryFrame* curr_query = NULL;
136 static dbi_conn writehandle; /* our MASTER db connection */
137 static dbi_conn dbhandle; /* our CURRENT db connection */
138 //static osrfHash * readHandles;
140 static int max_flesh_depth = 100;
142 static int perm_at_threshold = 5;
143 static int enforce_pcrud = 0; // Boolean
144 static char* modulename = NULL;
146 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id);
149 @brief Connect to the database.
150 @return A database connection if successful, or NULL if not.
152 dbi_conn oilsConnectDB( const char* mod_name ) {
154 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
155 if( dbi_initialize( NULL ) == -1 ) {
156 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
159 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
161 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
162 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
163 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
164 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
165 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
166 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
168 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
169 dbi_conn handle = dbi_conn_new( driver );
172 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
175 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
177 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
178 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
180 if( host ) dbi_conn_set_option( handle, "host", host );
181 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
182 if( user ) dbi_conn_set_option( handle, "username", user );
183 if( pw ) dbi_conn_set_option( handle, "password", pw );
184 if( db ) dbi_conn_set_option( handle, "dbname", db );
192 if( dbi_conn_connect( handle ) < 0 ) {
194 if( dbi_conn_connect( handle ) < 0 ) {
196 dbi_conn_error( handle, &msg );
197 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
198 msg ? msg : "(No description available)" );
203 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
209 @brief Select some options.
210 @param module_name: Name of the server.
211 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
213 This source file is used (at this writing) to implement three different servers:
214 - open-ils.reporter-store
218 These servers behave mostly the same, but they implement different combinations of
219 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
221 Here we use the server name in messages to identify which kind of server issued them.
222 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
224 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
226 module_name = "open-ils.cstore"; // bulletproofing with a default
231 modulename = strdup( module_name );
232 enforce_pcrud = do_pcrud;
233 max_flesh_depth = flesh_depth;
237 @brief Install a database connection.
238 @param conn Pointer to a database connection.
240 In some contexts, @a conn may merely provide a driver so that we can process strings
241 properly, without providing an open database connection.
243 void oilsSetDBConnection( dbi_conn conn ) {
244 dbhandle = writehandle = conn;
248 @brief Determine whether a database connection is alive.
249 @param handle Handle for a database connection.
250 @return 1 if the connection is alive, or zero if it isn't.
252 int oilsIsDBConnected( dbi_conn handle ) {
253 // Do an innocuous SELECT. If it succeeds, the database connection is still good.
254 dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
256 dbi_result_free( result );
259 // This is a terrible, horrible, no good, very bad kludge.
260 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
261 // but because (due to a previous error) the database is ignoring all commands,
262 // even innocuous SELECTs, until the current transaction is rolled back. The only
263 // known way to detect this condition via the dbi library is by looking at the error
264 // message. This approach will break if the language or wording of the message ever
266 // Note: the dbi_conn_ping function purports to determine whether the database
267 // connection is live, but at this writing this function is unreliable and useless.
268 static const char* ok_msg = "ERROR: current transaction is aborted, commands "
269 "ignored until end of transaction block\n";
271 dbi_conn_error( handle, &msg );
272 if( strcmp( msg, ok_msg )) {
273 osrfLogError( OSRF_LOG_MARK, "Database connection isn't working" );
276 return 1; // ignoring SELECT due to previous error; that's okay
281 @brief Get a table name, view name, or subquery for use in a FROM clause.
282 @param class Pointer to the IDL class entry.
283 @return A table name, a view name, or a subquery in parentheses.
285 In some cases the IDL defines a class, not with a table name or a view name, but with
286 a SELECT statement, which may be used as a subquery.
288 char* oilsGetRelation( osrfHash* classdef ) {
290 char* source_def = NULL;
291 const char* tabledef = osrfHashGet( classdef, "tablename" );
294 source_def = strdup( tabledef ); // Return the name of a table or view
296 tabledef = osrfHashGet( classdef, "source_definition" );
298 // Return a subquery, enclosed in parentheses
299 source_def = safe_malloc( strlen( tabledef ) + 3 );
300 source_def[ 0 ] = '(';
301 strcpy( source_def + 1, tabledef );
302 strcat( source_def, ")" );
304 // Not found: return an error
305 const char* classname = osrfHashGet( classdef, "classname" );
310 "%s ERROR No tablename or source_definition for class \"%s\"",
321 @brief Add datatypes from the database to the fields in the IDL.
322 @param handle Handle for a database connection
323 @return Zero if successful, or 1 upon error.
325 For each relevant class in the IDL: ask the database for the datatype of every field.
326 In particular, determine which fields are text fields and which fields are numeric
327 fields, so that we know whether to enclose their values in quotes.
329 int oilsExtendIDL( dbi_conn handle ) {
330 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
331 osrfHash* class = NULL;
332 growing_buffer* query_buf = buffer_init( 64 );
333 int results_found = 0; // boolean
335 // For each class in the IDL...
336 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
337 const char* classname = osrfHashIteratorKey( class_itr );
338 osrfHash* fields = osrfHashGet( class, "fields" );
340 // If the class is virtual, ignore it
341 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
342 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
346 char* tabledef = oilsGetRelation( class );
348 continue; // No such relation -- a query of it would be doomed to failure
350 buffer_reset( query_buf );
351 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
355 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
356 modulename, OSRF_BUFFER_C_STR( query_buf ) );
358 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
363 const char* columnName;
364 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
366 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
369 /* fetch the fieldmapper index */
370 osrfHash* _f = osrfHashGet(fields, columnName);
373 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
375 /* determine the field type and storage attributes */
377 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
379 case DBI_TYPE_INTEGER : {
381 if( !osrfHashGet(_f, "primitive") )
382 osrfHashSet(_f, "number", "primitive");
384 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
385 if( attr & DBI_INTEGER_SIZE8 )
386 osrfHashSet( _f, "INT8", "datatype" );
388 osrfHashSet( _f, "INT", "datatype" );
391 case DBI_TYPE_DECIMAL :
392 if( !osrfHashGet( _f, "primitive" ))
393 osrfHashSet( _f, "number", "primitive" );
395 osrfHashSet( _f, "NUMERIC", "datatype" );
398 case DBI_TYPE_STRING :
399 if( !osrfHashGet( _f, "primitive" ))
400 osrfHashSet( _f, "string", "primitive" );
402 osrfHashSet( _f,"TEXT", "datatype" );
405 case DBI_TYPE_DATETIME :
406 if( !osrfHashGet( _f, "primitive" ))
407 osrfHashSet( _f, "string", "primitive" );
409 osrfHashSet( _f, "TIMESTAMP", "datatype" );
412 case DBI_TYPE_BINARY :
413 if( !osrfHashGet( _f, "primitive" ))
414 osrfHashSet( _f, "string", "primitive" );
416 osrfHashSet( _f, "BYTEA", "datatype" );
421 "Setting [%s] to primitive [%s] and datatype [%s]...",
423 osrfHashGet( _f, "primitive" ),
424 osrfHashGet( _f, "datatype" )
428 } // end while loop for traversing columns of result
429 dbi_result_free( result );
432 int errnum = dbi_conn_error( handle, &msg );
433 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
434 errnum, msg ? msg : "(No description available)" );
435 // We don't check the database connection here. It's routine to get failures at
436 // this point; we routinely try to query tables that don't exist, because they
437 // are defined in the IDL but not in the database.
439 } // end for each class in IDL
441 buffer_free( query_buf );
442 osrfHashIteratorFree( class_itr );
443 child_initialized = 1;
445 if( !results_found ) {
446 osrfLogError( OSRF_LOG_MARK,
447 "No results found for any class -- bad database connection?" );
449 } else if( ! oilsIsDBConnected( handle )) {
450 osrfLogError( OSRF_LOG_MARK,
451 "Unable to extend IDL: database connection isn't working" );
459 @brief Free an osrfHash that stores a transaction ID.
460 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
462 This function is a callback, to be called by the application session when it ends.
463 The application session stores the osrfHash via an opaque pointer.
465 If the osrfHash contains an entry for the key "xact_id", it means that an
466 uncommitted transaction is pending. Roll it back.
468 void userDataFree( void* blob ) {
469 osrfHash* hash = (osrfHash*) blob;
470 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
471 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
473 int errnum = dbi_conn_error( writehandle, &msg );
474 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
475 errnum, msg ? msg : "(No description available)" );
479 if( !dbi_conn_query( writehandle, "SELECT auditor.clear_audit_info();" ) ) {
481 int errnum = dbi_conn_error( writehandle, &msg );
482 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform audit info clearing: %d %s",
483 errnum, msg ? msg : "(No description available)" );
487 osrfHashFree( hash );
491 @name Managing session data
492 @brief Maintain data stored via the userData pointer of the application session.
494 Currently, session-level data is stored in an osrfHash. Other arrangements are
495 possible, and some would be more efficient. The application session calls a
496 callback function to free userData before terminating.
498 Currently, the only data we store at the session level is the transaction id. By this
499 means we can ensure that any pending transactions are rolled back before the application
505 @brief Free an item in the application session's userData.
506 @param key The name of a key for an osrfHash.
507 @param item An opaque pointer to the item associated with the key.
509 We store an osrfHash as userData with the application session, and arrange (by
510 installing userDataFree() as a different callback) for the session to free that
511 osrfHash before terminating.
513 This function is a callback for freeing items in the osrfHash. Currently we store
515 - Transaction id of a pending transaction; a character string. Key: "xact_id".
516 - Authkey; a character string. Key: "authkey".
517 - User object from the authentication server; a jsonObject. Key: "user_login".
519 If we ever store anything else in userData, we will need to revisit this function so
520 that it will free whatever else needs freeing.
522 static void sessionDataFree( char* key, void* item ) {
523 if( !strcmp( key, "xact_id" ) || !strcmp( key, "authkey" ) || !strncmp( key, "rs_size_", 8) )
525 else if( !strcmp( key, "user_login" ) )
526 jsonObjectFree( (jsonObject*) item );
527 else if( !strcmp( key, "pcache" ) )
528 osrfHashFree( (osrfHash*) item );
531 static void pcacheFree( char* key, void* item ) {
532 osrfStringArrayFree( (osrfStringArray*) item );
536 @brief Initialize session cache.
537 @param ctx Pointer to the method context.
539 Create a cache for the session by making the session's userData member point
540 to an osrfHash instance.
542 static osrfHash* initSessionCache( osrfMethodContext* ctx ) {
543 ctx->session->userData = osrfNewHash();
544 osrfHashSetCallback( (osrfHash*) ctx->session->userData, &sessionDataFree );
545 ctx->session->userDataFree = &userDataFree;
546 return ctx->session->userData;
550 @brief Save a transaction id.
551 @param ctx Pointer to the method context.
553 Save the session_id of the current application session as a transaction id.
555 static void setXactId( osrfMethodContext* ctx ) {
556 if( ctx && ctx->session ) {
557 osrfAppSession* session = ctx->session;
559 osrfHash* cache = session->userData;
561 // If the session doesn't already have a hash, create one. Make sure
562 // that the application session frees the hash when it terminates.
564 cache = initSessionCache( ctx );
566 // Save the transaction id in the hash, with the key "xact_id"
567 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
572 @brief Get the transaction ID for the current transaction, if any.
573 @param ctx Pointer to the method context.
574 @return Pointer to the transaction ID.
576 The return value points to an internal buffer, and will become invalid upon issuing
577 a commit or rollback.
579 static inline const char* getXactId( osrfMethodContext* ctx ) {
580 if( ctx && ctx->session && ctx->session->userData )
581 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
587 @brief Clear the current transaction id.
588 @param ctx Pointer to the method context.
590 static inline void clearXactId( osrfMethodContext* ctx ) {
591 if( ctx && ctx->session && ctx->session->userData )
592 osrfHashRemove( ctx->session->userData, "xact_id" );
597 @brief Stash the location for a particular perm in the sessionData cache
598 @param ctx Pointer to the method context.
599 @param perm Name of the permission we're looking at
600 @param array StringArray of perm location ids
602 static void setPermLocationCache( osrfMethodContext* ctx, const char* perm, osrfStringArray* locations ) {
603 if( ctx && ctx->session ) {
604 osrfAppSession* session = ctx->session;
606 osrfHash* cache = session->userData;
608 // If the session doesn't already have a hash, create one. Make sure
609 // that the application session frees the hash when it terminates.
611 cache = initSessionCache( ctx );
613 osrfHash* pcache = osrfHashGet(cache, "pcache");
615 if( NULL == pcache ) {
616 pcache = osrfNewHash();
617 osrfHashSetCallback( pcache, &pcacheFree );
618 osrfHashSet( cache, pcache, "pcache" );
621 if( perm && locations )
622 osrfHashSet( pcache, locations, strdup(perm) );
627 @brief Grab stashed location for a particular perm in the sessionData cache
628 @param ctx Pointer to the method context.
629 @param perm Name of the permission we're looking at
631 static osrfStringArray* getPermLocationCache( osrfMethodContext* ctx, const char* perm ) {
632 if( ctx && ctx->session ) {
633 osrfAppSession* session = ctx->session;
634 osrfHash* cache = session->userData;
636 osrfHash* pcache = osrfHashGet(cache, "pcache");
638 return osrfHashGet( pcache, perm );
647 @brief Save the user's login in the userData for the current application session.
648 @param ctx Pointer to the method context.
649 @param user_login Pointer to the user login object to be cached (we cache the original,
652 If @a user_login is NULL, remove the user login if one is already cached.
654 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
655 if( ctx && ctx->session ) {
656 osrfAppSession* session = ctx->session;
658 osrfHash* cache = session->userData;
660 // If the session doesn't already have a hash, create one. Make sure
661 // that the application session frees the hash when it terminates.
663 cache = initSessionCache( ctx );
666 osrfHashSet( cache, user_login, "user_login" );
668 osrfHashRemove( cache, "user_login" );
673 @brief Get the user login object for the current application session, if any.
674 @param ctx Pointer to the method context.
675 @return Pointer to the user login object if found; otherwise NULL.
677 The user login object was returned from the authentication server, and then cached so
678 we don't have to call the authentication server again for the same user.
680 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
681 if( ctx && ctx->session && ctx->session->userData )
682 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
688 @brief Save a copy of an authkey in the userData of the current application session.
689 @param ctx Pointer to the method context.
690 @param authkey The authkey to be saved.
692 If @a authkey is NULL, remove the authkey if one is already cached.
694 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
695 if( ctx && ctx->session && authkey ) {
696 osrfAppSession* session = ctx->session;
697 osrfHash* cache = session->userData;
699 // If the session doesn't already have a hash, create one. Make sure
700 // that the application session frees the hash when it terminates.
702 cache = initSessionCache( ctx );
704 // Save the transaction id in the hash, with the key "xact_id"
705 if( authkey && *authkey )
706 osrfHashSet( cache, strdup( authkey ), "authkey" );
708 osrfHashRemove( cache, "authkey" );
713 @brief Reset the login timeout.
714 @param authkey The authentication key for the current login session.
715 @param now The current time.
716 @return Zero if successful, or 1 if not.
718 Tell the authentication server to reset the timeout so that the login session won't
719 expire for a while longer.
721 We could dispense with the @a now parameter by calling time(). But we just called
722 time() in order to decide whether to reset the timeout, so we might as well reuse
723 the result instead of calling time() again.
725 static int reset_timeout( const char* authkey, time_t now ) {
726 jsonObject* auth_object = jsonNewObject( authkey );
728 // Ask the authentication server to reset the timeout. It returns an event
729 // indicating success or failure.
730 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
731 "open-ils.auth.session.reset_timeout", auth_object );
732 jsonObjectFree( auth_object );
734 if( !result || result->type != JSON_HASH ) {
735 osrfLogError( OSRF_LOG_MARK,
736 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
737 jsonObjectFree( result );
738 return 1; // Not the right sort of object returned
741 const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
742 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
743 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
744 jsonObjectFree( result );
745 return 1; // Return code from method not available
748 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
749 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
751 desc = "(No reason available)"; // failsafe; shouldn't happen
752 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
753 jsonObjectFree( result );
757 // Revise our local proxy for the timeout deadline
758 // by a smallish fraction of the timeout interval
759 const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
761 timeout = "1"; // failsafe; shouldn't happen
762 time_next_reset = now + atoi( timeout ) / 15;
764 jsonObjectFree( result );
765 return 0; // Successfully reset timeout
769 @brief Get the authkey string for the current application session, if any.
770 @param ctx Pointer to the method context.
771 @return Pointer to the cached authkey if found; otherwise NULL.
773 If present, the authkey string was cached from a previous method call.
775 static const char* getAuthkey( osrfMethodContext* ctx ) {
776 if( ctx && ctx->session && ctx->session->userData ) {
777 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
778 // LFW recent changes mean the userData hash gets set up earlier, but
779 // doesn't necessarily have an authkey yet
783 // Possibly reset the authentication timeout to keep the login alive. We do so
784 // no more than once per method call, and not at all if it has been only a short
785 // time since the last reset.
787 // Here we reset explicitly, if at all. We also implicitly reset the timeout
788 // whenever we call the "open-ils.auth.session.retrieve" method.
789 if( timeout_needs_resetting ) {
790 time_t now = time( NULL );
791 if( now >= time_next_reset && reset_timeout( authkey, now ) )
792 authkey = NULL; // timeout has apparently expired already
795 timeout_needs_resetting = 0;
803 @brief Implement the transaction.begin method.
804 @param ctx Pointer to the method context.
805 @return Zero if successful, or -1 upon error.
807 Start a transaction. Save a transaction ID for future reference.
810 - authkey (PCRUD only)
812 Return to client: Transaction ID
814 int beginTransaction( osrfMethodContext* ctx ) {
815 if(osrfMethodVerifyContext( ctx )) {
816 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
820 if( enforce_pcrud ) {
821 timeout_needs_resetting = 1;
822 const jsonObject* user = verifyUserPCRUD( ctx );
827 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
830 int errnum = dbi_conn_error( writehandle, &msg );
831 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
832 modulename, errnum, msg ? msg : "(No description available)" );
833 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
834 "osrfMethodException", ctx->request, "Error starting transaction" );
835 if( !oilsIsDBConnected( writehandle ))
836 osrfAppSessionPanic( ctx->session );
839 dbi_result_free( result );
841 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
842 osrfAppRespondComplete( ctx, ret );
843 jsonObjectFree( ret );
849 @brief Implement the savepoint.set method.
850 @param ctx Pointer to the method context.
851 @return Zero if successful, or -1 if not.
853 Issue a SAVEPOINT to the database server.
856 - authkey (PCRUD only)
859 Return to client: Savepoint name
861 int setSavepoint( osrfMethodContext* ctx ) {
862 if(osrfMethodVerifyContext( ctx )) {
863 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
868 if( enforce_pcrud ) {
870 timeout_needs_resetting = 1;
871 const jsonObject* user = verifyUserPCRUD( ctx );
876 // Verify that a transaction is pending
877 const char* trans_id = getXactId( ctx );
878 if( NULL == trans_id ) {
879 osrfAppSessionStatus(
881 OSRF_STATUS_INTERNALSERVERERROR,
882 "osrfMethodException",
884 "No active transaction -- required for savepoints"
889 // Get the savepoint name from the method params
890 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
892 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
895 int errnum = dbi_conn_error( writehandle, &msg );
898 "%s: Error creating savepoint %s in transaction %s: %d %s",
903 msg ? msg : "(No description available)"
905 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
906 "osrfMethodException", ctx->request, "Error creating savepoint" );
907 if( !oilsIsDBConnected( writehandle ))
908 osrfAppSessionPanic( ctx->session );
911 dbi_result_free( result );
912 jsonObject* ret = jsonNewObject( spName );
913 osrfAppRespondComplete( ctx, ret );
914 jsonObjectFree( ret );
920 @brief Implement the savepoint.release method.
921 @param ctx Pointer to the method context.
922 @return Zero if successful, or -1 if not.
924 Issue a RELEASE SAVEPOINT to the database server.
927 - authkey (PCRUD only)
930 Return to client: Savepoint name
932 int releaseSavepoint( osrfMethodContext* ctx ) {
933 if(osrfMethodVerifyContext( ctx )) {
934 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
939 if( enforce_pcrud ) {
941 timeout_needs_resetting = 1;
942 const jsonObject* user = verifyUserPCRUD( ctx );
947 // Verify that a transaction is pending
948 const char* trans_id = getXactId( ctx );
949 if( NULL == trans_id ) {
950 osrfAppSessionStatus(
952 OSRF_STATUS_INTERNALSERVERERROR,
953 "osrfMethodException",
955 "No active transaction -- required for savepoints"
960 // Get the savepoint name from the method params
961 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
963 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
966 int errnum = dbi_conn_error( writehandle, &msg );
969 "%s: Error releasing savepoint %s in transaction %s: %d %s",
974 msg ? msg : "(No description available)"
976 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
977 "osrfMethodException", ctx->request, "Error releasing savepoint" );
978 if( !oilsIsDBConnected( writehandle ))
979 osrfAppSessionPanic( ctx->session );
982 dbi_result_free( result );
983 jsonObject* ret = jsonNewObject( spName );
984 osrfAppRespondComplete( ctx, ret );
985 jsonObjectFree( ret );
991 @brief Implement the savepoint.rollback method.
992 @param ctx Pointer to the method context.
993 @return Zero if successful, or -1 if not.
995 Issue a ROLLBACK TO SAVEPOINT to the database server.
998 - authkey (PCRUD only)
1001 Return to client: Savepoint name
1003 int rollbackSavepoint( osrfMethodContext* ctx ) {
1004 if(osrfMethodVerifyContext( ctx )) {
1005 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1010 if( enforce_pcrud ) {
1012 timeout_needs_resetting = 1;
1013 const jsonObject* user = verifyUserPCRUD( ctx );
1018 // Verify that a transaction is pending
1019 const char* trans_id = getXactId( ctx );
1020 if( NULL == trans_id ) {
1021 osrfAppSessionStatus(
1023 OSRF_STATUS_INTERNALSERVERERROR,
1024 "osrfMethodException",
1026 "No active transaction -- required for savepoints"
1031 // Get the savepoint name from the method params
1032 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1034 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
1037 int errnum = dbi_conn_error( writehandle, &msg );
1040 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
1045 msg ? msg : "(No description available)"
1047 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1048 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1049 if( !oilsIsDBConnected( writehandle ))
1050 osrfAppSessionPanic( ctx->session );
1053 dbi_result_free( result );
1054 jsonObject* ret = jsonNewObject( spName );
1055 osrfAppRespondComplete( ctx, ret );
1056 jsonObjectFree( ret );
1062 @brief Implement the transaction.commit method.
1063 @param ctx Pointer to the method context.
1064 @return Zero if successful, or -1 if not.
1066 Issue a COMMIT to the database server.
1069 - authkey (PCRUD only)
1071 Return to client: Transaction ID.
1073 int commitTransaction( osrfMethodContext* ctx ) {
1074 if(osrfMethodVerifyContext( ctx )) {
1075 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1079 if( enforce_pcrud ) {
1080 timeout_needs_resetting = 1;
1081 const jsonObject* user = verifyUserPCRUD( ctx );
1086 // Verify that a transaction is pending
1087 const char* trans_id = getXactId( ctx );
1088 if( NULL == trans_id ) {
1089 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1090 "osrfMethodException", ctx->request, "No active transaction to commit" );
1094 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1097 int errnum = dbi_conn_error( writehandle, &msg );
1098 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1099 modulename, errnum, msg ? msg : "(No description available)" );
1100 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1101 "osrfMethodException", ctx->request, "Error committing transaction" );
1102 if( !oilsIsDBConnected( writehandle ))
1103 osrfAppSessionPanic( ctx->session );
1106 dbi_result_free( result );
1107 jsonObject* ret = jsonNewObject( trans_id );
1108 osrfAppRespondComplete( ctx, ret );
1109 jsonObjectFree( ret );
1116 @brief Implement the transaction.rollback method.
1117 @param ctx Pointer to the method context.
1118 @return Zero if successful, or -1 if not.
1120 Issue a ROLLBACK to the database server.
1123 - authkey (PCRUD only)
1125 Return to client: Transaction ID
1127 int rollbackTransaction( osrfMethodContext* ctx ) {
1128 if( osrfMethodVerifyContext( ctx )) {
1129 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1133 if( enforce_pcrud ) {
1134 timeout_needs_resetting = 1;
1135 const jsonObject* user = verifyUserPCRUD( ctx );
1140 // Verify that a transaction is pending
1141 const char* trans_id = getXactId( ctx );
1142 if( NULL == trans_id ) {
1143 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1144 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1148 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1151 int errnum = dbi_conn_error( writehandle, &msg );
1152 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1153 modulename, errnum, msg ? msg : "(No description available)" );
1154 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1155 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1156 if( !oilsIsDBConnected( writehandle ))
1157 osrfAppSessionPanic( ctx->session );
1160 dbi_result_free( result );
1161 jsonObject* ret = jsonNewObject( trans_id );
1162 osrfAppRespondComplete( ctx, ret );
1163 jsonObjectFree( ret );
1170 @brief Implement the "search" method.
1171 @param ctx Pointer to the method context.
1172 @return Zero if successful, or -1 if not.
1175 - authkey (PCRUD only)
1176 - WHERE clause, as jsonObject
1177 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1179 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1180 Optionally flesh linked fields.
1182 int doSearch( osrfMethodContext* ctx ) {
1183 if( osrfMethodVerifyContext( ctx )) {
1184 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1189 timeout_needs_resetting = 1;
1191 jsonObject* where_clause;
1192 jsonObject* rest_of_query;
1194 if( enforce_pcrud ) {
1195 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1196 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1198 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1199 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1202 if( !where_clause ) {
1203 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1207 // Get the class metadata
1208 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1209 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1213 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1215 osrfAppRespondComplete( ctx, NULL );
1219 // doFieldmapperSearch() now takes care of our responding for us
1220 // // Return each row to the client
1221 // jsonObject* cur = 0;
1222 // unsigned long res_idx = 0;
1224 // while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1225 // // We used to discard based on perms here, but now that's
1226 // // inside doFieldmapperSearch()
1227 // osrfAppRespond( ctx, cur );
1230 jsonObjectFree( obj );
1232 osrfAppRespondComplete( ctx, NULL );
1237 @brief Implement the "id_list" method.
1238 @param ctx Pointer to the method context.
1239 @param err Pointer through which to return an error code.
1240 @return Zero if successful, or -1 if not.
1243 - authkey (PCRUD only)
1244 - WHERE clause, as jsonObject
1245 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1247 Return to client: The primary key values for all rows of the relevant class that
1248 satisfy a specified WHERE clause.
1250 This method relies on the assumption that every class has a primary key consisting of
1253 int doIdList( osrfMethodContext* ctx ) {
1254 if( osrfMethodVerifyContext( ctx )) {
1255 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1260 timeout_needs_resetting = 1;
1262 jsonObject* where_clause;
1263 jsonObject* rest_of_query;
1265 // We use the where clause without change. But we need to massage the rest of the
1266 // query, so we work with a copy of it instead of modifying the original.
1268 if( enforce_pcrud ) {
1269 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1270 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1272 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1273 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1276 if( !where_clause ) {
1277 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1281 // Eliminate certain SQL clauses, if present.
1282 if( rest_of_query ) {
1283 jsonObjectRemoveKey( rest_of_query, "select" );
1284 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1285 jsonObjectRemoveKey( rest_of_query, "flesh" );
1286 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1288 rest_of_query = jsonNewObjectType( JSON_HASH );
1291 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1293 // Get the class metadata
1294 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1295 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1297 // Build a SELECT list containing just the primary key,
1298 // i.e. like { "classname":["keyname"] }
1299 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1301 // Load array with name of primary key
1302 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1303 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1304 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1306 jsonObjectSetKey( rest_of_query, "select", select_clause );
1311 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1313 jsonObjectFree( rest_of_query );
1315 osrfAppRespondComplete( ctx, NULL );
1319 // Return each primary key value to the client
1321 unsigned long res_idx = 0;
1322 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1323 // We used to discard based on perms here, but now that's
1324 // inside doFieldmapperSearch()
1325 osrfAppRespond( ctx,
1326 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1329 jsonObjectFree( obj );
1330 osrfAppRespondComplete( ctx, NULL );
1335 @brief Verify that we have a valid class reference.
1336 @param ctx Pointer to the method context.
1337 @param param Pointer to the method parameters.
1338 @return 1 if the class reference is valid, or zero if it isn't.
1340 The class of the method params must match the class to which the method id devoted.
1341 For PCRUD there are additional restrictions.
1343 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1345 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1346 osrfHash* class = osrfHashGet( method_meta, "class" );
1348 // Compare the method's class to the parameters' class
1349 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1351 // Oops -- they don't match. Complain.
1352 growing_buffer* msg = buffer_init( 128 );
1355 "%s: %s method for type %s was passed a %s",
1357 osrfHashGet( method_meta, "methodtype" ),
1358 osrfHashGet( class, "classname" ),
1359 param->classname ? param->classname : "(null)"
1362 char* m = buffer_release( msg );
1363 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1371 return verifyObjectPCRUD( ctx, class, param, 1 );
1377 @brief (PCRUD only) Verify that the user is properly logged in.
1378 @param ctx Pointer to the method context.
1379 @return If the user is logged in, a pointer to the user object from the authentication
1380 server; otherwise NULL.
1382 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1384 // Get the authkey (the first method parameter)
1385 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1387 // See if we have the same authkey, and a user object,
1388 // locally cached from a previous call
1389 const char* cached_authkey = getAuthkey( ctx );
1390 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1391 const jsonObject* cached_user = getUserLogin( ctx );
1396 // We have no matching authentication data in the cache. Authenticate from scratch.
1397 jsonObject* auth_object = jsonNewObject( auth );
1399 // Fetch the user object from the authentication server
1400 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1402 jsonObjectFree( auth_object );
1404 if( !user->classname || strcmp(user->classname, "au" )) {
1406 growing_buffer* msg = buffer_init( 128 );
1409 "%s: permacrud received a bad auth token: %s",
1414 char* m = buffer_release( msg );
1415 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1419 jsonObjectFree( user );
1421 } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
1422 // Failed to set audit information - But note that write_audit_info already set error information.
1423 jsonObjectFree( user );
1427 setUserLogin( ctx, user );
1428 setAuthkey( ctx, auth );
1430 // Allow ourselves up to a second before we have to reset the login timeout.
1431 // It would be nice to use some fraction of the timeout interval enforced by the
1432 // authentication server, but that value is not readily available at this point.
1433 // Instead, we use a conservative default interval.
1434 time_next_reset = time( NULL ) + 1;
1440 @brief For PCRUD: Determine whether the current user may access the current row.
1441 @param ctx Pointer to the method context.
1442 @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1443 @param obj Pointer to the row being potentially accessed.
1444 @return 1 if access is permitted, or 0 if it isn't.
1446 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1448 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1450 dbhandle = writehandle;
1452 // Figure out what class and method are involved
1453 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1454 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1457 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1458 if (rs_size_from_hash) {
1459 rs_size = *rs_size_from_hash;
1460 osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1464 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1465 // contexts we will do another lookup of the current row, even if we already have a
1466 // previously fetched row image, because the row image in hand may not include the
1467 // foreign key(s) that we need.
1469 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1470 // but they aren't implemented yet.
1473 if( *method_type == 's' || *method_type == 'i' ) {
1474 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1476 } else if( *method_type == 'u' || *method_type == 'd' ) {
1477 fetch = 1; // MUST go to the db for the object for update and delete
1480 // Get the appropriate permacrud entry from the IDL, depending on method type
1481 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1483 // No permacrud for this method type on this class
1485 growing_buffer* msg = buffer_init( 128 );
1488 "%s: %s on class %s has no permacrud IDL entry",
1490 osrfHashGet( method_metadata, "methodtype" ),
1491 osrfHashGet( class, "classname" )
1494 char* m = buffer_release( msg );
1495 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1496 "osrfMethodException", ctx->request, m );
1503 // Get the user id, and make sure the user is logged in
1504 const jsonObject* user = verifyUserPCRUD( ctx );
1506 return 0; // Not logged in? No access.
1508 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1510 // Get a list of permissions from the permacrud entry.
1511 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1512 if( permission->size == 0 ) {
1515 "No permissions required for this action (class %s), passing through",
1516 osrfHashGet(class, "classname")
1521 // Build a list of org units that own the row. This is fairly convoluted because there
1522 // are several different ways that an org unit may own the row, as defined by the
1525 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1526 // identifying an owning org_unit..
1527 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1529 // Foreign context adds a layer of indirection. The row points to some other row that
1530 // an org unit may own. The "jump" attribute, if present, adds another layer of
1532 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1534 // The following string array stores the list of org units. (We don't have a thingie
1535 // for storing lists of integers, so we fake it with a list of strings.)
1536 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1539 const char* pkey_value = NULL;
1540 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1541 // If the global_required attribute is present and true, then the only owning
1542 // org unit is the root org unit, i.e. the one with no parent.
1543 osrfLogDebug( OSRF_LOG_MARK,
1544 "global-level permissions required, fetching top of the org tree" );
1546 // no need to check perms for org tree root retrieval
1547 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1548 // check for perm at top of org tree
1549 const char* org_tree_root_id = org_tree_root( ctx );
1550 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1552 if( org_tree_root_id ) {
1553 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1554 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1556 osrfStringArrayFree( context_org_array );
1561 // If the global_required attribute is absent or false, then we look for
1562 // local and/or foreign context. In order to find the relevant foreign
1563 // keys, we must either read the relevant row from the database, or look at
1564 // the image of the row that we already have in memory.
1566 // Even if we have an image of the row in memory, that image may not include the
1567 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1568 // of the row to make sure that we have what we need.
1570 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1571 "fetching context org ids" );
1572 const char* pkey = osrfHashGet( class, "primarykey" );
1573 jsonObject *param = NULL;
1576 // There is no primary key, so we can't do a fresh lookup. Use the row
1577 // image that we already have. If it doesn't have everything we need, too bad.
1579 param = jsonObjectClone( obj );
1580 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1581 } else if( obj->classname ) {
1582 pkey_value = oilsFMGetStringConst( obj, pkey );
1584 param = jsonObjectClone( obj );
1585 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1588 pkey_value = jsonObjectGetString( obj );
1590 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1591 "of %s and retrieving from the database", pkey_value );
1595 // Fetch the row so that we can look at the foreign key(s)
1596 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1597 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1598 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1599 jsonObjectFree( _tmp_params );
1600 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1602 param = jsonObjectExtractIndex( _list, 0 );
1603 jsonObjectFree( _list );
1607 // The row doesn't exist. Complain, and deny access.
1608 osrfLogDebug( OSRF_LOG_MARK,
1609 "Object not found in the database with primary key %s of %s",
1612 growing_buffer* msg = buffer_init( 128 );
1615 "%s: no object found with primary key %s of %s",
1621 char* m = buffer_release( msg );
1622 osrfAppSessionStatus(
1624 OSRF_STATUS_INTERNALSERVERERROR,
1625 "osrfMethodException",
1634 if( local_context && local_context->size > 0 ) {
1635 // The IDL provides a list of column names for the foreign keys denoting
1636 // local context, i.e. columns identifying owing org units directly. Look up
1637 // the value of each one, and if it isn't null, add it to the list of org units.
1638 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1639 local_context->size );
1641 const char* lcontext = NULL;
1642 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1643 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1644 if( fkey_value ) { // if not null
1645 osrfStringArrayAdd( context_org_array, fkey_value );
1648 "adding class-local field %s (value: %s) to the context org list",
1650 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1656 if( foreign_context ) {
1657 unsigned long class_count = osrfHashGetCount( foreign_context );
1658 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1660 if( class_count > 0 ) {
1662 // The IDL provides a list of foreign key columns pointing to rows that
1663 // an org unit may own. Follow each link, identify the owning org unit,
1664 // and add it to the list.
1665 osrfHash* fcontext = NULL;
1666 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1667 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1668 // For each class to which a foreign key points:
1669 const char* class_name = osrfHashIteratorKey( class_itr );
1670 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1674 "%d foreign context fields(s) specified for class %s",
1675 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1679 // Get the name of the key field in the foreign table
1680 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1682 // Get the value of the foreign key pointing to the foreign table
1683 char* foreign_pkey_value =
1684 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1685 if( !foreign_pkey_value )
1686 continue; // Foreign key value is null; skip it
1688 // Look up the row to which the foreign key points
1689 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1691 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1692 jsonObject* _list = doFieldmapperSearch(
1693 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1694 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1696 jsonObject* _fparam = NULL;
1697 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1698 _fparam = jsonObjectExtractIndex( _list, 0 );
1700 jsonObjectFree( _tmp_params );
1701 jsonObjectFree( _list );
1703 // At this point _fparam either points to the row identified by the
1704 // foreign key, or it's NULL (no such row found).
1706 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1708 const char* bad_class = NULL; // For noting failed lookups
1710 bad_class = class_name; // Referenced row not found
1711 else if( jump_list ) {
1712 // Follow a chain of rows, linked by foreign keys, to find an owner
1713 const char* flink = NULL;
1715 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1716 // For each entry in the jump list. Each entry (i.e. flink) is
1717 // the name of a foreign key column in the current row.
1719 // From the IDL, get the linkage information for the next jump
1720 osrfHash* foreign_link_hash =
1721 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1723 // Get the class metadata for the class
1724 // to which the foreign key points
1725 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1726 osrfHashGet( foreign_link_hash, "class" ));
1728 // Get the name of the referenced key of that class
1729 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1731 // Get the value of the foreign key pointing to that class
1732 free( foreign_pkey_value );
1733 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1734 if( !foreign_pkey_value )
1735 break; // Foreign key is null; quit looking
1737 // Build a WHERE clause for the lookup
1738 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1741 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1742 _tmp_params, NULL, &err );
1744 // Get the resulting row
1745 jsonObjectFree( _fparam );
1746 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1747 _fparam = jsonObjectExtractIndex( _list, 0 );
1749 // Referenced row not found
1751 bad_class = osrfHashGet( foreign_link_hash, "class" );
1754 jsonObjectFree( _tmp_params );
1755 jsonObjectFree( _list );
1761 // We had a foreign key pointing to such-and-such a row, but then
1762 // we couldn't fetch that row. The data in the database are in an
1763 // inconsistent state; the database itself may even be corrupted.
1764 growing_buffer* msg = buffer_init( 128 );
1767 "%s: no object of class %s found with primary key %s of %s",
1771 foreign_pkey_value ? foreign_pkey_value : "(null)"
1774 char* m = buffer_release( msg );
1775 osrfAppSessionStatus(
1777 OSRF_STATUS_INTERNALSERVERERROR,
1778 "osrfMethodException",
1784 osrfHashIteratorFree( class_itr );
1785 free( foreign_pkey_value );
1786 jsonObjectFree( param );
1791 free( foreign_pkey_value );
1794 // Examine each context column of the foreign row,
1795 // and add its value to the list of org units.
1797 const char* foreign_field = NULL;
1798 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1799 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1800 osrfStringArrayAdd( context_org_array,
1801 oilsFMGetStringConst( _fparam, foreign_field ));
1802 osrfLogDebug( OSRF_LOG_MARK,
1803 "adding foreign class %s field %s (value: %s) "
1804 "to the context org list",
1807 osrfStringArrayGetString(
1808 context_org_array, context_org_array->size - 1 )
1812 jsonObjectFree( _fparam );
1816 osrfHashIteratorFree( class_itr );
1820 jsonObjectFree( param );
1823 const char* context_org = NULL;
1824 const char* perm = NULL;
1827 // For every combination of permission and context org unit: call a stored procedure
1828 // to determine if the user has this permission in the context of this org unit.
1829 // If the answer is yes at any point, then we're done, and the user has permission.
1830 // In other words permissions are additive.
1832 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1835 osrfStringArray* pcache = NULL;
1836 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
1837 pcache = getPermLocationCache(ctx, perm);
1840 pcache = osrfNewStringArray(0);
1842 result = dbi_conn_queryf(
1844 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
1852 "Received a result for permission [%s] for user %d",
1857 if( dbi_result_first_row( result )) {
1859 jsonObject* return_val = oilsMakeJSONFromResult( result );
1860 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
1861 jsonObjectFree( return_val );
1862 } while( dbi_result_next_row( result ));
1864 setPermLocationCache(ctx, perm, pcache);
1867 dbi_result_free( result );
1873 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1875 if (rs_size > perm_at_threshold) {
1876 if (osrfStringArrayContains( pcache, context_org )) {
1885 "Checking object permission [%s] for user %d "
1886 "on object %s (class %s) at org %d",
1890 osrfHashGet( class, "classname" ),
1894 result = dbi_conn_queryf(
1896 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1899 osrfHashGet( class, "classname" ),
1907 "Received a result for object permission [%s] "
1908 "for user %d on object %s (class %s) at org %d",
1912 osrfHashGet( class, "classname" ),
1916 if( dbi_result_first_row( result )) {
1917 jsonObject* return_val = oilsMakeJSONFromResult( result );
1918 const char* has_perm = jsonObjectGetString(
1919 jsonObjectGetKeyConst( return_val, "has_perm" ));
1923 "Status of object permission [%s] for user %d "
1924 "on object %s (class %s) at org %d is %s",
1928 osrfHashGet(class, "classname"),
1933 if( *has_perm == 't' )
1935 jsonObjectFree( return_val );
1938 dbi_result_free( result );
1943 int errnum = dbi_conn_error( writehandle, &msg );
1944 osrfLogWarning( OSRF_LOG_MARK,
1945 "Unable to call check object permissions: %d, %s",
1946 errnum, msg ? msg : "(No description available)" );
1947 if( !oilsIsDBConnected( writehandle ))
1948 osrfAppSessionPanic( ctx->session );
1952 if (rs_size > perm_at_threshold) break;
1954 osrfLogDebug( OSRF_LOG_MARK,
1955 "Checking non-object permission [%s] for user %d at org %d",
1956 perm, userid, atoi(context_org) );
1957 result = dbi_conn_queryf(
1959 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1966 osrfLogDebug( OSRF_LOG_MARK,
1967 "Received a result for permission [%s] for user %d at org %d",
1968 perm, userid, atoi( context_org ));
1969 if( dbi_result_first_row( result )) {
1970 jsonObject* return_val = oilsMakeJSONFromResult( result );
1971 const char* has_perm = jsonObjectGetString(
1972 jsonObjectGetKeyConst( return_val, "has_perm" ));
1973 osrfLogDebug( OSRF_LOG_MARK,
1974 "Status of permission [%s] for user %d at org %d is [%s]",
1975 perm, userid, atoi( context_org ), has_perm );
1976 if( *has_perm == 't' )
1978 jsonObjectFree( return_val );
1981 dbi_result_free( result );
1986 int errnum = dbi_conn_error( writehandle, &msg );
1987 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
1988 errnum, msg ? msg : "(No description available)" );
1989 if( !oilsIsDBConnected( writehandle ))
1990 osrfAppSessionPanic( ctx->session );
1999 osrfStringArrayFree( context_org_array );
2005 @brief Look up the root of the org_unit tree.
2006 @param ctx Pointer to the method context.
2007 @return The id of the root org unit, as a character string.
2009 Query actor.org_unit where parent_ou is null, and return the id as a string.
2011 This function assumes that there is only one root org unit, i.e. that we
2012 have a single tree, not a forest.
2014 The calling code is responsible for freeing the returned string.
2016 static const char* org_tree_root( osrfMethodContext* ctx ) {
2018 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
2019 static time_t last_lookup_time = 0;
2020 time_t current_time = time( NULL );
2022 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2023 // We successfully looked this up less than an hour ago.
2024 // It's not likely to have changed since then.
2025 return strdup( cached_root_id );
2027 last_lookup_time = current_time;
2030 jsonObject* where_clause = single_hash( "parent_ou", NULL );
2031 jsonObject* result = doFieldmapperSearch(
2032 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2033 jsonObjectFree( where_clause );
2035 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2038 jsonObjectFree( result );
2040 growing_buffer* msg = buffer_init( 128 );
2041 OSRF_BUFFER_ADD( msg, modulename );
2042 OSRF_BUFFER_ADD( msg,
2043 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2045 char* m = buffer_release( msg );
2046 osrfAppSessionStatus( ctx->session,
2047 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2050 cached_root_id[ 0 ] = '\0';
2054 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2055 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2057 strcpy( cached_root_id, root_org_unit_id );
2058 jsonObjectFree( result );
2059 return cached_root_id;
2063 @brief Create a JSON_HASH with a single key/value pair.
2064 @param key The key of the key/value pair.
2065 @param value the value of the key/value pair.
2066 @return Pointer to a newly created jsonObject of type JSON_HASH.
2068 The value of the key/value is either a string or (if @a value is NULL) a null.
2070 static jsonObject* single_hash( const char* key, const char* value ) {
2072 if( ! key ) key = "";
2074 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2075 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2080 int doCreate( osrfMethodContext* ctx ) {
2081 if(osrfMethodVerifyContext( ctx )) {
2082 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2087 timeout_needs_resetting = 1;
2089 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2090 jsonObject* target = NULL;
2091 jsonObject* options = NULL;
2093 if( enforce_pcrud ) {
2094 target = jsonObjectGetIndex( ctx->params, 1 );
2095 options = jsonObjectGetIndex( ctx->params, 2 );
2097 target = jsonObjectGetIndex( ctx->params, 0 );
2098 options = jsonObjectGetIndex( ctx->params, 1 );
2101 if( !verifyObjectClass( ctx, target )) {
2102 osrfAppRespondComplete( ctx, NULL );
2106 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2108 const char* trans_id = getXactId( ctx );
2110 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2112 osrfAppSessionStatus(
2114 OSRF_STATUS_BADREQUEST,
2115 "osrfMethodException",
2117 "No active transaction -- required for CREATE"
2119 osrfAppRespondComplete( ctx, NULL );
2123 // The following test is harmless but redundant. If a class is
2124 // readonly, we don't register a create method for it.
2125 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2126 osrfAppSessionStatus(
2128 OSRF_STATUS_BADREQUEST,
2129 "osrfMethodException",
2131 "Cannot INSERT readonly class"
2133 osrfAppRespondComplete( ctx, NULL );
2137 // Set the last_xact_id
2138 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2140 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2141 trans_id, target->classname, index);
2142 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2145 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2147 dbhandle = writehandle;
2149 osrfHash* fields = osrfHashGet( meta, "fields" );
2150 char* pkey = osrfHashGet( meta, "primarykey" );
2151 char* seq = osrfHashGet( meta, "sequence" );
2153 growing_buffer* table_buf = buffer_init( 128 );
2154 growing_buffer* col_buf = buffer_init( 128 );
2155 growing_buffer* val_buf = buffer_init( 128 );
2157 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2158 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2159 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2160 buffer_add( val_buf,"VALUES (" );
2164 osrfHash* field = NULL;
2165 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2166 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2168 const char* field_name = osrfHashIteratorKey( field_itr );
2170 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2173 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2176 if( field_object && field_object->classname ) {
2177 value = oilsFMGetString(
2179 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2181 } else if( field_object && JSON_BOOL == field_object->type ) {
2182 if( jsonBoolIsTrue( field_object ) )
2183 value = strdup( "t" );
2185 value = strdup( "f" );
2187 value = jsonObjectToSimpleString( field_object );
2193 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2194 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2197 buffer_add( col_buf, field_name );
2199 if( !field_object || field_object->type == JSON_NULL ) {
2200 buffer_add( val_buf, "DEFAULT" );
2202 } else if( !strcmp( get_primitive( field ), "number" )) {
2203 const char* numtype = get_datatype( field );
2204 if( !strcmp( numtype, "INT8" )) {
2205 buffer_fadd( val_buf, "%lld", atoll( value ));
2207 } else if( !strcmp( numtype, "INT" )) {
2208 buffer_fadd( val_buf, "%d", atoi( value ));
2210 } else if( !strcmp( numtype, "NUMERIC" )) {
2211 buffer_fadd( val_buf, "%f", atof( value ));
2214 if( dbi_conn_quote_string( writehandle, &value )) {
2215 OSRF_BUFFER_ADD( val_buf, value );
2218 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2219 osrfAppSessionStatus(
2221 OSRF_STATUS_INTERNALSERVERERROR,
2222 "osrfMethodException",
2224 "Error quoting string -- please see the error log for more details"
2227 buffer_free( table_buf );
2228 buffer_free( col_buf );
2229 buffer_free( val_buf );
2230 osrfAppRespondComplete( ctx, NULL );
2238 osrfHashIteratorFree( field_itr );
2240 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2241 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2243 char* table_str = buffer_release( table_buf );
2244 char* col_str = buffer_release( col_buf );
2245 char* val_str = buffer_release( val_buf );
2246 growing_buffer* sql = buffer_init( 128 );
2247 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2252 char* query = buffer_release( sql );
2254 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2256 jsonObject* obj = NULL;
2259 dbi_result result = dbi_conn_query( writehandle, query );
2261 obj = jsonNewObject( NULL );
2263 int errnum = dbi_conn_error( writehandle, &msg );
2266 "%s ERROR inserting %s object using query [%s]: %d %s",
2268 osrfHashGet(meta, "fieldmapper"),
2271 msg ? msg : "(No description available)"
2273 osrfAppSessionStatus(
2275 OSRF_STATUS_INTERNALSERVERERROR,
2276 "osrfMethodException",
2278 "INSERT error -- please see the error log for more details"
2280 if( !oilsIsDBConnected( writehandle ))
2281 osrfAppSessionPanic( ctx->session );
2284 dbi_result_free( result );
2286 char* id = oilsFMGetString( target, pkey );
2288 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2289 growing_buffer* _id = buffer_init( 10 );
2290 buffer_fadd( _id, "%lld", new_id );
2291 id = buffer_release( _id );
2294 // Find quietness specification, if present
2295 const char* quiet_str = NULL;
2297 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2299 quiet_str = jsonObjectGetString( quiet_obj );
2302 if( str_is_true( quiet_str )) { // if quietness is specified
2303 obj = jsonNewObject( id );
2307 // Fetch the row that we just inserted, so that we can return it to the client
2308 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2309 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2312 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2316 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2318 jsonObjectFree( list );
2319 jsonObjectFree( where_clause );
2326 osrfAppRespondComplete( ctx, obj );
2327 jsonObjectFree( obj );
2332 @brief Implement the retrieve method.
2333 @param ctx Pointer to the method context.
2334 @param err Pointer through which to return an error code.
2335 @return If successful, a pointer to the result to be returned to the client;
2338 From the method's class, fetch a row with a specified value in the primary key. This
2339 method relies on the database design convention that a primary key consists of a single
2343 - authkey (PCRUD only)
2344 - value of the primary key for the desired row, for building the WHERE clause
2345 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2347 Return to client: One row from the query.
2349 int doRetrieve( osrfMethodContext* ctx ) {
2350 if(osrfMethodVerifyContext( ctx )) {
2351 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2356 timeout_needs_resetting = 1;
2361 if( enforce_pcrud ) {
2366 // Get the class metadata
2367 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2369 // Get the value of the primary key, from a method parameter
2370 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2374 "%s retrieving %s object with primary key value of %s",
2376 osrfHashGet( class_def, "fieldmapper" ),
2377 jsonObjectGetString( id_obj )
2380 // Build a WHERE clause based on the key value
2381 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2384 osrfHashGet( class_def, "primarykey" ), // name of key column
2385 jsonObjectClone( id_obj ) // value of key column
2388 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2392 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2394 jsonObjectFree( where_clause );
2396 osrfAppRespondComplete( ctx, NULL );
2400 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2401 jsonObjectFree( list );
2403 if( enforce_pcrud ) {
2404 // no result, skip this entirely
2405 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2406 jsonObjectFree( obj );
2408 growing_buffer* msg = buffer_init( 128 );
2409 OSRF_BUFFER_ADD( msg, modulename );
2410 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2412 char* m = buffer_release( msg );
2413 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2417 osrfAppRespondComplete( ctx, NULL );
2422 // doFieldmapperSearch() now does the responding for us
2423 //osrfAppRespondComplete( ctx, obj );
2424 osrfAppRespondComplete( ctx, NULL );
2426 jsonObjectFree( obj );
2431 @brief Translate a numeric value to a string representation for the database.
2432 @param field Pointer to the IDL field definition.
2433 @param value Pointer to a jsonObject holding the value of a field.
2434 @return Pointer to a newly allocated string.
2436 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2437 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2438 or (what is worse) valid SQL that is wrong.
2440 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2442 The calling code is responsible for freeing the resulting string by calling free().
2444 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2445 growing_buffer* val_buf = buffer_init( 32 );
2446 const char* numtype = get_datatype( field );
2448 // For historical reasons the following contains cruft that could be cleaned up.
2449 if( !strncmp( numtype, "INT", 3 ) ) {
2450 if( value->type == JSON_NUMBER )
2451 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2452 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2454 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2457 } else if( !strcmp( numtype, "NUMERIC" )) {
2458 if( value->type == JSON_NUMBER )
2459 buffer_fadd( val_buf, jsonObjectGetString( value ));
2461 buffer_fadd( val_buf, jsonObjectGetString( value ));
2465 // Presumably this was really intended to be a string, so quote it
2466 char* str = jsonObjectToSimpleString( value );
2467 if( dbi_conn_quote_string( dbhandle, &str )) {
2468 OSRF_BUFFER_ADD( val_buf, str );
2471 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2473 buffer_free( val_buf );
2478 return buffer_release( val_buf );
2481 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2482 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2483 growing_buffer* sql_buf = buffer_init( 32 );
2489 osrfHashGet( field, "name" )
2493 buffer_add( sql_buf, "IN (" );
2494 } else if( !strcasecmp( op,"not in" )) {
2495 buffer_add( sql_buf, "NOT IN (" );
2497 buffer_add( sql_buf, "IN (" );
2500 if( node->type == JSON_HASH ) {
2501 // subquery predicate
2502 char* subpred = buildQuery( ctx, node, SUBSELECT );
2504 buffer_free( sql_buf );
2508 buffer_add( sql_buf, subpred );
2511 } else if( node->type == JSON_ARRAY ) {
2512 // literal value list
2513 int in_item_index = 0;
2514 int in_item_first = 1;
2515 const jsonObject* in_item;
2516 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2521 buffer_add( sql_buf, ", " );
2524 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2525 osrfLogError( OSRF_LOG_MARK,
2526 "%s: Expected string or number within IN list; found %s",
2527 modulename, json_type( in_item->type ) );
2528 buffer_free( sql_buf );
2532 // Append the literal value -- quoted if not a number
2533 if( JSON_NUMBER == in_item->type ) {
2534 char* val = jsonNumberToDBString( field, in_item );
2535 OSRF_BUFFER_ADD( sql_buf, val );
2538 } else if( !strcmp( get_primitive( field ), "number" )) {
2539 char* val = jsonNumberToDBString( field, in_item );
2540 OSRF_BUFFER_ADD( sql_buf, val );
2544 char* key_string = jsonObjectToSimpleString( in_item );
2545 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2546 OSRF_BUFFER_ADD( sql_buf, key_string );
2549 osrfLogError( OSRF_LOG_MARK,
2550 "%s: Error quoting key string [%s]", modulename, key_string );
2552 buffer_free( sql_buf );
2558 if( in_item_first ) {
2559 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2560 buffer_free( sql_buf );
2564 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2565 modulename, json_type( node->type ));
2566 buffer_free( sql_buf );
2570 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2572 return buffer_release( sql_buf );
2575 // Receive a JSON_ARRAY representing a function call. The first
2576 // entry in the array is the function name. The rest are parameters.
2577 static char* searchValueTransform( const jsonObject* array ) {
2579 if( array->size < 1 ) {
2580 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2584 // Get the function name
2585 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2586 if( func_item->type != JSON_STRING ) {
2587 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2588 modulename, json_type( func_item->type ));
2592 growing_buffer* sql_buf = buffer_init( 32 );
2594 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2595 OSRF_BUFFER_ADD( sql_buf, "( " );
2597 // Get the parameters
2598 int func_item_index = 1; // We already grabbed the zeroth entry
2599 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2601 // Add a separator comma, if we need one
2602 if( func_item_index > 2 )
2603 buffer_add( sql_buf, ", " );
2605 // Add the current parameter
2606 if( func_item->type == JSON_NULL ) {
2607 buffer_add( sql_buf, "NULL" );
2609 if( func_item->type == JSON_BOOL ) {
2610 if( jsonBoolIsTrue(func_item) ) {
2611 buffer_add( sql_buf, "TRUE" );
2613 buffer_add( sql_buf, "FALSE" );
2616 char* val = jsonObjectToSimpleString( func_item );
2617 if( dbi_conn_quote_string( dbhandle, &val )) {
2618 OSRF_BUFFER_ADD( sql_buf, val );
2621 osrfLogError( OSRF_LOG_MARK,
2622 "%s: Error quoting key string [%s]", modulename, val );
2623 buffer_free( sql_buf );
2631 buffer_add( sql_buf, " )" );
2633 return buffer_release( sql_buf );
2636 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2637 const jsonObject* node, const char* op ) {
2639 if( ! is_good_operator( op ) ) {
2640 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2644 char* val = searchValueTransform( node );
2648 growing_buffer* sql_buf = buffer_init( 32 );
2653 osrfHashGet( field, "name" ),
2660 return buffer_release( sql_buf );
2663 // class_alias is a class name or other table alias
2664 // field is a field definition as stored in the IDL
2665 // node comes from the method parameter, and may represent an entry in the SELECT list
2666 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2667 const jsonObject* node ) {
2668 growing_buffer* sql_buf = buffer_init( 32 );
2670 const char* field_transform = jsonObjectGetString(
2671 jsonObjectGetKeyConst( node, "transform" ) );
2672 const char* transform_subcolumn = jsonObjectGetString(
2673 jsonObjectGetKeyConst( node, "result_field" ) );
2675 if( transform_subcolumn ) {
2676 if( ! is_identifier( transform_subcolumn ) ) {
2677 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2678 modulename, transform_subcolumn );
2679 buffer_free( sql_buf );
2682 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2685 if( field_transform ) {
2687 if( ! is_identifier( field_transform ) ) {
2688 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2689 modulename, field_transform );
2690 buffer_free( sql_buf );
2694 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2695 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2696 field_transform, class_alias, osrfHashGet( field, "name" ));
2698 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2699 field_transform, class_alias, osrfHashGet( field, "name" ));
2702 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2705 if( array->type != JSON_ARRAY ) {
2706 osrfLogError( OSRF_LOG_MARK,
2707 "%s: Expected JSON_ARRAY for function params; found %s",
2708 modulename, json_type( array->type ) );
2709 buffer_free( sql_buf );
2712 int func_item_index = 0;
2713 jsonObject* func_item;
2714 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2716 char* val = jsonObjectToSimpleString( func_item );
2719 buffer_add( sql_buf, ",NULL" );
2720 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2721 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2722 OSRF_BUFFER_ADD( sql_buf, val );
2724 osrfLogError( OSRF_LOG_MARK,
2725 "%s: Error quoting key string [%s]", modulename, val );
2727 buffer_free( sql_buf );
2734 buffer_add( sql_buf, " )" );
2737 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2740 if( transform_subcolumn )
2741 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2743 return buffer_release( sql_buf );
2746 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2747 const jsonObject* node, const char* op ) {
2749 if( ! is_good_operator( op ) ) {
2750 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2754 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2755 if( ! field_transform )
2758 int extra_parens = 0; // boolean
2760 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2762 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2764 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2766 free( field_transform );
2770 } else if( value_obj->type == JSON_ARRAY ) {
2771 value = searchValueTransform( value_obj );
2773 osrfLogError( OSRF_LOG_MARK,
2774 "%s: Error building value transform for field transform", modulename );
2775 free( field_transform );
2778 } else if( value_obj->type == JSON_HASH ) {
2779 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2781 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2783 free( field_transform );
2787 } else if( value_obj->type == JSON_NUMBER ) {
2788 value = jsonNumberToDBString( field, value_obj );
2789 } else if( value_obj->type == JSON_NULL ) {
2790 osrfLogError( OSRF_LOG_MARK,
2791 "%s: Error building predicate for field transform: null value", modulename );
2792 free( field_transform );
2794 } else if( value_obj->type == JSON_BOOL ) {
2795 osrfLogError( OSRF_LOG_MARK,
2796 "%s: Error building predicate for field transform: boolean value", modulename );
2797 free( field_transform );
2800 if( !strcmp( get_primitive( field ), "number") ) {
2801 value = jsonNumberToDBString( field, value_obj );
2803 value = jsonObjectToSimpleString( value_obj );
2804 if( !dbi_conn_quote_string( dbhandle, &value )) {
2805 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2806 modulename, value );
2808 free( field_transform );
2814 const char* left_parens = "";
2815 const char* right_parens = "";
2817 if( extra_parens ) {
2822 const char* right_percent = "";
2823 const char* real_op = op;
2825 if( !strcasecmp( op, "startwith") ) {
2827 right_percent = "|| '%'";
2830 growing_buffer* sql_buf = buffer_init( 32 );
2834 "%s%s %s %s %s%s %s%s",
2846 free( field_transform );
2848 return buffer_release( sql_buf );
2851 static char* searchSimplePredicate( const char* op, const char* class_alias,
2852 osrfHash* field, const jsonObject* node ) {
2854 if( ! is_good_operator( op ) ) {
2855 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2861 // Get the value to which we are comparing the specified column
2862 if( node->type != JSON_NULL ) {
2863 if( node->type == JSON_NUMBER ) {
2864 val = jsonNumberToDBString( field, node );
2865 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2866 val = jsonNumberToDBString( field, node );
2868 val = jsonObjectToSimpleString( node );
2873 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2874 // Value is not numeric; enclose it in quotes
2875 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2876 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2883 // Compare to a null value
2884 val = strdup( "NULL" );
2885 if( strcmp( op, "=" ))
2891 growing_buffer* sql_buf = buffer_init( 32 );
2892 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2893 char* pred = buffer_release( sql_buf );
2900 static char* searchBETWEENPredicate( const char* class_alias,
2901 osrfHash* field, const jsonObject* node ) {
2903 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2904 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2906 if( NULL == y_node ) {
2907 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2910 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2911 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2918 if( !strcmp( get_primitive( field ), "number") ) {
2919 x_string = jsonNumberToDBString( field, x_node );
2920 y_string = jsonNumberToDBString( field, y_node );
2923 x_string = jsonObjectToSimpleString( x_node );
2924 y_string = jsonObjectToSimpleString( y_node );
2925 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2926 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2927 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2928 modulename, x_string, y_string );
2935 growing_buffer* sql_buf = buffer_init( 32 );
2936 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2937 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2941 return buffer_release( sql_buf );
2944 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2945 jsonObject* node, osrfMethodContext* ctx ) {
2948 if( node->type == JSON_ARRAY ) { // equality IN search
2949 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2950 } else if( node->type == JSON_HASH ) { // other search
2951 jsonIterator* pred_itr = jsonNewIterator( node );
2952 if( !jsonIteratorHasNext( pred_itr ) ) {
2953 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2954 modulename, osrfHashGet(field, "name" ));
2956 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2958 // Verify that there are no additional predicates
2959 if( jsonIteratorHasNext( pred_itr ) ) {
2960 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2961 modulename, osrfHashGet(field, "name" ));
2962 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2963 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2964 else if( !(strcasecmp( pred_itr->key,"in" ))
2965 || !(strcasecmp( pred_itr->key,"not in" )) )
2966 pred = searchINPredicate(
2967 class_info->alias, field, pred_node, pred_itr->key, ctx );
2968 else if( pred_node->type == JSON_ARRAY )
2969 pred = searchFunctionPredicate(
2970 class_info->alias, field, pred_node, pred_itr->key );
2971 else if( pred_node->type == JSON_HASH )
2972 pred = searchFieldTransformPredicate(
2973 class_info, field, pred_node, pred_itr->key );
2975 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2977 jsonIteratorFree( pred_itr );
2979 } else if( node->type == JSON_NULL ) { // IS NULL search
2980 growing_buffer* _p = buffer_init( 64 );
2983 "\"%s\".%s IS NULL",
2985 osrfHashGet( field, "name" )
2987 pred = buffer_release( _p );
2988 } else { // equality search
2989 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3008 field : call_number,
3024 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3026 const jsonObject* working_hash;
3027 jsonObject* freeable_hash = NULL;
3029 if( join_hash->type == JSON_HASH ) {
3030 working_hash = join_hash;
3031 } else if( join_hash->type == JSON_STRING ) {
3032 // turn it into a JSON_HASH by creating a wrapper
3033 // around a copy of the original
3034 const char* _tmp = jsonObjectGetString( join_hash );
3035 freeable_hash = jsonNewObjectType( JSON_HASH );
3036 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3037 working_hash = freeable_hash;
3041 "%s: JOIN failed; expected JSON object type not found",
3047 growing_buffer* join_buf = buffer_init( 128 );
3048 const char* leftclass = left_info->class_name;
3050 jsonObject* snode = NULL;
3051 jsonIterator* search_itr = jsonNewIterator( working_hash );
3053 while ( (snode = jsonIteratorNext( search_itr )) ) {
3054 const char* right_alias = search_itr->key;
3056 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3058 class = right_alias;
3060 const ClassInfo* right_info = add_joined_class( right_alias, class );
3064 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3068 jsonIteratorFree( search_itr );
3069 buffer_free( join_buf );
3071 jsonObjectFree( freeable_hash );
3074 osrfHash* links = right_info->links;
3075 const char* table = right_info->source_def;
3077 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3078 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3080 if( field && !fkey ) {
3081 // Look up the corresponding join column in the IDL.
3082 // The link must be defined in the child table,
3083 // and point to the right parent table.
3084 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3085 const char* reltype = NULL;
3086 const char* other_class = NULL;
3087 reltype = osrfHashGet( idl_link, "reltype" );
3088 if( reltype && strcmp( reltype, "has_many" ) )
3089 other_class = osrfHashGet( idl_link, "class" );
3090 if( other_class && !strcmp( other_class, leftclass ) )
3091 fkey = osrfHashGet( idl_link, "key" );
3095 "%s: JOIN failed. No link defined from %s.%s to %s",
3101 buffer_free( join_buf );
3103 jsonObjectFree( freeable_hash );
3104 jsonIteratorFree( search_itr );
3108 } else if( !field && fkey ) {
3109 // Look up the corresponding join column in the IDL.
3110 // The link must be defined in the child table,
3111 // and point to the right parent table.
3112 osrfHash* left_links = left_info->links;
3113 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3114 const char* reltype = NULL;
3115 const char* other_class = NULL;
3116 reltype = osrfHashGet( idl_link, "reltype" );
3117 if( reltype && strcmp( reltype, "has_many" ) )
3118 other_class = osrfHashGet( idl_link, "class" );
3119 if( other_class && !strcmp( other_class, class ) )
3120 field = osrfHashGet( idl_link, "key" );
3124 "%s: JOIN failed. No link defined from %s.%s to %s",
3130 buffer_free( join_buf );
3132 jsonObjectFree( freeable_hash );
3133 jsonIteratorFree( search_itr );
3137 } else if( !field && !fkey ) {
3138 osrfHash* left_links = left_info->links;
3140 // For each link defined for the left class:
3141 // see if the link references the joined class
3142 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3143 osrfHash* curr_link = NULL;
3144 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3145 const char* other_class = osrfHashGet( curr_link, "class" );
3146 if( other_class && !strcmp( other_class, class ) ) {
3148 // In the IDL, the parent class doesn't always know then names of the child
3149 // columns that are pointing to it, so don't use that end of the link
3150 const char* reltype = osrfHashGet( curr_link, "reltype" );
3151 if( reltype && strcmp( reltype, "has_many" ) ) {
3152 // Found a link between the classes
3153 fkey = osrfHashIteratorKey( itr );
3154 field = osrfHashGet( curr_link, "key" );
3159 osrfHashIteratorFree( itr );
3161 if( !field || !fkey ) {
3162 // Do another such search, with the classes reversed
3164 // For each link defined for the joined class:
3165 // see if the link references the left class
3166 osrfHashIterator* itr = osrfNewHashIterator( links );
3167 osrfHash* curr_link = NULL;
3168 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3169 const char* other_class = osrfHashGet( curr_link, "class" );
3170 if( other_class && !strcmp( other_class, leftclass ) ) {
3172 // In the IDL, the parent class doesn't know then names of the child
3173 // columns that are pointing to it, so don't use that end of the link
3174 const char* reltype = osrfHashGet( curr_link, "reltype" );
3175 if( reltype && strcmp( reltype, "has_many" ) ) {
3176 // Found a link between the classes
3177 field = osrfHashIteratorKey( itr );
3178 fkey = osrfHashGet( curr_link, "key" );
3183 osrfHashIteratorFree( itr );
3186 if( !field || !fkey ) {
3189 "%s: JOIN failed. No link defined between %s and %s",
3194 buffer_free( join_buf );
3196 jsonObjectFree( freeable_hash );
3197 jsonIteratorFree( search_itr );
3202 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3204 if( !strcasecmp( type,"left" )) {
3205 buffer_add( join_buf, " LEFT JOIN" );
3206 } else if( !strcasecmp( type,"right" )) {
3207 buffer_add( join_buf, " RIGHT JOIN" );
3208 } else if( !strcasecmp( type,"full" )) {
3209 buffer_add( join_buf, " FULL JOIN" );
3211 buffer_add( join_buf, " INNER JOIN" );
3214 buffer_add( join_buf, " INNER JOIN" );
3217 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3218 table, right_alias, right_alias, field, left_info->alias, fkey );
3220 // Add any other join conditions as specified by "filter"
3221 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3223 const char* filter_op = jsonObjectGetString(
3224 jsonObjectGetKeyConst( snode, "filter_op" ) );
3225 if( filter_op && !strcasecmp( "or",filter_op )) {
3226 buffer_add( join_buf, " OR " );
3228 buffer_add( join_buf, " AND " );
3231 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3233 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3234 OSRF_BUFFER_ADD( join_buf, jpred );
3239 "%s: JOIN failed. Invalid conditional expression.",
3242 jsonIteratorFree( search_itr );
3243 buffer_free( join_buf );
3245 jsonObjectFree( freeable_hash );
3250 buffer_add( join_buf, " ) " );
3252 // Recursively add a nested join, if one is present
3253 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3255 char* jpred = searchJOIN( join_filter, right_info );
3257 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3258 OSRF_BUFFER_ADD( join_buf, jpred );
3261 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3262 jsonIteratorFree( search_itr );
3263 buffer_free( join_buf );
3265 jsonObjectFree( freeable_hash );
3272 jsonObjectFree( freeable_hash );
3273 jsonIteratorFree( search_itr );
3275 return buffer_release( join_buf );
3280 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3281 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3282 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3284 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3286 search_hash is the JSON expression of the conditions.
3287 meta is the class definition from the IDL, for the relevant table.
3288 opjoin_type indicates whether multiple conditions, if present, should be
3289 connected by AND or OR.
3290 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3291 to pass it to other functions -- and all they do with it is to use the session
3292 and request members to send error messages back to the client.
3296 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3297 int opjoin_type, osrfMethodContext* ctx ) {
3301 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3302 "opjoin_type = %d, ctx addr = %p",
3305 class_info->class_def,
3310 growing_buffer* sql_buf = buffer_init( 128 );
3312 jsonObject* node = NULL;
3315 if( search_hash->type == JSON_ARRAY ) {
3316 if( 0 == search_hash->size ) {
3319 "%s: Invalid predicate structure: empty JSON array",
3322 buffer_free( sql_buf );
3326 unsigned long i = 0;
3327 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3331 if( opjoin_type == OR_OP_JOIN )
3332 buffer_add( sql_buf, " OR " );
3334 buffer_add( sql_buf, " AND " );
3337 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3339 buffer_free( sql_buf );
3343 buffer_fadd( sql_buf, "( %s )", subpred );
3347 } else if( search_hash->type == JSON_HASH ) {
3348 osrfLogDebug( OSRF_LOG_MARK,
3349 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3350 jsonIterator* search_itr = jsonNewIterator( search_hash );
3351 if( !jsonIteratorHasNext( search_itr ) ) {
3354 "%s: Invalid predicate structure: empty JSON object",
3357 jsonIteratorFree( search_itr );
3358 buffer_free( sql_buf );
3362 while( (node = jsonIteratorNext( search_itr )) ) {
3367 if( opjoin_type == OR_OP_JOIN )
3368 buffer_add( sql_buf, " OR " );
3370 buffer_add( sql_buf, " AND " );
3373 if( '+' == search_itr->key[ 0 ] ) {
3375 // This plus sign prefixes a class name or other table alias;
3376 // make sure the table alias is in scope
3377 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3378 if( ! alias_info ) {
3381 "%s: Invalid table alias \"%s\" in WHERE clause",
3385 jsonIteratorFree( search_itr );
3386 buffer_free( sql_buf );
3390 if( node->type == JSON_STRING ) {
3391 // It's the name of a column; make sure it belongs to the class
3392 const char* fieldname = jsonObjectGetString( node );
3393 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3396 "%s: Invalid column name \"%s\" in WHERE clause "
3397 "for table alias \"%s\"",
3402 jsonIteratorFree( search_itr );
3403 buffer_free( sql_buf );
3407 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3409 // It's something more complicated
3410 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3412 jsonIteratorFree( search_itr );
3413 buffer_free( sql_buf );
3417 buffer_fadd( sql_buf, "( %s )", subpred );
3420 } else if( '-' == search_itr->key[ 0 ] ) {
3421 if( !strcasecmp( "-or", search_itr->key )) {
3422 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3424 jsonIteratorFree( search_itr );
3425 buffer_free( sql_buf );
3429 buffer_fadd( sql_buf, "( %s )", subpred );
3431 } else if( !strcasecmp( "-and", search_itr->key )) {
3432 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3434 jsonIteratorFree( search_itr );
3435 buffer_free( sql_buf );
3439 buffer_fadd( sql_buf, "( %s )", subpred );
3441 } else if( !strcasecmp("-not",search_itr->key) ) {
3442 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3444 jsonIteratorFree( search_itr );
3445 buffer_free( sql_buf );
3449 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3451 } else if( !strcasecmp( "-exists", search_itr->key )) {
3452 char* subpred = buildQuery( ctx, node, SUBSELECT );
3454 jsonIteratorFree( search_itr );
3455 buffer_free( sql_buf );
3459 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3461 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3462 char* subpred = buildQuery( ctx, node, SUBSELECT );
3464 jsonIteratorFree( search_itr );
3465 buffer_free( sql_buf );
3469 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3471 } else { // Invalid "minus" operator
3474 "%s: Invalid operator \"%s\" in WHERE clause",
3478 jsonIteratorFree( search_itr );
3479 buffer_free( sql_buf );
3485 const char* class = class_info->class_name;
3486 osrfHash* fields = class_info->fields;
3487 osrfHash* field = osrfHashGet( fields, search_itr->key );
3490 const char* table = class_info->source_def;
3493 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3496 table ? table : "?",
3499 jsonIteratorFree( search_itr );
3500 buffer_free( sql_buf );
3504 char* subpred = searchPredicate( class_info, field, node, ctx );
3506 buffer_free( sql_buf );
3507 jsonIteratorFree( search_itr );
3511 buffer_add( sql_buf, subpred );
3515 jsonIteratorFree( search_itr );
3518 // ERROR ... only hash and array allowed at this level
3519 char* predicate_string = jsonObjectToJSON( search_hash );
3522 "%s: Invalid predicate structure: %s",
3526 buffer_free( sql_buf );
3527 free( predicate_string );
3531 return buffer_release( sql_buf );
3534 /* Build a JSON_ARRAY of field names for a given table alias
3536 static jsonObject* defaultSelectList( const char* table_alias ) {
3541 ClassInfo* class_info = search_all_alias( table_alias );
3542 if( ! class_info ) {
3545 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3552 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3553 osrfHash* field_def = NULL;
3554 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3555 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3556 const char* field_name = osrfHashIteratorKey( field_itr );
3557 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3558 jsonObjectPush( array, jsonNewObject( field_name ) );
3561 osrfHashIteratorFree( field_itr );
3566 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3567 // The jsonObject must be a JSON_HASH with an single entry for "union",
3568 // "intersect", or "except". The data associated with this key must be an
3569 // array of hashes, each hash being a query.
3570 // Also allowed but currently ignored: entries for "order_by" and "alias".
3571 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3573 if( ! combo || combo->type != JSON_HASH )
3574 return NULL; // should be impossible; validated by caller
3576 const jsonObject* query_array = NULL; // array of subordinate queries
3577 const char* op = NULL; // name of operator, e.g. UNION
3578 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3579 int op_count = 0; // for detecting conflicting operators
3580 int excepting = 0; // boolean
3581 int all = 0; // boolean
3582 jsonObject* order_obj = NULL;
3584 // Identify the elements in the hash
3585 jsonIterator* query_itr = jsonNewIterator( combo );
3586 jsonObject* curr_obj = NULL;
3587 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3588 if( ! strcmp( "union", query_itr->key ) ) {
3591 query_array = curr_obj;
3592 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3595 query_array = curr_obj;
3596 } else if( ! strcmp( "except", query_itr->key ) ) {
3600 query_array = curr_obj;
3601 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3604 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3607 order_obj = curr_obj;
3608 } else if( ! strcmp( "alias", query_itr->key ) ) {
3609 if( curr_obj->type != JSON_STRING ) {
3610 jsonIteratorFree( query_itr );
3613 alias = jsonObjectGetString( curr_obj );
3614 } else if( ! strcmp( "all", query_itr->key ) ) {
3615 if( obj_is_true( curr_obj ) )
3619 osrfAppSessionStatus(
3621 OSRF_STATUS_INTERNALSERVERERROR,
3622 "osrfMethodException",
3624 "Malformed query; unexpected entry in query object"
3628 "%s: Unexpected entry for \"%s\" in%squery",
3633 jsonIteratorFree( query_itr );
3637 jsonIteratorFree( query_itr );
3639 // More sanity checks
3640 if( ! query_array ) {
3642 osrfAppSessionStatus(
3644 OSRF_STATUS_INTERNALSERVERERROR,
3645 "osrfMethodException",
3647 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3651 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3654 return NULL; // should be impossible...
3655 } else if( op_count > 1 ) {
3657 osrfAppSessionStatus(
3659 OSRF_STATUS_INTERNALSERVERERROR,
3660 "osrfMethodException",
3662 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3666 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3670 } if( query_array->type != JSON_ARRAY ) {
3672 osrfAppSessionStatus(
3674 OSRF_STATUS_INTERNALSERVERERROR,
3675 "osrfMethodException",
3677 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3681 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3684 json_type( query_array->type )
3687 } if( query_array->size < 2 ) {
3689 osrfAppSessionStatus(
3691 OSRF_STATUS_INTERNALSERVERERROR,
3692 "osrfMethodException",
3694 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3698 "%s:%srequires multiple queries as operands",
3703 } else if( excepting && query_array->size > 2 ) {
3705 osrfAppSessionStatus(
3707 OSRF_STATUS_INTERNALSERVERERROR,
3708 "osrfMethodException",
3710 "EXCEPT operator has too many queries as operands"
3714 "%s:EXCEPT operator has too many queries as operands",
3718 } else if( order_obj && ! alias ) {
3720 osrfAppSessionStatus(
3722 OSRF_STATUS_INTERNALSERVERERROR,
3723 "osrfMethodException",
3725 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3729 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3735 // So far so good. Now build the SQL.
3736 growing_buffer* sql = buffer_init( 256 );
3738 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3739 // Add a layer of parentheses
3740 if( flags & SUBCOMBO )
3741 OSRF_BUFFER_ADD( sql, "( " );
3743 // Traverse the query array. Each entry should be a hash.
3744 int first = 1; // boolean
3746 jsonObject* query = NULL;
3747 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3748 if( query->type != JSON_HASH ) {
3750 osrfAppSessionStatus(
3752 OSRF_STATUS_INTERNALSERVERERROR,
3753 "osrfMethodException",
3755 "Malformed query under UNION, INTERSECT or EXCEPT"
3759 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3762 json_type( query->type )
3771 OSRF_BUFFER_ADD( sql, op );
3773 OSRF_BUFFER_ADD( sql, "ALL " );
3776 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3780 "%s: Error building query under%s",
3788 OSRF_BUFFER_ADD( sql, query_str );
3791 if( flags & SUBCOMBO )
3792 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3794 if( !(flags & SUBSELECT) )
3795 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3797 return buffer_release( sql );
3800 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3801 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3802 // or "except" to indicate the type of query.
3803 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3807 osrfAppSessionStatus(
3809 OSRF_STATUS_INTERNALSERVERERROR,
3810 "osrfMethodException",
3812 "Malformed query; no query object"
3814 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3816 } else if( query->type != JSON_HASH ) {
3818 osrfAppSessionStatus(
3820 OSRF_STATUS_INTERNALSERVERERROR,
3821 "osrfMethodException",
3823 "Malformed query object"
3827 "%s: Query object is %s instead of JSON_HASH",
3829 json_type( query->type )
3834 // Determine what kind of query it purports to be, and dispatch accordingly.
3835 if( jsonObjectGetKeyConst( query, "union" ) ||
3836 jsonObjectGetKeyConst( query, "intersect" ) ||
3837 jsonObjectGetKeyConst( query, "except" )) {
3838 return doCombo( ctx, query, flags );
3840 // It is presumably a SELECT query
3842 // Push a node onto the stack for the current query. Every level of
3843 // subquery gets its own QueryFrame on the Stack.
3846 // Build an SQL SELECT statement
3849 jsonObjectGetKey( query, "select" ),
3850 jsonObjectGetKeyConst( query, "from" ),
3851 jsonObjectGetKeyConst( query, "where" ),
3852 jsonObjectGetKeyConst( query, "having" ),
3853 jsonObjectGetKeyConst( query, "order_by" ),
3854 jsonObjectGetKeyConst( query, "limit" ),
3855 jsonObjectGetKeyConst( query, "offset" ),
3864 /* method context */ osrfMethodContext* ctx,
3866 /* SELECT */ jsonObject* selhash,
3867 /* FROM */ const jsonObject* join_hash,
3868 /* WHERE */ const jsonObject* search_hash,
3869 /* HAVING */ const jsonObject* having_hash,
3870 /* ORDER BY */ const jsonObject* order_hash,
3871 /* LIMIT */ const jsonObject* limit,
3872 /* OFFSET */ const jsonObject* offset,
3873 /* flags */ int flags
3875 const char* locale = osrf_message_get_last_locale();
3877 // general tmp objects
3878 const jsonObject* tmp_const;
3879 jsonObject* selclass = NULL;
3880 jsonObject* snode = NULL;
3881 jsonObject* onode = NULL;
3883 char* string = NULL;
3884 int from_function = 0;
3889 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3891 // punt if there's no FROM clause
3892 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3895 "%s: FROM clause is missing or empty",
3899 osrfAppSessionStatus(
3901 OSRF_STATUS_INTERNALSERVERERROR,
3902 "osrfMethodException",
3904 "FROM clause is missing or empty in JSON query"
3909 // the core search class
3910 const char* core_class = NULL;
3912 // get the core class -- the only key of the top level FROM clause, or a string
3913 if( join_hash->type == JSON_HASH ) {
3914 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3915 snode = jsonIteratorNext( tmp_itr );
3917 // Populate the current QueryFrame with information
3918 // about the core class
3919 if( add_query_core( NULL, tmp_itr->key ) ) {
3921 osrfAppSessionStatus(
3923 OSRF_STATUS_INTERNALSERVERERROR,
3924 "osrfMethodException",
3926 "Unable to look up core class"
3930 core_class = curr_query->core.class_name;
3933 jsonObject* extra = jsonIteratorNext( tmp_itr );
3935 jsonIteratorFree( tmp_itr );
3938 // There shouldn't be more than one entry in join_hash
3942 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3946 osrfAppSessionStatus(
3948 OSRF_STATUS_INTERNALSERVERERROR,
3949 "osrfMethodException",
3951 "Malformed FROM clause in JSON query"
3953 return NULL; // Malformed join_hash; extra entry
3955 } else if( join_hash->type == JSON_ARRAY ) {
3956 // We're selecting from a function, not from a table
3958 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3961 } else if( join_hash->type == JSON_STRING ) {
3962 // Populate the current QueryFrame with information
3963 // about the core class
3964 core_class = jsonObjectGetString( join_hash );
3966 if( add_query_core( NULL, core_class ) ) {
3968 osrfAppSessionStatus(
3970 OSRF_STATUS_INTERNALSERVERERROR,
3971 "osrfMethodException",
3973 "Unable to look up core class"
3981 "%s: FROM clause is unexpected JSON type: %s",
3983 json_type( join_hash->type )
3986 osrfAppSessionStatus(
3988 OSRF_STATUS_INTERNALSERVERERROR,
3989 "osrfMethodException",
3991 "Ill-formed FROM clause in JSON query"
3996 // Build the join clause, if any, while filling out the list
3997 // of joined classes in the current QueryFrame.
3998 char* join_clause = NULL;
3999 if( join_hash && ! from_function ) {
4001 join_clause = searchJOIN( join_hash, &curr_query->core );
4002 if( ! join_clause ) {
4004 osrfAppSessionStatus(
4006 OSRF_STATUS_INTERNALSERVERERROR,
4007 "osrfMethodException",
4009 "Unable to construct JOIN clause(s)"
4015 // For in case we don't get a select list
4016 jsonObject* defaultselhash = NULL;
4018 // if there is no select list, build a default select list ...
4019 if( !selhash && !from_function ) {
4020 jsonObject* default_list = defaultSelectList( core_class );
4021 if( ! default_list ) {
4023 osrfAppSessionStatus(
4025 OSRF_STATUS_INTERNALSERVERERROR,
4026 "osrfMethodException",
4028 "Unable to build default SELECT clause in JSON query"
4030 free( join_clause );
4035 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4036 jsonObjectSetKey( selhash, core_class, default_list );
4039 // The SELECT clause can be encoded only by a hash
4040 if( !from_function && selhash->type != JSON_HASH ) {
4043 "%s: Expected JSON_HASH for SELECT clause; found %s",
4045 json_type( selhash->type )
4049 osrfAppSessionStatus(
4051 OSRF_STATUS_INTERNALSERVERERROR,
4052 "osrfMethodException",
4054 "Malformed SELECT clause in JSON query"
4056 free( join_clause );
4060 // If you see a null or wild card specifier for the core class, or an
4061 // empty array, replace it with a default SELECT list
4062 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4064 int default_needed = 0; // boolean
4065 if( JSON_STRING == tmp_const->type
4066 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4068 else if( JSON_NULL == tmp_const->type )
4071 if( default_needed ) {
4072 // Build a default SELECT list
4073 jsonObject* default_list = defaultSelectList( core_class );
4074 if( ! default_list ) {
4076 osrfAppSessionStatus(
4078 OSRF_STATUS_INTERNALSERVERERROR,
4079 "osrfMethodException",
4081 "Can't build default SELECT clause in JSON query"
4083 free( join_clause );
4088 jsonObjectSetKey( selhash, core_class, default_list );
4092 // temp buffers for the SELECT list and GROUP BY clause
4093 growing_buffer* select_buf = buffer_init( 128 );
4094 growing_buffer* group_buf = buffer_init( 128 );
4096 int aggregate_found = 0; // boolean
4098 // Build a select list
4099 if( from_function ) // From a function we select everything
4100 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4103 // Build the SELECT list as SQL
4107 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4108 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4110 const char* cname = selclass_itr->key;
4112 // Make sure the target relation is in the FROM clause.
4114 // At this point join_hash is a step down from the join_hash we
4115 // received as a parameter. If the original was a JSON_STRING,
4116 // then json_hash is now NULL. If the original was a JSON_HASH,
4117 // then json_hash is now the first (and only) entry in it,
4118 // denoting the core class. We've already excluded the
4119 // possibility that the original was a JSON_ARRAY, because in
4120 // that case from_function would be non-NULL, and we wouldn't
4123 // If the current table alias isn't in scope, bail out
4124 ClassInfo* class_info = search_alias( cname );
4125 if( ! class_info ) {
4128 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4133 osrfAppSessionStatus(
4135 OSRF_STATUS_INTERNALSERVERERROR,
4136 "osrfMethodException",
4138 "Selected class not in FROM clause in JSON query"
4140 jsonIteratorFree( selclass_itr );
4141 buffer_free( select_buf );
4142 buffer_free( group_buf );
4143 if( defaultselhash )
4144 jsonObjectFree( defaultselhash );
4145 free( join_clause );
4149 if( selclass->type != JSON_ARRAY ) {
4152 "%s: Malformed SELECT list for class \"%s\"; not an array",
4157 osrfAppSessionStatus(
4159 OSRF_STATUS_INTERNALSERVERERROR,
4160 "osrfMethodException",
4162 "Selected class not in FROM clause in JSON query"
4165 jsonIteratorFree( selclass_itr );
4166 buffer_free( select_buf );
4167 buffer_free( group_buf );
4168 if( defaultselhash )
4169 jsonObjectFree( defaultselhash );
4170 free( join_clause );
4174 // Look up some attributes of the current class
4175 osrfHash* idlClass = class_info->class_def;
4176 osrfHash* class_field_set = class_info->fields;
4177 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4178 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4180 if( 0 == selclass->size ) {
4183 "%s: No columns selected from \"%s\"",
4189 // stitch together the column list for the current table alias...
4190 unsigned long field_idx = 0;
4191 jsonObject* selfield = NULL;
4192 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4194 // If we need a separator comma, add one
4198 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4201 // if the field specification is a string, add it to the list
4202 if( selfield->type == JSON_STRING ) {
4204 // Look up the field in the IDL
4205 const char* col_name = jsonObjectGetString( selfield );
4206 osrfHash* field_def;
4208 if (!osrfStringArrayContains(
4210 osrfHashGet( class_field_set, col_name ),
4211 "suppress_controller"),
4214 field_def = osrfHashGet( class_field_set, col_name );
4217 // No such field in current class
4220 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4226 osrfAppSessionStatus(
4228 OSRF_STATUS_INTERNALSERVERERROR,
4229 "osrfMethodException",
4231 "Selected column not defined in JSON query"
4233 jsonIteratorFree( selclass_itr );
4234 buffer_free( select_buf );
4235 buffer_free( group_buf );
4236 if( defaultselhash )
4237 jsonObjectFree( defaultselhash );
4238 free( join_clause );
4240 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4241 // Virtual field not allowed
4244 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4250 osrfAppSessionStatus(
4252 OSRF_STATUS_INTERNALSERVERERROR,
4253 "osrfMethodException",
4255 "Selected column may not be virtual in JSON query"
4257 jsonIteratorFree( selclass_itr );
4258 buffer_free( select_buf );
4259 buffer_free( group_buf );
4260 if( defaultselhash )
4261 jsonObjectFree( defaultselhash );
4262 free( join_clause );
4268 if( flags & DISABLE_I18N )
4271 i18n = osrfHashGet( field_def, "i18n" );
4273 if( str_is_true( i18n ) ) {
4274 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4275 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4276 class_tname, cname, col_name, class_pkey,
4277 cname, class_pkey, locale, col_name );
4279 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4280 cname, col_name, col_name );
4283 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4284 cname, col_name, col_name );
4287 // ... but it could be an object, in which case we check for a Field Transform
4288 } else if( selfield->type == JSON_HASH ) {
4290 const char* col_name = jsonObjectGetString(
4291 jsonObjectGetKeyConst( selfield, "column" ) );
4293 // Get the field definition from the IDL
4294 osrfHash* field_def;
4295 if (!osrfStringArrayContains(
4297 osrfHashGet( class_field_set, col_name ),
4298 "suppress_controller"),
4301 field_def = osrfHashGet( class_field_set, col_name );
4305 // No such field in current class
4308 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4314 osrfAppSessionStatus(
4316 OSRF_STATUS_INTERNALSERVERERROR,
4317 "osrfMethodException",
4319 "Selected column is not defined in JSON query"
4321 jsonIteratorFree( selclass_itr );
4322 buffer_free( select_buf );
4323 buffer_free( group_buf );
4324 if( defaultselhash )
4325 jsonObjectFree( defaultselhash );
4326 free( join_clause );
4328 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4329 // No such field in current class
4332 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4338 osrfAppSessionStatus(
4340 OSRF_STATUS_INTERNALSERVERERROR,
4341 "osrfMethodException",
4343 "Selected column is virtual in JSON query"
4345 jsonIteratorFree( selclass_itr );
4346 buffer_free( select_buf );
4347 buffer_free( group_buf );
4348 if( defaultselhash )
4349 jsonObjectFree( defaultselhash );
4350 free( join_clause );
4354 // Decide what to use as a column alias
4356 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4357 _alias = jsonObjectGetString( tmp_const );
4358 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4359 _alias = jsonObjectGetString( tmp_const );
4360 } else { // Use field name as the alias
4364 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4365 char* transform_str = searchFieldTransform(
4366 class_info->alias, field_def, selfield );
4367 if( transform_str ) {
4368 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4369 free( transform_str );
4372 osrfAppSessionStatus(
4374 OSRF_STATUS_INTERNALSERVERERROR,
4375 "osrfMethodException",
4377 "Unable to generate transform function in JSON query"
4379 jsonIteratorFree( selclass_itr );
4380 buffer_free( select_buf );
4381 buffer_free( group_buf );
4382 if( defaultselhash )
4383 jsonObjectFree( defaultselhash );
4384 free( join_clause );
4391 if( flags & DISABLE_I18N )
4394 i18n = osrfHashGet( field_def, "i18n" );
4396 if( str_is_true( i18n ) ) {
4397 buffer_fadd( select_buf,
4398 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4399 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4400 class_tname, cname, col_name, class_pkey, cname,
4401 class_pkey, locale, _alias );
4403 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4404 cname, col_name, _alias );
4407 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4408 cname, col_name, _alias );
4415 "%s: Selected item is unexpected JSON type: %s",
4417 json_type( selfield->type )
4420 osrfAppSessionStatus(
4422 OSRF_STATUS_INTERNALSERVERERROR,
4423 "osrfMethodException",
4425 "Ill-formed SELECT item in JSON query"
4427 jsonIteratorFree( selclass_itr );
4428 buffer_free( select_buf );
4429 buffer_free( group_buf );
4430 if( defaultselhash )
4431 jsonObjectFree( defaultselhash );
4432 free( join_clause );
4436 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4437 if( obj_is_true( agg_obj ) )
4438 aggregate_found = 1;
4440 // Append a comma (except for the first one)
4441 // and add the column to a GROUP BY clause
4445 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4447 buffer_fadd( group_buf, " %d", sel_pos );
4451 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4453 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( elfield, "aggregate");
4454 if ( ! obj_is_true( aggregate_obj ) ) {
4458 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4461 buffer_fadd(group_buf, " %d", sel_pos);
4464 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4468 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4471 _column = searchFieldTransform(class_info->alias, field, selfield);
4472 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4473 OSRF_BUFFER_ADD(group_buf, _column);
4474 _column = searchFieldTransform(class_info->alias, field, selfield);
4481 } // end while -- iterating across SELECT columns
4483 } // end while -- iterating across classes
4485 jsonIteratorFree( selclass_itr );
4488 char* col_list = buffer_release( select_buf );
4490 // Make sure the SELECT list isn't empty. This can happen, for example,
4491 // if we try to build a default SELECT clause from a non-core table.
4494 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4496 osrfAppSessionStatus(
4498 OSRF_STATUS_INTERNALSERVERERROR,
4499 "osrfMethodException",
4501 "SELECT list is empty"
4504 buffer_free( group_buf );
4505 if( defaultselhash )
4506 jsonObjectFree( defaultselhash );
4507 free( join_clause );
4513 table = searchValueTransform( join_hash );
4515 table = strdup( curr_query->core.source_def );
4519 osrfAppSessionStatus(
4521 OSRF_STATUS_INTERNALSERVERERROR,
4522 "osrfMethodException",
4524 "Unable to identify table for core class"
4527 buffer_free( group_buf );
4528 if( defaultselhash )
4529 jsonObjectFree( defaultselhash );
4530 free( join_clause );
4534 // Put it all together
4535 growing_buffer* sql_buf = buffer_init( 128 );
4536 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4540 // Append the join clause, if any
4542 buffer_add(sql_buf, join_clause );
4543 free( join_clause );
4546 char* order_by_list = NULL;
4547 char* having_buf = NULL;
4549 if( !from_function ) {
4551 // Build a WHERE clause, if there is one
4553 buffer_add( sql_buf, " WHERE " );
4555 // and it's on the WHERE clause
4556 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4559 osrfAppSessionStatus(
4561 OSRF_STATUS_INTERNALSERVERERROR,
4562 "osrfMethodException",
4564 "Severe query error in WHERE predicate -- see error log for more details"
4567 buffer_free( group_buf );
4568 buffer_free( sql_buf );
4569 if( defaultselhash )
4570 jsonObjectFree( defaultselhash );
4574 buffer_add( sql_buf, pred );
4578 // Build a HAVING clause, if there is one
4581 // and it's on the the WHERE clause
4582 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4584 if( ! having_buf ) {
4586 osrfAppSessionStatus(
4588 OSRF_STATUS_INTERNALSERVERERROR,
4589 "osrfMethodException",
4591 "Severe query error in HAVING predicate -- see error log for more details"
4594 buffer_free( group_buf );
4595 buffer_free( sql_buf );
4596 if( defaultselhash )
4597 jsonObjectFree( defaultselhash );
4602 // Build an ORDER BY clause, if there is one
4603 if( NULL == order_hash )
4604 ; // No ORDER BY? do nothing
4605 else if( JSON_ARRAY == order_hash->type ) {
4606 order_by_list = buildOrderByFromArray( ctx, order_hash );
4607 if( !order_by_list ) {
4609 buffer_free( group_buf );
4610 buffer_free( sql_buf );
4611 if( defaultselhash )
4612 jsonObjectFree( defaultselhash );
4615 } else if( JSON_HASH == order_hash->type ) {
4616 // This hash is keyed on class alias. Each class has either
4617 // an array of field names or a hash keyed on field name.
4618 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4619 jsonIterator* class_itr = jsonNewIterator( order_hash );
4620 while( (snode = jsonIteratorNext( class_itr )) ) {
4622 ClassInfo* order_class_info = search_alias( class_itr->key );
4623 if( ! order_class_info ) {
4624 osrfLogError( OSRF_LOG_MARK,
4625 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4626 modulename, class_itr->key );
4628 osrfAppSessionStatus(
4630 OSRF_STATUS_INTERNALSERVERERROR,
4631 "osrfMethodException",
4633 "Invalid class referenced in ORDER BY clause -- "
4634 "see error log for more details"
4636 jsonIteratorFree( class_itr );
4637 buffer_free( order_buf );
4639 buffer_free( group_buf );
4640 buffer_free( sql_buf );
4641 if( defaultselhash )
4642 jsonObjectFree( defaultselhash );
4646 osrfHash* field_list_def = order_class_info->fields;
4648 if( snode->type == JSON_HASH ) {
4650 // Hash is keyed on field names from the current class. For each field
4651 // there is another layer of hash to define the sorting details, if any,
4652 // or a string to indicate direction of sorting.
4653 jsonIterator* order_itr = jsonNewIterator( snode );
4654 while( (onode = jsonIteratorNext( order_itr )) ) {
4656 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4658 osrfLogError( OSRF_LOG_MARK,
4659 "%s: Invalid field \"%s\" in ORDER BY clause",
4660 modulename, order_itr->key );
4662 osrfAppSessionStatus(
4664 OSRF_STATUS_INTERNALSERVERERROR,
4665 "osrfMethodException",
4667 "Invalid field in ORDER BY clause -- "
4668 "see error log for more details"
4670 jsonIteratorFree( order_itr );
4671 jsonIteratorFree( class_itr );
4672 buffer_free( order_buf );
4674 buffer_free( group_buf );
4675 buffer_free( sql_buf );
4676 if( defaultselhash )
4677 jsonObjectFree( defaultselhash );
4679 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4680 osrfLogError( OSRF_LOG_MARK,
4681 "%s: Virtual field \"%s\" in ORDER BY clause",
4682 modulename, order_itr->key );
4684 osrfAppSessionStatus(
4686 OSRF_STATUS_INTERNALSERVERERROR,
4687 "osrfMethodException",
4689 "Virtual field in ORDER BY clause -- "
4690 "see error log for more details"
4692 jsonIteratorFree( order_itr );
4693 jsonIteratorFree( class_itr );
4694 buffer_free( order_buf );
4696 buffer_free( group_buf );
4697 buffer_free( sql_buf );
4698 if( defaultselhash )
4699 jsonObjectFree( defaultselhash );
4703 const char* direction = NULL;
4704 if( onode->type == JSON_HASH ) {
4705 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4706 string = searchFieldTransform(
4708 osrfHashGet( field_list_def, order_itr->key ),
4712 if( ctx ) osrfAppSessionStatus(
4714 OSRF_STATUS_INTERNALSERVERERROR,
4715 "osrfMethodException",
4717 "Severe query error in ORDER BY clause -- "
4718 "see error log for more details"
4720 jsonIteratorFree( order_itr );
4721 jsonIteratorFree( class_itr );
4723 buffer_free( group_buf );
4724 buffer_free( order_buf);
4725 buffer_free( sql_buf );
4726 if( defaultselhash )
4727 jsonObjectFree( defaultselhash );
4731 growing_buffer* field_buf = buffer_init( 16 );
4732 buffer_fadd( field_buf, "\"%s\".%s",
4733 class_itr->key, order_itr->key );
4734 string = buffer_release( field_buf );
4737 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4738 const char* dir = jsonObjectGetString( tmp_const );
4739 if(!strncasecmp( dir, "d", 1 )) {
4740 direction = " DESC";
4746 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4747 osrfLogError( OSRF_LOG_MARK,
4748 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4749 modulename, json_type( onode->type ) );
4751 osrfAppSessionStatus(
4753 OSRF_STATUS_INTERNALSERVERERROR,
4754 "osrfMethodException",
4756 "Malformed ORDER BY clause -- see error log for more details"
4758 jsonIteratorFree( order_itr );
4759 jsonIteratorFree( class_itr );
4761 buffer_free( group_buf );
4762 buffer_free( order_buf );
4763 buffer_free( sql_buf );
4764 if( defaultselhash )
4765 jsonObjectFree( defaultselhash );
4769 string = strdup( order_itr->key );
4770 const char* dir = jsonObjectGetString( onode );
4771 if( !strncasecmp( dir, "d", 1 )) {
4772 direction = " DESC";
4779 OSRF_BUFFER_ADD( order_buf, ", " );
4781 order_buf = buffer_init( 128 );
4783 OSRF_BUFFER_ADD( order_buf, string );
4787 OSRF_BUFFER_ADD( order_buf, direction );
4791 jsonIteratorFree( order_itr );
4793 } else if( snode->type == JSON_ARRAY ) {
4795 // Array is a list of fields from the current class
4796 unsigned long order_idx = 0;
4797 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4799 const char* _f = jsonObjectGetString( onode );
4801 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4803 osrfLogError( OSRF_LOG_MARK,
4804 "%s: Invalid field \"%s\" in ORDER BY clause",
4807 osrfAppSessionStatus(
4809 OSRF_STATUS_INTERNALSERVERERROR,
4810 "osrfMethodException",
4812 "Invalid field in ORDER BY clause -- "
4813 "see error log for more details"
4815 jsonIteratorFree( class_itr );
4816 buffer_free( order_buf );
4818 buffer_free( group_buf );
4819 buffer_free( sql_buf );
4820 if( defaultselhash )
4821 jsonObjectFree( defaultselhash );
4823 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4824 osrfLogError( OSRF_LOG_MARK,
4825 "%s: Virtual field \"%s\" in ORDER BY clause",
4828 osrfAppSessionStatus(
4830 OSRF_STATUS_INTERNALSERVERERROR,
4831 "osrfMethodException",
4833 "Virtual field in ORDER BY clause -- "
4834 "see error log for more details"
4836 jsonIteratorFree( class_itr );
4837 buffer_free( order_buf );
4839 buffer_free( group_buf );
4840 buffer_free( sql_buf );
4841 if( defaultselhash )
4842 jsonObjectFree( defaultselhash );
4847 OSRF_BUFFER_ADD( order_buf, ", " );
4849 order_buf = buffer_init( 128 );
4851 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4855 // IT'S THE OOOOOOOOOOOLD STYLE!
4857 osrfLogError( OSRF_LOG_MARK,
4858 "%s: Possible SQL injection attempt; direct order by is not allowed",
4861 osrfAppSessionStatus(
4863 OSRF_STATUS_INTERNALSERVERERROR,
4864 "osrfMethodException",
4866 "Severe query error -- see error log for more details"
4871 buffer_free( group_buf );
4872 buffer_free( order_buf );
4873 buffer_free( sql_buf );
4874 if( defaultselhash )
4875 jsonObjectFree( defaultselhash );
4876 jsonIteratorFree( class_itr );
4880 jsonIteratorFree( class_itr );
4882 order_by_list = buffer_release( order_buf );
4884 osrfLogError( OSRF_LOG_MARK,
4885 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4886 modulename, json_type( order_hash->type ) );
4888 osrfAppSessionStatus(
4890 OSRF_STATUS_INTERNALSERVERERROR,
4891 "osrfMethodException",
4893 "Malformed ORDER BY clause -- see error log for more details"
4896 buffer_free( group_buf );
4897 buffer_free( sql_buf );
4898 if( defaultselhash )
4899 jsonObjectFree( defaultselhash );
4904 string = buffer_release( group_buf );
4906 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4907 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4908 OSRF_BUFFER_ADD( sql_buf, string );
4913 if( having_buf && *having_buf ) {
4914 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4915 OSRF_BUFFER_ADD( sql_buf, having_buf );
4919 if( order_by_list ) {
4921 if( *order_by_list ) {
4922 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4923 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4926 free( order_by_list );
4930 const char* str = jsonObjectGetString( limit );
4931 if (str) { // limit could be JSON_NULL, etc.
4932 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4937 const char* str = jsonObjectGetString( offset );
4939 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4943 if( !(flags & SUBSELECT) )
4944 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4946 if( defaultselhash )
4947 jsonObjectFree( defaultselhash );
4949 return buffer_release( sql_buf );
4951 } // end of SELECT()
4954 @brief Build a list of ORDER BY expressions.
4955 @param ctx Pointer to the method context.
4956 @param order_array Pointer to a JSON_ARRAY of field specifications.
4957 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
4958 Each expression may be either a column reference or a function call whose first parameter
4959 is a column reference.
4961 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
4962 It may optionally include entries for "direction" and/or "transform".
4964 The calling code is responsible for freeing the returned string.
4966 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
4967 if( ! order_array ) {
4968 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
4971 osrfAppSessionStatus(
4973 OSRF_STATUS_INTERNALSERVERERROR,
4974 "osrfMethodException",
4976 "Logic error: ORDER BY clause expected, not found; "
4977 "see error log for more details"
4980 } else if( order_array->type != JSON_ARRAY ) {
4981 osrfLogError( OSRF_LOG_MARK,
4982 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
4984 osrfAppSessionStatus(
4986 OSRF_STATUS_INTERNALSERVERERROR,
4987 "osrfMethodException",
4989 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
4993 growing_buffer* order_buf = buffer_init( 128 );
4994 int first = 1; // boolean
4996 jsonObject* order_spec;
4997 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
4999 if( JSON_HASH != order_spec->type ) {
5000 osrfLogError( OSRF_LOG_MARK,
5001 "%s: Malformed field specification in ORDER BY clause; "
5002 "expected JSON_HASH, found %s",
5003 modulename, json_type( order_spec->type ) );
5005 osrfAppSessionStatus(
5007 OSRF_STATUS_INTERNALSERVERERROR,
5008 "osrfMethodException",
5010 "Malformed ORDER BY clause -- see error log for more details"
5012 buffer_free( order_buf );
5016 const char* class_alias =
5017 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5019 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5021 jsonObject* compare_to = jsonObjectGetKeyConst( order_spec, "compare" );
5023 if( !field || !class_alias ) {
5024 osrfLogError( OSRF_LOG_MARK,
5025 "%s: Missing class or field name in field specification of ORDER BY clause",
5028 osrfAppSessionStatus(
5030 OSRF_STATUS_INTERNALSERVERERROR,
5031 "osrfMethodException",
5033 "Malformed ORDER BY clause -- see error log for more details"
5035 buffer_free( order_buf );
5039 const ClassInfo* order_class_info = search_alias( class_alias );
5040 if( ! order_class_info ) {
5041 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5042 "not in FROM clause, skipping it", modulename, class_alias );
5046 // Add a separating comma, except at the beginning
5050 OSRF_BUFFER_ADD( order_buf, ", " );
5052 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5054 osrfLogError( OSRF_LOG_MARK,
5055 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5056 modulename, class_alias, field );
5058 osrfAppSessionStatus(
5060 OSRF_STATUS_INTERNALSERVERERROR,
5061 "osrfMethodException",
5063 "Invalid field referenced in ORDER BY clause -- "
5064 "see error log for more details"
5068 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5069 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5070 modulename, field );
5072 osrfAppSessionStatus(
5074 OSRF_STATUS_INTERNALSERVERERROR,
5075 "osrfMethodException",
5077 "Virtual field in ORDER BY clause -- see error log for more details"
5079 buffer_free( order_buf );
5083 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5084 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5085 if( ! transform_str ) {
5087 osrfAppSessionStatus(
5089 OSRF_STATUS_INTERNALSERVERERROR,
5090 "osrfMethodException",
5092 "Severe query error in ORDER BY clause -- "
5093 "see error log for more details"
5095 buffer_free( order_buf );
5099 OSRF_BUFFER_ADD( order_buf, transform_str );
5100 free( transform_str );
5101 } else if( compare_to ) {
5102 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5103 if( ! compare_str ) {
5105 osrfAppSessionStatus(
5107 OSRF_STATUS_INTERNALSERVERERROR,
5108 "osrfMethodException",
5110 "Severe query error in ORDER BY clause -- "
5111 "see error log for more details"
5113 buffer_free( order_buf );
5117 buffer_fadd( order_buf, "(%s)", compare_str );
5118 free( compare_str );
5121 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5123 const char* direction =
5124 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5126 if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5127 OSRF_BUFFER_ADD( order_buf, " DESC" );
5129 OSRF_BUFFER_ADD( order_buf, " ASC" );
5133 return buffer_release( order_buf );
5137 @brief Build a SELECT statement.
5138 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5139 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5140 @param meta Pointer to the class metadata for the core class.
5141 @param ctx Pointer to the method context.
5142 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5144 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5145 "order_by", "limit", and "offset".
5147 The SELECT statements built here are distinct from those built for the json_query method.
5149 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5150 osrfHash* meta, osrfMethodContext* ctx ) {
5152 const char* locale = osrf_message_get_last_locale();
5154 osrfHash* fields = osrfHashGet( meta, "fields" );
5155 const char* core_class = osrfHashGet( meta, "classname" );
5157 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5159 jsonObject* selhash = NULL;
5160 jsonObject* defaultselhash = NULL;
5162 growing_buffer* sql_buf = buffer_init( 128 );
5163 growing_buffer* select_buf = buffer_init( 128 );
5165 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5166 defaultselhash = jsonNewObjectType( JSON_HASH );
5167 selhash = defaultselhash;
5170 // If there's no SELECT list for the core class, build one
5171 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5172 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5174 // Add every non-virtual field to the field list
5175 osrfHash* field_def = NULL;
5176 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5177 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5178 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5179 const char* field = osrfHashIteratorKey( field_itr );
5180 jsonObjectPush( field_list, jsonNewObject( field ) );
5183 osrfHashIteratorFree( field_itr );
5184 jsonObjectSetKey( selhash, core_class, field_list );
5187 // Build a list of columns for the SELECT clause
5189 const jsonObject* snode = NULL;
5190 jsonIterator* class_itr = jsonNewIterator( selhash );
5191 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5193 // If the class isn't in the IDL, ignore it
5194 const char* cname = class_itr->key;
5195 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5199 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5200 if( strcmp( core_class, class_itr->key )) {
5204 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5205 if( !found->size ) {
5206 jsonObjectFree( found );
5210 jsonObjectFree( found );
5213 const jsonObject* node = NULL;
5214 jsonIterator* select_itr = jsonNewIterator( snode );
5215 while( (node = jsonIteratorNext( select_itr )) ) {
5216 const char* item_str = jsonObjectGetString( node );
5217 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5218 char* fname = osrfHashGet( field, "name" );
5223 if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5229 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5234 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5235 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5238 i18n = osrfHashGet( field, "i18n" );
5240 if( str_is_true( i18n ) ) {
5241 char* pkey = osrfHashGet( idlClass, "primarykey" );
5242 char* tname = osrfHashGet( idlClass, "tablename" );
5244 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5245 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5246 tname, cname, fname, pkey, cname, pkey, locale, fname );
5248 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5251 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5255 jsonIteratorFree( select_itr );
5258 jsonIteratorFree( class_itr );
5260 char* col_list = buffer_release( select_buf );
5261 char* table = oilsGetRelation( meta );
5263 table = strdup( "(null)" );
5265 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5269 // Clear the query stack (as a fail-safe precaution against possible
5270 // leftover garbage); then push the first query frame onto the stack.
5271 clear_query_stack();
5273 if( add_query_core( NULL, core_class ) ) {
5275 osrfAppSessionStatus(
5277 OSRF_STATUS_INTERNALSERVERERROR,
5278 "osrfMethodException",
5280 "Unable to build query frame for core class"
5282 buffer_free( sql_buf );
5283 if( defaultselhash )
5284 jsonObjectFree( defaultselhash );
5288 // Add the JOIN clauses, if any
5290 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5291 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5292 OSRF_BUFFER_ADD( sql_buf, join_clause );
5293 free( join_clause );
5296 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5297 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5299 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5301 // Add the conditions in the WHERE clause
5302 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5304 osrfAppSessionStatus(
5306 OSRF_STATUS_INTERNALSERVERERROR,
5307 "osrfMethodException",
5309 "Severe query error -- see error log for more details"
5311 buffer_free( sql_buf );
5312 if( defaultselhash )
5313 jsonObjectFree( defaultselhash );
5314 clear_query_stack();
5317 buffer_add( sql_buf, pred );
5321 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5322 if( rest_of_query ) {
5323 const jsonObject* order_by = NULL;
5324 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5326 char* order_by_list = NULL;
5328 if( JSON_ARRAY == order_by->type ) {
5329 order_by_list = buildOrderByFromArray( ctx, order_by );
5330 if( !order_by_list ) {
5331 buffer_free( sql_buf );
5332 if( defaultselhash )
5333 jsonObjectFree( defaultselhash );
5334 clear_query_stack();
5337 } else if( JSON_HASH == order_by->type ) {
5338 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5339 // and build a list of ORDER BY expressions.
5340 growing_buffer* order_buf = buffer_init( 128 );
5342 jsonIterator* class_itr = jsonNewIterator( order_by );
5343 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5345 ClassInfo* order_class_info = search_alias( class_itr->key );
5346 if( ! order_class_info )
5347 continue; // class not referenced by FROM clause? Ignore it.
5349 if( JSON_HASH == snode->type ) {
5351 // If the data for the current class is a JSON_HASH, then it is
5352 // keyed on field name.
5354 const jsonObject* onode = NULL;
5355 jsonIterator* order_itr = jsonNewIterator( snode );
5356 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5358 osrfHash* field_def = osrfHashGet(
5359 order_class_info->fields, order_itr->key );
5361 continue; // Field not defined in IDL? Ignore it.
5362 if( str_is_true( osrfHashGet( field_def, "virtual")))
5363 continue; // Field is virtual? Ignore it.
5365 char* field_str = NULL;
5366 char* direction = NULL;
5367 if( onode->type == JSON_HASH ) {
5368 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5369 field_str = searchFieldTransform(
5370 class_itr->key, field_def, onode );
5372 osrfAppSessionStatus(
5374 OSRF_STATUS_INTERNALSERVERERROR,
5375 "osrfMethodException",
5377 "Severe query error in ORDER BY clause -- "
5378 "see error log for more details"
5380 jsonIteratorFree( order_itr );
5381 jsonIteratorFree( class_itr );
5382 buffer_free( order_buf );
5383 buffer_free( sql_buf );
5384 if( defaultselhash )
5385 jsonObjectFree( defaultselhash );
5386 clear_query_stack();
5390 growing_buffer* field_buf = buffer_init( 16 );
5391 buffer_fadd( field_buf, "\"%s\".%s",
5392 class_itr->key, order_itr->key );
5393 field_str = buffer_release( field_buf );
5396 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5397 const char* dir = jsonObjectGetString( order_by );
5398 if(!strncasecmp( dir, "d", 1 )) {
5399 direction = " DESC";
5403 field_str = strdup( order_itr->key );
5404 const char* dir = jsonObjectGetString( onode );
5405 if( !strncasecmp( dir, "d", 1 )) {
5406 direction = " DESC";
5415 buffer_add( order_buf, ", " );
5418 buffer_add( order_buf, field_str );
5422 buffer_add( order_buf, direction );
5424 } // end while; looping over ORDER BY expressions
5426 jsonIteratorFree( order_itr );
5428 } else if( JSON_STRING == snode->type ) {
5429 // We expect a comma-separated list of sort fields.
5430 const char* str = jsonObjectGetString( snode );
5431 if( strchr( str, ';' )) {
5432 // No semicolons allowed. It is theoretically possible for a
5433 // legitimate semicolon to occur within quotes, but it's not likely
5434 // to occur in practice in the context of an ORDER BY list.
5435 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5436 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5438 osrfAppSessionStatus(
5440 OSRF_STATUS_INTERNALSERVERERROR,
5441 "osrfMethodException",
5443 "Possible attempt at SOL injection -- "
5444 "semicolon found in ORDER BY list"
5447 jsonIteratorFree( class_itr );
5448 buffer_free( order_buf );
5449 buffer_free( sql_buf );
5450 if( defaultselhash )
5451 jsonObjectFree( defaultselhash );
5452 clear_query_stack();
5455 buffer_add( order_buf, str );
5459 } // end while; looping over order_by classes
5461 jsonIteratorFree( class_itr );
5462 order_by_list = buffer_release( order_buf );
5465 osrfLogWarning( OSRF_LOG_MARK,
5466 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5467 "no ORDER BY generated" );
5470 if( order_by_list && *order_by_list ) {
5471 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5472 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5475 free( order_by_list );
5478 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5480 const char* str = jsonObjectGetString( limit );
5490 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5492 const char* str = jsonObjectGetString( offset );
5503 if( defaultselhash )
5504 jsonObjectFree( defaultselhash );
5505 clear_query_stack();
5507 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5508 return buffer_release( sql_buf );
5511 int doJSONSearch ( osrfMethodContext* ctx ) {
5512 if(osrfMethodVerifyContext( ctx )) {
5513 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5517 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5521 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5525 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5526 flags |= SELECT_DISTINCT;
5528 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5529 flags |= DISABLE_I18N;
5531 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5532 clear_query_stack(); // a possibly needless precaution
5533 char* sql = buildQuery( ctx, hash, flags );
5534 clear_query_stack();
5541 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5544 dbhandle = writehandle;
5546 dbi_result result = dbi_conn_query( dbhandle, sql );
5549 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5551 if( dbi_result_first_row( result )) {
5552 /* JSONify the result */
5553 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5556 jsonObject* return_val = oilsMakeJSONFromResult( result );
5557 osrfAppRespond( ctx, return_val );
5558 jsonObjectFree( return_val );
5559 } while( dbi_result_next_row( result ));
5562 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5565 osrfAppRespondComplete( ctx, NULL );
5567 /* clean up the query */
5568 dbi_result_free( result );
5573 int errnum = dbi_conn_error( dbhandle, &msg );
5574 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5575 modulename, sql, errnum, msg ? msg : "(No description available)" );
5576 osrfAppSessionStatus(
5578 OSRF_STATUS_INTERNALSERVERERROR,
5579 "osrfMethodException",
5581 "Severe query error -- see error log for more details"
5583 if( !oilsIsDBConnected( dbhandle ))
5584 osrfAppSessionPanic( ctx->session );
5591 // The last parameter, err, is used to report an error condition by updating an int owned by
5592 // the calling code.
5594 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5595 // It is the responsibility of the calling code to initialize *err before the
5596 // call, so that it will be able to make sense of the result.
5598 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5599 // redundant anyway.
5600 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5601 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5604 dbhandle = writehandle;
5606 char* core_class = osrfHashGet( class_meta, "classname" );
5607 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5609 char* pkey = osrfHashGet( class_meta, "primarykey" );
5611 if (!ctx->session->userData)
5612 (void) initSessionCache( ctx );
5614 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5615 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5616 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5618 int i_respond_directly = 0;
5619 int flesh_depth = 0;
5621 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5623 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5628 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5630 dbi_result result = dbi_conn_query( dbhandle, sql );
5631 if( NULL == result ) {
5633 int errnum = dbi_conn_error( dbhandle, &msg );
5634 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5635 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5636 msg ? msg : "(No description available)" );
5637 if( !oilsIsDBConnected( dbhandle ))
5638 osrfAppSessionPanic( ctx->session );
5639 osrfAppSessionStatus(
5641 OSRF_STATUS_INTERNALSERVERERROR,
5642 "osrfMethodException",
5644 "Severe query error -- see error log for more details"
5651 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5654 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5655 jsonObject* row_obj = NULL;
5657 // The following two steps are for verifyObjectPCRUD()'s benefit.
5658 // 1. get the flesh depth
5659 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5661 flesh_depth = (int) jsonObjectGetNumber( _tmp );
5662 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5663 flesh_depth = max_flesh_depth;
5666 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
5667 // over the whole life of this request. This means if we've already set
5668 // up a rs_size_req_%d, do nothing.
5669 // a. Incidentally, we can also use this opportunity to set i_respond_directly
5670 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
5671 if( !rs_size ) { // pointer null, so value not set in hash
5672 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
5673 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
5675 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
5676 unsigned long long result_count = dbi_result_get_numrows( result );
5677 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
5678 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
5681 if( dbi_result_first_row( result )) {
5683 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5684 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5685 // eliminate the duplicates.
5686 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5687 osrfHash* dedup = osrfNewHash();
5689 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5690 char* pkey_val = oilsFMGetString( row_obj, pkey );
5691 if( osrfHashGet( dedup, pkey_val ) ) {
5692 jsonObjectFree( row_obj );
5695 if( !enforce_pcrud || !need_to_verify ||
5696 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
5697 osrfHashSet( dedup, pkey_val, pkey_val );
5698 jsonObjectPush( res_list, row_obj );
5701 } while( dbi_result_next_row( result ));
5702 osrfHashFree( dedup );
5705 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5709 /* clean up the query */
5710 dbi_result_free( result );
5713 // If we're asked to flesh, and there's anything to flesh, then flesh it
5714 // (formerly we would skip fleshing if in pcrud mode, but now we support
5715 // fleshing even in PCRUD).
5716 if( res_list->size ) {
5717 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
5718 jsonObject* flesh_fields;
5719 jsonObject* flesh_blob = NULL;
5720 osrfStringArray* link_fields = NULL;
5721 osrfHash* links = NULL;
5725 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
5726 if( temp_blob && flesh_depth > 0 ) {
5728 flesh_blob = jsonObjectClone( temp_blob );
5729 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
5731 links = osrfHashGet( class_meta, "links" );
5733 // Make an osrfStringArray of the names of fields to be fleshed
5734 if( flesh_fields ) {
5735 if( flesh_fields->size == 1 ) {
5736 const char* _t = jsonObjectGetString(
5737 jsonObjectGetIndex( flesh_fields, 0 ) );
5738 if( !strcmp( _t, "*" ))
5739 link_fields = osrfHashKeys( links );
5742 if( !link_fields ) {
5744 link_fields = osrfNewStringArray( 1 );
5745 jsonIterator* _i = jsonNewIterator( flesh_fields );
5746 while ((_f = jsonIteratorNext( _i ))) {
5747 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5749 jsonIteratorFree( _i );
5752 want_flesh = link_fields ? 1 : 0;
5756 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5758 // Iterate over the JSON_ARRAY of rows
5760 unsigned long res_idx = 0;
5761 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5764 const char* link_field;
5766 // Iterate over the list of fleshable fields
5768 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5770 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5772 osrfHash* kid_link = osrfHashGet( links, link_field );
5774 continue; // Not a link field; skip it
5776 osrfHash* field = osrfHashGet( fields, link_field );
5778 continue; // Not a field at all; skip it (IDL is ill-formed)
5780 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5781 osrfHashGet( kid_link, "class" ));
5783 continue; // The class it links to doesn't exist; skip it
5785 const char* reltype = osrfHashGet( kid_link, "reltype" );
5787 continue; // No reltype; skip it (IDL is ill-formed)
5789 osrfHash* value_field = field;
5791 if( !strcmp( reltype, "has_many" )
5792 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5793 value_field = osrfHashGet(
5794 fields, osrfHashGet( class_meta, "primarykey" ) );
5797 int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
5798 // fleshing pcrud case: we require the controller in need_to_verify mode
5799 if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
5800 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
5804 (unsigned long) atoi( osrfHashGet(field, "array_position") ),
5806 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
5812 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5814 if( link_map->size > 0 ) {
5815 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5818 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5823 osrfHashGet( kid_link, "class" ),
5830 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5831 osrfHashGet( kid_link, "field" ),
5832 osrfHashGet( kid_link, "class" ),
5833 osrfHashGet( kid_link, "key" ),
5834 osrfHashGet( kid_link, "reltype" )
5837 const char* search_key = jsonObjectGetString(
5838 jsonObjectGetIndex( cur,
5839 atoi( osrfHashGet( value_field, "array_position" ) )
5844 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5848 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5850 // construct WHERE clause
5851 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5854 osrfHashGet( kid_link, "key" ),
5855 jsonNewObject( search_key )
5858 // construct the rest of the query, mostly
5859 // by copying pieces of the previous level of query
5860 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5861 jsonObjectSetKey( rest_of_query, "flesh",
5862 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5866 jsonObjectSetKey( rest_of_query, "flesh_fields",
5867 jsonObjectClone( flesh_blob ));
5869 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5870 jsonObjectSetKey( rest_of_query, "order_by",
5871 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5875 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5876 jsonObjectSetKey( rest_of_query, "select",
5877 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5881 // do the query, recursively, to expand the fleshable field
5882 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5883 where_clause, rest_of_query, err );
5885 jsonObjectFree( where_clause );
5886 jsonObjectFree( rest_of_query );
5889 osrfStringArrayFree( link_fields );
5890 jsonObjectFree( res_list );
5891 jsonObjectFree( flesh_blob );
5895 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5896 osrfHashGet( kid_link, "class" ), kids->size );
5898 // Traverse the result set
5899 jsonObject* X = NULL;
5900 if( link_map->size > 0 && kids->size > 0 ) {
5902 kids = jsonNewObjectType( JSON_ARRAY );
5904 jsonObject* _k_node;
5905 unsigned long res_idx = 0;
5906 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5912 (unsigned long) atoi(
5918 osrfHashGet( kid_link, "class" )
5922 osrfStringArrayGetString( link_map, 0 )
5930 } // end while loop traversing X
5933 if (kids->size > 0) {
5935 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5936 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
5938 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5939 osrfHashGet( kid_link, "field" ));
5942 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5943 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5948 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5950 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5951 osrfHashGet( kid_link, "field" ) );
5954 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5955 jsonObjectClone( kids )
5960 jsonObjectFree( kids );
5964 jsonObjectFree( kids );
5966 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5967 osrfHashGet( kid_link, "field" ) );
5968 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5970 } // end while loop traversing list of fleshable fields
5973 if( i_respond_directly ) {
5974 if ( *methodtype == 'i' ) {
5975 osrfAppRespond( ctx,
5976 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
5978 osrfAppRespond( ctx, cur );
5981 } // end while loop traversing res_list
5982 jsonObjectFree( flesh_blob );
5983 osrfStringArrayFree( link_fields );
5986 if( i_respond_directly ) {
5987 jsonObjectFree( res_list );
5988 return jsonNewObjectType( JSON_ARRAY );
5995 int doUpdate( osrfMethodContext* ctx ) {
5996 if( osrfMethodVerifyContext( ctx )) {
5997 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6002 timeout_needs_resetting = 1;
6004 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6006 jsonObject* target = NULL;
6008 target = jsonObjectGetIndex( ctx->params, 1 );
6010 target = jsonObjectGetIndex( ctx->params, 0 );
6012 if(!verifyObjectClass( ctx, target )) {
6013 osrfAppRespondComplete( ctx, NULL );
6017 if( getXactId( ctx ) == NULL ) {
6018 osrfAppSessionStatus(
6020 OSRF_STATUS_BADREQUEST,
6021 "osrfMethodException",
6023 "No active transaction -- required for UPDATE"
6025 osrfAppRespondComplete( ctx, NULL );
6029 // The following test is harmless but redundant. If a class is
6030 // readonly, we don't register an update method for it.
6031 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6032 osrfAppSessionStatus(
6034 OSRF_STATUS_BADREQUEST,
6035 "osrfMethodException",
6037 "Cannot UPDATE readonly class"
6039 osrfAppRespondComplete( ctx, NULL );
6043 const char* trans_id = getXactId( ctx );
6045 // Set the last_xact_id
6046 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6048 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6049 trans_id, target->classname, index );
6050 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6053 char* pkey = osrfHashGet( meta, "primarykey" );
6054 osrfHash* fields = osrfHashGet( meta, "fields" );
6056 char* id = oilsFMGetString( target, pkey );
6060 "%s updating %s object with %s = %s",
6062 osrfHashGet( meta, "fieldmapper" ),
6067 dbhandle = writehandle;
6068 growing_buffer* sql = buffer_init( 128 );
6069 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6072 osrfHash* field_def = NULL;
6073 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6074 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6076 // Skip virtual fields, and the primary key
6077 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6080 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6084 const char* field_name = osrfHashIteratorKey( field_itr );
6085 if( ! strcmp( field_name, pkey ) )
6088 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6090 int value_is_numeric = 0; // boolean
6092 if( field_object && field_object->classname ) {
6093 value = oilsFMGetString(
6095 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6097 } else if( field_object && JSON_BOOL == field_object->type ) {
6098 if( jsonBoolIsTrue( field_object ) )
6099 value = strdup( "t" );
6101 value = strdup( "f" );
6103 value = jsonObjectToSimpleString( field_object );
6104 if( field_object && JSON_NUMBER == field_object->type )
6105 value_is_numeric = 1;
6108 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6109 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6111 if( !field_object || field_object->type == JSON_NULL ) {
6112 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6113 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6117 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6118 buffer_fadd( sql, " %s = NULL", field_name );
6121 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6125 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6127 const char* numtype = get_datatype( field_def );
6128 if( !strncmp( numtype, "INT", 3 ) ) {
6129 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6130 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6131 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6133 // Must really be intended as a string, so quote it
6134 if( dbi_conn_quote_string( dbhandle, &value )) {
6135 buffer_fadd( sql, " %s = %s", field_name, value );
6137 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6138 modulename, value );
6139 osrfAppSessionStatus(
6141 OSRF_STATUS_INTERNALSERVERERROR,
6142 "osrfMethodException",
6144 "Error quoting string -- please see the error log for more details"
6148 osrfHashIteratorFree( field_itr );
6150 osrfAppRespondComplete( ctx, NULL );
6155 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6158 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6162 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6163 buffer_fadd( sql, " %s = %s", field_name, value );
6165 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6166 osrfAppSessionStatus(
6168 OSRF_STATUS_INTERNALSERVERERROR,
6169 "osrfMethodException",
6171 "Error quoting string -- please see the error log for more details"
6175 osrfHashIteratorFree( field_itr );
6177 osrfAppRespondComplete( ctx, NULL );
6186 osrfHashIteratorFree( field_itr );
6188 jsonObject* obj = jsonNewObject( id );
6190 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6191 dbi_conn_quote_string( dbhandle, &id );
6193 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6195 char* query = buffer_release( sql );
6196 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6198 dbi_result result = dbi_conn_query( dbhandle, query );
6203 jsonObjectFree( obj );
6204 obj = jsonNewObject( NULL );
6206 int errnum = dbi_conn_error( dbhandle, &msg );
6209 "%s ERROR updating %s object with %s = %s: %d %s",
6211 osrfHashGet( meta, "fieldmapper" ),
6215 msg ? msg : "(No description available)"
6217 osrfAppSessionStatus(
6219 OSRF_STATUS_INTERNALSERVERERROR,
6220 "osrfMethodException",
6222 "Error in updating a row -- please see the error log for more details"
6224 if( !oilsIsDBConnected( dbhandle ))
6225 osrfAppSessionPanic( ctx->session );
6228 dbi_result_free( result );
6231 osrfAppRespondComplete( ctx, obj );
6232 jsonObjectFree( obj );
6236 int doDelete( osrfMethodContext* ctx ) {
6237 if( osrfMethodVerifyContext( ctx )) {
6238 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6243 timeout_needs_resetting = 1;
6245 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6247 if( getXactId( ctx ) == NULL ) {
6248 osrfAppSessionStatus(
6250 OSRF_STATUS_BADREQUEST,
6251 "osrfMethodException",
6253 "No active transaction -- required for DELETE"
6255 osrfAppRespondComplete( ctx, NULL );
6259 // The following test is harmless but redundant. If a class is
6260 // readonly, we don't register a delete method for it.
6261 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6262 osrfAppSessionStatus(
6264 OSRF_STATUS_BADREQUEST,
6265 "osrfMethodException",
6267 "Cannot DELETE readonly class"
6269 osrfAppRespondComplete( ctx, NULL );
6273 dbhandle = writehandle;
6275 char* pkey = osrfHashGet( meta, "primarykey" );
6282 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6283 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6284 osrfAppRespondComplete( ctx, NULL );
6288 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6290 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6291 osrfAppRespondComplete( ctx, NULL );
6294 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6299 "%s deleting %s object with %s = %s",
6301 osrfHashGet( meta, "fieldmapper" ),
6306 jsonObject* obj = jsonNewObject( id );
6308 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6309 dbi_conn_quote_string( writehandle, &id );
6311 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6312 osrfHashGet( meta, "tablename" ), pkey, id );
6317 jsonObjectFree( obj );
6318 obj = jsonNewObject( NULL );
6320 int errnum = dbi_conn_error( writehandle, &msg );
6323 "%s ERROR deleting %s object with %s = %s: %d %s",
6325 osrfHashGet( meta, "fieldmapper" ),
6329 msg ? msg : "(No description available)"
6331 osrfAppSessionStatus(
6333 OSRF_STATUS_INTERNALSERVERERROR,
6334 "osrfMethodException",
6336 "Error in deleting a row -- please see the error log for more details"
6338 if( !oilsIsDBConnected( writehandle ))
6339 osrfAppSessionPanic( ctx->session );
6341 dbi_result_free( result );
6345 osrfAppRespondComplete( ctx, obj );
6346 jsonObjectFree( obj );
6351 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6352 @param result An iterator for a result set; we only look at the current row.
6353 @param @meta Pointer to the class metadata for the core class.
6354 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6356 If a column is not defined in the IDL, or if it has no array_position defined for it in
6357 the IDL, or if it is defined as virtual, ignore it.
6359 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6360 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6361 array_position in the IDL.
6363 A field defined in the IDL but not represented in the returned row will leave a hole
6364 in the JSON_ARRAY. In effect it will be treated as a null value.
6366 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6367 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6368 classname corresponding to the @a meta argument.
6370 The calling code is responsible for freeing the the resulting jsonObject by calling
6373 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6374 if( !( result && meta )) return NULL;
6376 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6377 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6378 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6380 osrfHash* fields = osrfHashGet( meta, "fields" );
6382 int columnIndex = 1;
6383 const char* columnName;
6385 /* cycle through the columns in the row returned from the database */
6386 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6388 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6390 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6392 /* determine the field type and storage attributes */
6393 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6394 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6396 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6397 // or if it has no sequence number there, or if it's virtual, skip it.
6398 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6401 if( str_is_true( osrfHashGet( _f, "virtual" )))
6402 continue; // skip this column: IDL says it's virtual
6404 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6405 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6406 continue; // since we assign sequence numbers dynamically as we load the IDL.
6408 fmIndex = atoi( pos );
6409 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6411 continue; // This field is not defined in the IDL
6414 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6415 // sequence number from the IDL (which is likely to be different from the sequence
6416 // of columns in the SELECT clause).
6417 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6418 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6423 case DBI_TYPE_INTEGER :
6425 if( attr & DBI_INTEGER_SIZE8 )
6426 jsonObjectSetIndex( object, fmIndex,
6427 jsonNewNumberObject(
6428 dbi_result_get_longlong_idx( result, columnIndex )));
6430 jsonObjectSetIndex( object, fmIndex,
6431 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6435 case DBI_TYPE_DECIMAL :
6436 jsonObjectSetIndex( object, fmIndex,
6437 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6440 case DBI_TYPE_STRING :
6445 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6450 case DBI_TYPE_DATETIME : {
6452 char dt_string[ 256 ] = "";
6455 // Fetch the date column as a time_t
6456 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6458 // Translate the time_t to a human-readable string
6459 if( !( attr & DBI_DATETIME_DATE )) {
6460 gmtime_r( &_tmp_dt, &gmdt );
6461 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6462 } else if( !( attr & DBI_DATETIME_TIME )) {
6463 localtime_r( &_tmp_dt, &gmdt );
6464 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6466 localtime_r( &_tmp_dt, &gmdt );
6467 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6470 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6474 case DBI_TYPE_BINARY :
6475 osrfLogError( OSRF_LOG_MARK,
6476 "Can't do binary at column %s : index %d", columnName, columnIndex );
6485 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6486 if( !result ) return NULL;
6488 jsonObject* object = jsonNewObject( NULL );
6491 char dt_string[ 256 ];
6495 int columnIndex = 1;
6497 unsigned short type;
6498 const char* columnName;
6500 /* cycle through the column list */
6501 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6503 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6505 fmIndex = -1; // reset the position
6507 /* determine the field type and storage attributes */
6508 type = dbi_result_get_field_type_idx( result, columnIndex );
6509 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6511 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6512 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6517 case DBI_TYPE_INTEGER :
6519 if( attr & DBI_INTEGER_SIZE8 )
6520 jsonObjectSetKey( object, columnName,
6521 jsonNewNumberObject( dbi_result_get_longlong_idx(
6522 result, columnIndex )) );
6524 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6525 dbi_result_get_int_idx( result, columnIndex )) );
6528 case DBI_TYPE_DECIMAL :
6529 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6530 dbi_result_get_double_idx( result, columnIndex )) );
6533 case DBI_TYPE_STRING :
6534 jsonObjectSetKey( object, columnName,
6535 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6538 case DBI_TYPE_DATETIME :
6540 memset( dt_string, '\0', sizeof( dt_string ));
6541 memset( &gmdt, '\0', sizeof( gmdt ));
6543 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6545 if( !( attr & DBI_DATETIME_DATE )) {
6546 gmtime_r( &_tmp_dt, &gmdt );
6547 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6548 } else if( !( attr & DBI_DATETIME_TIME )) {
6549 localtime_r( &_tmp_dt, &gmdt );
6550 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6552 localtime_r( &_tmp_dt, &gmdt );
6553 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6556 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6559 case DBI_TYPE_BINARY :
6560 osrfLogError( OSRF_LOG_MARK,
6561 "Can't do binary at column %s : index %d", columnName, columnIndex );
6565 } // end while loop traversing result
6570 // Interpret a string as true or false
6571 int str_is_true( const char* str ) {
6572 if( NULL == str || strcasecmp( str, "true" ) )
6578 // Interpret a jsonObject as true or false
6579 static int obj_is_true( const jsonObject* obj ) {
6582 else switch( obj->type )
6590 if( strcasecmp( obj->value.s, "true" ) )
6594 case JSON_NUMBER : // Support 1/0 for perl's sake
6595 if( jsonObjectGetNumber( obj ) == 1.0 )
6604 // Translate a numeric code into a text string identifying a type of
6605 // jsonObject. To be used for building error messages.
6606 static const char* json_type( int code ) {
6612 return "JSON_ARRAY";
6614 return "JSON_STRING";
6616 return "JSON_NUMBER";
6622 return "(unrecognized)";
6626 // Extract the "primitive" attribute from an IDL field definition.
6627 // If we haven't initialized the app, then we must be running in
6628 // some kind of testbed. In that case, default to "string".
6629 static const char* get_primitive( osrfHash* field ) {
6630 const char* s = osrfHashGet( field, "primitive" );
6632 if( child_initialized )
6635 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6637 osrfHashGet( field, "name" )
6645 // Extract the "datatype" attribute from an IDL field definition.
6646 // If we haven't initialized the app, then we must be running in
6647 // some kind of testbed. In that case, default to to NUMERIC,
6648 // since we look at the datatype only for numbers.
6649 static const char* get_datatype( osrfHash* field ) {
6650 const char* s = osrfHashGet( field, "datatype" );
6652 if( child_initialized )
6655 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6657 osrfHashGet( field, "name" )
6666 @brief Determine whether a string is potentially a valid SQL identifier.
6667 @param s The identifier to be tested.
6668 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6670 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6671 need to follow all the rules exactly, such as requiring that the first character not
6674 We allow leading and trailing white space. In between, we do not allow punctuation
6675 (except for underscores and dollar signs), control characters, or embedded white space.
6677 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6678 for the foreseeable future such quoted identifiers are not likely to be an issue.
6680 int is_identifier( const char* s) {
6684 // Skip leading white space
6685 while( isspace( (unsigned char) *s ) )
6689 return 0; // Nothing but white space? Not okay.
6691 // Check each character until we reach white space or
6692 // end-of-string. Letters, digits, underscores, and
6693 // dollar signs are okay. With the exception of periods
6694 // (as in schema.identifier), control characters and other
6695 // punctuation characters are not okay. Anything else
6696 // is okay -- it could for example be part of a multibyte
6697 // UTF8 character such as a letter with diacritical marks,
6698 // and those are allowed.
6700 if( isalnum( (unsigned char) *s )
6704 ; // Fine; keep going
6705 else if( ispunct( (unsigned char) *s )
6706 || iscntrl( (unsigned char) *s ) )
6709 } while( *s && ! isspace( (unsigned char) *s ) );
6711 // If we found any white space in the above loop,
6712 // the rest had better be all white space.
6714 while( isspace( (unsigned char) *s ) )
6718 return 0; // White space was embedded within non-white space
6724 @brief Determine whether to accept a character string as a comparison operator.
6725 @param op The candidate comparison operator.
6726 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6728 We don't validate the operator for real. We just make sure that it doesn't contain
6729 any semicolons or white space (with special exceptions for a few specific operators).
6730 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6731 space but it's still not a valid operator, then the database will complain.
6733 Another approach would be to compare the string against a short list of approved operators.
6734 We don't do that because we want to allow custom operators like ">100*", which at this
6735 writing would be difficult or impossible to express otherwise in a JSON query.
6737 int is_good_operator( const char* op ) {
6738 if( !op ) return 0; // Sanity check
6742 if( isspace( (unsigned char) *s ) ) {
6743 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6744 // and IS NOT DISTINCT FROM.
6745 if( !strcasecmp( op, "similar to" ) )
6747 else if( !strcasecmp( op, "is distinct from" ) )
6749 else if( !strcasecmp( op, "is not distinct from" ) )
6754 else if( ';' == *s )
6762 @name Query Frame Management
6764 The following machinery supports a stack of query frames for use by SELECT().
6766 A query frame caches information about one level of a SELECT query. When we enter
6767 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6769 The query frame stores information about the core class, and about any joined classes
6772 The main purpose is to map table aliases to classes and tables, so that a query can
6773 join to the same table more than once. A secondary goal is to reduce the number of
6774 lookups in the IDL by caching the results.
6778 #define STATIC_CLASS_INFO_COUNT 3
6780 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6783 @brief Allocate a ClassInfo as raw memory.
6784 @return Pointer to the newly allocated ClassInfo.
6786 Except for the in_use flag, which is used only by the allocation and deallocation
6787 logic, we don't initialize the ClassInfo here.
6789 static ClassInfo* allocate_class_info( void ) {
6790 // In order to reduce the number of mallocs and frees, we return a static
6791 // instance of ClassInfo, if we can find one that we're not already using.
6792 // We rely on the fact that the compiler will implicitly initialize the
6793 // static instances so that in_use == 0.
6796 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6797 if( ! static_class_info[ i ].in_use ) {
6798 static_class_info[ i ].in_use = 1;
6799 return static_class_info + i;
6803 // The static ones are all in use. Malloc one.
6805 return safe_malloc( sizeof( ClassInfo ) );
6809 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6810 @param info Pointer to the ClassInfo to be cleared.
6812 static void clear_class_info( ClassInfo* info ) {
6817 // Free any malloc'd strings
6819 if( info->alias != info->alias_store )
6820 free( info->alias );
6822 if( info->class_name != info->class_name_store )
6823 free( info->class_name );
6825 free( info->source_def );
6827 info->alias = info->class_name = info->source_def = NULL;
6832 @brief Free a ClassInfo and everything it owns.
6833 @param info Pointer to the ClassInfo to be freed.
6835 static void free_class_info( ClassInfo* info ) {
6840 clear_class_info( info );
6842 // If it's one of the static instances, just mark it as not in use
6845 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6846 if( info == static_class_info + i ) {
6847 static_class_info[ i ].in_use = 0;
6852 // Otherwise it must have been malloc'd, so free it
6858 @brief Populate an already-allocated ClassInfo.
6859 @param info Pointer to the ClassInfo to be populated.
6860 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6862 @param class Name of the class.
6863 @return Zero if successful, or 1 if not.
6865 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6866 the relevant portions of the IDL for the specified class.
6868 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6871 osrfLogError( OSRF_LOG_MARK,
6872 "%s ERROR: No ClassInfo available to populate", modulename );
6873 info->alias = info->class_name = info->source_def = NULL;
6874 info->class_def = info->fields = info->links = NULL;
6879 osrfLogError( OSRF_LOG_MARK,
6880 "%s ERROR: No class name provided for lookup", modulename );
6881 info->alias = info->class_name = info->source_def = NULL;
6882 info->class_def = info->fields = info->links = NULL;
6886 // Alias defaults to class name if not supplied
6887 if( ! alias || ! alias[ 0 ] )
6890 // Look up class info in the IDL
6891 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6893 osrfLogError( OSRF_LOG_MARK,
6894 "%s ERROR: Class %s not defined in IDL", modulename, class );
6895 info->alias = info->class_name = info->source_def = NULL;
6896 info->class_def = info->fields = info->links = NULL;
6898 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6899 osrfLogError( OSRF_LOG_MARK,
6900 "%s ERROR: Class %s is defined as virtual", modulename, class );
6901 info->alias = info->class_name = info->source_def = NULL;
6902 info->class_def = info->fields = info->links = NULL;
6906 osrfHash* links = osrfHashGet( class_def, "links" );
6908 osrfLogError( OSRF_LOG_MARK,
6909 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6910 info->alias = info->class_name = info->source_def = NULL;
6911 info->class_def = info->fields = info->links = NULL;
6915 osrfHash* fields = osrfHashGet( class_def, "fields" );
6917 osrfLogError( OSRF_LOG_MARK,
6918 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6919 info->alias = info->class_name = info->source_def = NULL;
6920 info->class_def = info->fields = info->links = NULL;
6924 char* source_def = oilsGetRelation( class_def );
6928 // We got everything we need, so populate the ClassInfo
6929 if( strlen( alias ) > ALIAS_STORE_SIZE )
6930 info->alias = strdup( alias );
6932 strcpy( info->alias_store, alias );
6933 info->alias = info->alias_store;
6936 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6937 info->class_name = strdup( class );
6939 strcpy( info->class_name_store, class );
6940 info->class_name = info->class_name_store;
6943 info->source_def = source_def;
6945 info->class_def = class_def;
6946 info->links = links;
6947 info->fields = fields;
6952 #define STATIC_FRAME_COUNT 3
6954 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6957 @brief Allocate a QueryFrame as raw memory.
6958 @return Pointer to the newly allocated QueryFrame.
6960 Except for the in_use flag, which is used only by the allocation and deallocation
6961 logic, we don't initialize the QueryFrame here.
6963 static QueryFrame* allocate_frame( void ) {
6964 // In order to reduce the number of mallocs and frees, we return a static
6965 // instance of QueryFrame, if we can find one that we're not already using.
6966 // We rely on the fact that the compiler will implicitly initialize the
6967 // static instances so that in_use == 0.
6970 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6971 if( ! static_frame[ i ].in_use ) {
6972 static_frame[ i ].in_use = 1;
6973 return static_frame + i;
6977 // The static ones are all in use. Malloc one.
6979 return safe_malloc( sizeof( QueryFrame ) );
6983 @brief Free a QueryFrame, and all the memory it owns.
6984 @param frame Pointer to the QueryFrame to be freed.
6986 static void free_query_frame( QueryFrame* frame ) {
6991 clear_class_info( &frame->core );
6993 // Free the join list
6995 ClassInfo* info = frame->join_list;
6998 free_class_info( info );
7002 frame->join_list = NULL;
7005 // If the frame is a static instance, just mark it as unused
7007 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7008 if( frame == static_frame + i ) {
7009 static_frame[ i ].in_use = 0;
7014 // Otherwise it must have been malloc'd, so free it
7020 @brief Search a given QueryFrame for a specified alias.
7021 @param frame Pointer to the QueryFrame to be searched.
7022 @param target The alias for which to search.
7023 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7025 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7026 if( ! frame || ! target ) {
7030 ClassInfo* found_class = NULL;
7032 if( !strcmp( target, frame->core.alias ) )
7033 return &(frame->core);
7035 ClassInfo* curr_class = frame->join_list;
7036 while( curr_class ) {
7037 if( strcmp( target, curr_class->alias ) )
7038 curr_class = curr_class->next;
7040 found_class = curr_class;
7050 @brief Push a new (blank) QueryFrame onto the stack.
7052 static void push_query_frame( void ) {
7053 QueryFrame* frame = allocate_frame();
7054 frame->join_list = NULL;
7055 frame->next = curr_query;
7057 // Initialize the ClassInfo for the core class
7058 ClassInfo* core = &frame->core;
7059 core->alias = core->class_name = core->source_def = NULL;
7060 core->class_def = core->fields = core->links = NULL;
7066 @brief Pop a QueryFrame off the stack and destroy it.
7068 static void pop_query_frame( void ) {
7073 QueryFrame* popped = curr_query;
7074 curr_query = popped->next;
7076 free_query_frame( popped );
7080 @brief Populate the ClassInfo for the core class.
7081 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7082 class name as an alias.
7083 @param class_name Name of the core class.
7084 @return Zero if successful, or 1 if not.
7086 Populate the ClassInfo of the core class with copies of the alias and class name, and
7087 with pointers to the relevant portions of the IDL for the core class.
7089 static int add_query_core( const char* alias, const char* class_name ) {
7092 if( ! curr_query ) {
7093 osrfLogError( OSRF_LOG_MARK,
7094 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7096 } else if( curr_query->core.alias ) {
7097 osrfLogError( OSRF_LOG_MARK,
7098 "%s ERROR: Core class %s already populated as %s",
7099 modulename, curr_query->core.class_name, curr_query->core.alias );
7103 build_class_info( &curr_query->core, alias, class_name );
7104 if( curr_query->core.alias )
7107 osrfLogError( OSRF_LOG_MARK,
7108 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7114 @brief Search the current QueryFrame for a specified alias.
7115 @param target The alias for which to search.
7116 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7118 static inline ClassInfo* search_alias( const char* target ) {
7119 return search_alias_in_frame( curr_query, target );
7123 @brief Search all levels of query for a specified alias, starting with the current query.
7124 @param target The alias for which to search.
7125 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7127 static ClassInfo* search_all_alias( const char* target ) {
7128 ClassInfo* found_class = NULL;
7129 QueryFrame* curr_frame = curr_query;
7131 while( curr_frame ) {
7132 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7135 curr_frame = curr_frame->next;
7142 @brief Add a class to the list of classes joined to the current query.
7143 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7144 the class name as an alias.
7145 @param classname The name of the class to be added.
7146 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7148 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7150 if( ! classname || ! *classname ) { // sanity check
7151 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7158 const ClassInfo* conflict = search_alias( alias );
7160 osrfLogError( OSRF_LOG_MARK,
7161 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7162 modulename, alias, conflict->class_name );
7166 ClassInfo* info = allocate_class_info();
7168 if( build_class_info( info, alias, classname ) ) {
7169 free_class_info( info );
7173 // Add the new ClassInfo to the join list of the current QueryFrame
7174 info->next = curr_query->join_list;
7175 curr_query->join_list = info;
7181 @brief Destroy all nodes on the query stack.
7183 static void clear_query_stack( void ) {
7189 @brief Implement the set_audit_info method.
7190 @param ctx Pointer to the method context.
7191 @return Zero if successful, or -1 if not.
7193 Issue a SAVEPOINT to the database server.
7198 - workstation id (int)
7200 If user id is not provided the authkey will be used.
7201 For PCRUD the authkey is always used, even if a user is provided.
7203 int setAuditInfo( osrfMethodContext* ctx ) {
7204 if(osrfMethodVerifyContext( ctx )) {
7205 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
7209 // Get the user id from the parameters
7210 const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7212 if( enforce_pcrud || !user_id ) {
7213 timeout_needs_resetting = 1;
7214 const jsonObject* user = verifyUserPCRUD( ctx );
7217 osrfAppRespondComplete( ctx, NULL );
7221 // Not PCRUD and have a user_id?
7222 int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7223 osrfAppRespondComplete( ctx, NULL );
7228 @brief Save a audit info
7229 @param ctx Pointer to the method context.
7230 @param user_id User ID to write as a string
7231 @param ws_id Workstation ID to write as a string
7233 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7234 if( ctx && ctx->session ) {
7235 osrfAppSession* session = ctx->session;
7237 osrfHash* cache = session->userData;
7239 // If the session doesn't already have a hash, create one. Make sure
7240 // that the application session frees the hash when it terminates.
7241 if( NULL == cache ) {
7242 session->userData = cache = osrfNewHash();
7243 osrfHashSetCallback( cache, &sessionDataFree );
7244 ctx->session->userDataFree = &userDataFree;
7247 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7249 osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7251 int errnum = dbi_conn_error( writehandle, &msg );
7254 "%s: Error setting auditor information: %d %s",
7257 msg ? msg : "(No description available)"
7259 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7260 "osrfMethodException", ctx->request, "Error setting auditor info" );
7261 if( !oilsIsDBConnected( writehandle ))
7262 osrfAppSessionPanic( ctx->session );
7265 dbi_result_free( result );