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*, const ClassInfo*, int, osrfMethodContext* );
94 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
95 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
97 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
99 void userDataFree( void* );
100 static void sessionDataFree( char*, void* );
101 static char* getRelation( osrfHash* );
102 static int obj_is_true( const jsonObject* obj );
103 static const char* json_type( int code );
104 static const char* get_primitive( osrfHash* field );
105 static const char* get_datatype( osrfHash* field );
106 static int is_identifier( const char* s );
107 static int is_good_operator( const char* op );
108 static void pop_query_frame( void );
109 static void push_query_frame( void );
110 static int add_query_core( const char* alias, const char* class_name );
111 static inline ClassInfo* search_alias( const char* target );
112 static ClassInfo* search_all_alias( const char* target );
113 static ClassInfo* add_joined_class( const char* alias, const char* classname );
114 static void clear_query_stack( void );
116 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
117 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
118 static char* org_tree_root( osrfMethodContext* ctx );
119 static jsonObject* single_hash( const char* key, const char* value );
121 static int child_initialized = 0; /* boolean */
123 static dbi_conn writehandle; /* our MASTER db connection */
124 static dbi_conn dbhandle; /* our CURRENT db connection */
125 //static osrfHash * readHandles;
127 // The following points to the top of a stack of QueryFrames. It's a little
128 // confusing because the top level of the query is at the bottom of the stack.
129 static QueryFrame* curr_query = NULL;
131 static dbi_conn writehandle; /* our MASTER db connection */
132 static dbi_conn dbhandle; /* our CURRENT db connection */
133 //static osrfHash * readHandles;
135 static int max_flesh_depth = 100;
137 static int enforce_pcrud = 0; // Boolean
138 static char* modulename = NULL;
141 @brief Select some options.
142 @param module_name: Name of the server.
143 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
145 This source file is used (at this writing) to implement three different servers:
146 - open-ils.reporter-store
150 These servers behave mostly the same, but they implement different combinations of
151 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
153 Here we use the server name in messages to identify which kind of server issued them.
154 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
156 void oilsSetSQLOptions( const char* module_name, int do_pcrud ) {
158 module_name = "open-ils.cstore"; // bulletproofing with a default
163 modulename = strdup( module_name );
165 enforce_pcrud = do_pcrud;
169 @brief Install a database connection.
170 @param conn Pointer to a database connection.
172 In some contexts, @a conn may merely provide a driver so that we can process strings
173 properly, without providing an open database connection.
175 void oilsSetDBConnection( dbi_conn conn ) {
176 dbhandle = writehandle = conn;
180 @brief Get a table name, view name, or subquery for use in a FROM clause.
181 @param class Pointer to the IDL class entry.
182 @return A table name, a view name, or a subquery in parentheses.
184 In some cases the IDL defines a class, not with a table name or a view name, but with
185 a SELECT statement, which may be used as a subquery.
187 static char* getRelation( osrfHash* class ) {
189 char* source_def = NULL;
190 const char* tabledef = osrfHashGet( class, "tablename" );
193 source_def = strdup( tabledef ); // Return the name of a table or view
195 tabledef = osrfHashGet( class, "source_definition" );
197 // Return a subquery, enclosed in parentheses
198 source_def = safe_malloc( strlen( tabledef ) + 3 );
199 source_def[ 0 ] = '(';
200 strcpy( source_def + 1, tabledef );
201 strcat( source_def, ")" );
203 // Not found: return an error
204 const char* classname = osrfHashGet( class, "classname" );
209 "%s ERROR No tablename or source_definition for class \"%s\"",
220 @brief Add datatypes from the database to the fields in the IDL.
221 @return Zero if successful, or 1 upon error.
223 For each relevant class in the IDL: ask the database for the datatype of every field.
224 In particular, determine which fields are text fields and which fields are numeric
225 fields, so that we know whether to enclose their values in quotes.
227 At this writing this function does not detect any errors, so it always returns zero.
229 int oilsExtendIDL( void ) {
230 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
231 osrfHash* class = NULL;
232 growing_buffer* query_buf = buffer_init( 64 );
234 // For each class in the IDL...
235 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
236 const char* classname = osrfHashIteratorKey( class_itr );
237 osrfHash* fields = osrfHashGet( class, "fields" );
239 // If the class is virtual, ignore it
240 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
241 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
245 char* tabledef = getRelation( class );
247 continue; // No such relation -- a query of it would be doomed to failure
249 buffer_reset( query_buf );
250 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
254 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
255 modulename, OSRF_BUFFER_C_STR( query_buf ) );
257 dbi_result result = dbi_conn_query( writehandle, OSRF_BUFFER_C_STR( query_buf ) );
261 const char* columnName;
262 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
264 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
267 /* fetch the fieldmapper index */
268 osrfHash* _f = osrfHashGet(fields, columnName);
271 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
273 /* determine the field type and storage attributes */
275 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
277 case DBI_TYPE_INTEGER : {
279 if( !osrfHashGet(_f, "primitive") )
280 osrfHashSet(_f, "number", "primitive");
282 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
283 if( attr & DBI_INTEGER_SIZE8 )
284 osrfHashSet( _f, "INT8", "datatype" );
286 osrfHashSet( _f, "INT", "datatype" );
289 case DBI_TYPE_DECIMAL :
290 if( !osrfHashGet( _f, "primitive" ))
291 osrfHashSet( _f, "number", "primitive" );
293 osrfHashSet( _f, "NUMERIC", "datatype" );
296 case DBI_TYPE_STRING :
297 if( !osrfHashGet( _f, "primitive" ))
298 osrfHashSet( _f, "string", "primitive" );
300 osrfHashSet( _f,"TEXT", "datatype" );
303 case DBI_TYPE_DATETIME :
304 if( !osrfHashGet( _f, "primitive" ))
305 osrfHashSet( _f, "string", "primitive" );
307 osrfHashSet( _f, "TIMESTAMP", "datatype" );
310 case DBI_TYPE_BINARY :
311 if( !osrfHashGet( _f, "primitive" ))
312 osrfHashSet( _f, "string", "primitive" );
314 osrfHashSet( _f, "BYTEA", "datatype" );
319 "Setting [%s] to primitive [%s] and datatype [%s]...",
321 osrfHashGet( _f, "primitive" ),
322 osrfHashGet( _f, "datatype" )
326 } // end while loop for traversing columns of result
327 dbi_result_free( result );
329 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]...", classname );
331 } // end for each class in IDL
333 buffer_free( query_buf );
334 osrfHashIteratorFree( class_itr );
335 child_initialized = 1;
340 @brief Free an osrfHash that stores a transaction ID.
341 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
343 This function is a callback, to be called by the application session when it ends.
344 The application session stores the osrfHash via an opaque pointer.
346 If the osrfHash contains an entry for the key "xact_id", it means that an
347 uncommitted transaction is pending. Roll it back.
349 void userDataFree( void* blob ) {
350 osrfHash* hash = (osrfHash*) blob;
351 if( osrfHashGet( hash, "xact_id" ) && writehandle )
352 dbi_conn_query( writehandle, "ROLLBACK;" );
354 osrfHashFree( hash );
358 @name Managing session data
359 @brief Maintain data stored via the userData pointer of the application session.
361 Currently, session-level data is stored in an osrfHash. Other arrangements are
362 possible, and some would be more efficient. The application session calls a
363 callback function to free userData before terminating.
365 Currently, the only data we store at the session level is the transaction id. By this
366 means we can ensure that any pending transactions are rolled back before the application
372 @brief Free an item in the application session's userData.
373 @param key The name of a key for an osrfHash.
374 @param item An opaque pointer to the item associated with the key.
376 We store an osrfHash as userData with the application session, and arrange (by
377 installing userDataFree() as a different callback) for the session to free that
378 osrfHash before terminating.
380 This function is a callback for freeing items in the osrfHash. Currently we store
382 - Transaction id of a pending transaction; a character string. Key: "xact_id".
383 - Authkey; a character string. Key: "authkey".
384 - User object from the authentication server; a jsonObject. Key: "user_login".
386 If we ever store anything else in userData, we will need to revisit this function so
387 that it will free whatever else needs freeing.
389 static void sessionDataFree( char* key, void* item ) {
390 if( !strcmp( key, "xact_id" )
391 || !strcmp( key, "authkey" ) ) {
393 } else if( !strcmp( key, "user_login" ) )
394 jsonObjectFree( (jsonObject*) item );
398 @brief Save a transaction id.
399 @param ctx Pointer to the method context.
401 Save the session_id of the current application session as a transaction id.
403 static void setXactId( osrfMethodContext* ctx ) {
404 if( ctx && ctx->session ) {
405 osrfAppSession* session = ctx->session;
407 osrfHash* cache = session->userData;
409 // If the session doesn't already have a hash, create one. Make sure
410 // that the application session frees the hash when it terminates.
411 if( NULL == cache ) {
412 session->userData = cache = osrfNewHash();
413 osrfHashSetCallback( cache, &sessionDataFree );
414 ctx->session->userDataFree = &userDataFree;
417 // Save the transaction id in the hash, with the key "xact_id"
418 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
423 @brief Get the transaction ID for the current transaction, if any.
424 @param ctx Pointer to the method context.
425 @return Pointer to the transaction ID.
427 The return value points to an internal buffer, and will become invalid upon issuing
428 a commit or rollback.
430 static inline const char* getXactId( osrfMethodContext* ctx ) {
431 if( ctx && ctx->session && ctx->session->userData )
432 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
438 @brief Clear the current transaction id.
439 @param ctx Pointer to the method context.
441 static inline void clearXactId( osrfMethodContext* ctx ) {
442 if( ctx && ctx->session && ctx->session->userData )
443 osrfHashRemove( ctx->session->userData, "xact_id" );
448 @brief Save the user's login in the userData for the current application session.
449 @param ctx Pointer to the method context.
450 @param user_login Pointer to the user login object to be cached (we cache the original,
453 If @a user_login is NULL, remove the user login if one is already cached.
455 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
456 if( ctx && ctx->session ) {
457 osrfAppSession* session = ctx->session;
459 osrfHash* cache = session->userData;
461 // If the session doesn't already have a hash, create one. Make sure
462 // that the application session frees the hash when it terminates.
463 if( NULL == cache ) {
464 session->userData = cache = osrfNewHash();
465 osrfHashSetCallback( cache, &sessionDataFree );
466 ctx->session->userDataFree = &userDataFree;
470 osrfHashSet( cache, user_login, "user_login" );
472 osrfHashRemove( cache, "user_login" );
477 @brief Get the user login object for the current application session, if any.
478 @param ctx Pointer to the method context.
479 @return Pointer to the user login object if found; otherwise NULL.
481 The user login object was returned from the authentication server, and then cached so
482 we don't have to call the authentication server again for the same user.
484 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
485 if( ctx && ctx->session && ctx->session->userData )
486 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
492 @brief Save a copy of an authkey in the userData of the current application session.
493 @param ctx Pointer to the method context.
494 @param authkey The authkey to be saved.
496 If @a authkey is NULL, remove the authkey if one is already cached.
498 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
499 if( ctx && ctx->session && authkey ) {
500 osrfAppSession* session = ctx->session;
501 osrfHash* cache = session->userData;
503 // If the session doesn't already have a hash, create one. Make sure
504 // that the application session frees the hash when it terminates.
505 if( NULL == cache ) {
506 session->userData = cache = osrfNewHash();
507 osrfHashSetCallback( cache, &sessionDataFree );
508 ctx->session->userDataFree = &userDataFree;
511 // Save the transaction id in the hash, with the key "xact_id"
512 if( authkey && *authkey )
513 osrfHashSet( cache, strdup( authkey ), "authkey" );
515 osrfHashRemove( cache, "authkey" );
520 @brief Reset the login timeout.
521 @param authkey The authentication key for the current login session.
522 @param now The current time.
523 @return Zero if successful, or 1 if not.
525 Tell the authentication server to reset the timeout so that the login session won't
526 expire for a while longer.
528 We could dispense with the @a now parameter by calling time(). But we just called
529 time() in order to decide whether to reset the timeout, so we might as well reuse
530 the result instead of calling time() again.
532 static int reset_timeout( const char* authkey, time_t now ) {
533 jsonObject* auth_object = jsonNewObject( authkey );
535 // Ask the authentication server to reset the timeout. It returns an event
536 // indicating success or failure.
537 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
538 "open-ils.auth.session.reset_timeout", auth_object );
539 jsonObjectFree( auth_object );
541 if( !result || result->type != JSON_HASH ) {
542 osrfLogError( OSRF_LOG_MARK,
543 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
544 jsonObjectFree( result );
545 return 1; // Not the right sort of object returned
548 const jsonObject* ilsevent = jsonObjectGetKey( result, "ilsevent" );
549 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
550 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
551 jsonObjectFree( result );
552 return 1; // Return code from method not available
555 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
556 const char* desc = jsonObjectGetString( jsonObjectGetKey( result, "desc" ) );
558 desc = "(No reason available)"; // failsafe; shouldn't happen
559 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
560 jsonObjectFree( result );
564 // Revise our local proxy for the timeout deadline
565 // by a smallish fraction of the timeout interval
566 const char* timeout = jsonObjectGetString( jsonObjectGetKey( result, "payload" ) );
568 timeout = "1"; // failsafe; shouldn't happen
569 time_next_reset = now + atoi( timeout ) / 15;
571 jsonObjectFree( result );
572 return 0; // Successfully reset timeout
576 @brief Get the authkey string for the current application session, if any.
577 @param ctx Pointer to the method context.
578 @return Pointer to the cached authkey if found; otherwise NULL.
580 If present, the authkey string was cached from a previous method call.
582 static const char* getAuthkey( osrfMethodContext* ctx ) {
583 if( ctx && ctx->session && ctx->session->userData ) {
584 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
586 // Possibly reset the authentication timeout to keep the login alive. We do so
587 // no more than once per method call, and not at all if it has been only a short
588 // time since the last reset.
590 // Here we reset explicitly, if at all. We also implicitly reset the timeout
591 // whenever we call the "open-ils.auth.session.retrieve" method.
592 if( timeout_needs_resetting ) {
593 time_t now = time( NULL );
594 if( now >= time_next_reset && reset_timeout( authkey, now ) )
595 authkey = NULL; // timeout has apparently expired already
598 timeout_needs_resetting = 0;
606 @brief Implement the transaction.begin method.
607 @param ctx Pointer to the method context.
608 @return Zero if successful, or -1 upon error.
610 Start a transaction. Save a transaction ID for future reference.
613 - authkey (PCRUD only)
615 Return to client: Transaction ID
617 int beginTransaction( osrfMethodContext* ctx ) {
618 if(osrfMethodVerifyContext( ctx )) {
619 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
623 if( enforce_pcrud ) {
624 timeout_needs_resetting = 1;
625 const jsonObject* user = verifyUserPCRUD( ctx );
630 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
632 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction", modulename );
633 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
634 "osrfMethodException", ctx->request, "Error starting transaction" );
638 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
639 osrfAppRespondComplete( ctx, ret );
640 jsonObjectFree( ret );
646 @brief Implement the savepoint.set method.
647 @param ctx Pointer to the method context.
648 @return Zero if successful, or -1 if not.
650 Issue a SAVEPOINT to the database server.
653 - authkey (PCRUD only)
656 Return to client: Savepoint name
658 int setSavepoint( osrfMethodContext* ctx ) {
659 if(osrfMethodVerifyContext( ctx )) {
660 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
665 if( enforce_pcrud ) {
667 timeout_needs_resetting = 1;
668 const jsonObject* user = verifyUserPCRUD( ctx );
673 // Verify that a transaction is pending
674 const char* trans_id = getXactId( ctx );
675 if( NULL == trans_id ) {
676 osrfAppSessionStatus(
678 OSRF_STATUS_INTERNALSERVERERROR,
679 "osrfMethodException",
681 "No active transaction -- required for savepoints"
686 // Get the savepoint name from the method params
687 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
689 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
693 "%s: Error creating savepoint %s in transaction %s",
698 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
699 "osrfMethodException", ctx->request, "Error creating savepoint" );
702 jsonObject* ret = jsonNewObject( spName );
703 osrfAppRespondComplete( ctx, ret );
704 jsonObjectFree( ret );
710 @brief Implement the savepoint.release method.
711 @param ctx Pointer to the method context.
712 @return Zero if successful, or -1 if not.
714 Issue a RELEASE SAVEPOINT to the database server.
717 - authkey (PCRUD only)
720 Return to client: Savepoint name
722 int releaseSavepoint( osrfMethodContext* ctx ) {
723 if(osrfMethodVerifyContext( ctx )) {
724 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
729 if( enforce_pcrud ) {
731 timeout_needs_resetting = 1;
732 const jsonObject* user = verifyUserPCRUD( ctx );
737 // Verify that a transaction is pending
738 const char* trans_id = getXactId( ctx );
739 if( NULL == trans_id ) {
740 osrfAppSessionStatus(
742 OSRF_STATUS_INTERNALSERVERERROR,
743 "osrfMethodException",
745 "No active transaction -- required for savepoints"
750 // Get the savepoint name from the method params
751 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
753 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
757 "%s: Error releasing savepoint %s in transaction %s",
762 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
763 "osrfMethodException", ctx->request, "Error releasing savepoint" );
766 jsonObject* ret = jsonNewObject( spName );
767 osrfAppRespondComplete( ctx, ret );
768 jsonObjectFree( ret );
774 @brief Implement the savepoint.rollback method.
775 @param ctx Pointer to the method context.
776 @return Zero if successful, or -1 if not.
778 Issue a ROLLBACK TO SAVEPOINT to the database server.
781 - authkey (PCRUD only)
784 Return to client: Savepoint name
786 int rollbackSavepoint( osrfMethodContext* ctx ) {
787 if(osrfMethodVerifyContext( ctx )) {
788 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
793 if( enforce_pcrud ) {
795 timeout_needs_resetting = 1;
796 const jsonObject* user = verifyUserPCRUD( ctx );
801 // Verify that a transaction is pending
802 const char* trans_id = getXactId( ctx );
803 if( NULL == trans_id ) {
804 osrfAppSessionStatus(
806 OSRF_STATUS_INTERNALSERVERERROR,
807 "osrfMethodException",
809 "No active transaction -- required for savepoints"
814 // Get the savepoint name from the method params
815 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
817 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
821 "%s: Error rolling back savepoint %s in transaction %s",
826 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
827 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
830 jsonObject* ret = jsonNewObject( spName );
831 osrfAppRespondComplete( ctx, ret );
832 jsonObjectFree( ret );
838 @brief Implement the transaction.commit method.
839 @param ctx Pointer to the method context.
840 @return Zero if successful, or -1 if not.
842 Issue a COMMIT to the database server.
845 - authkey (PCRUD only)
847 Return to client: Transaction ID.
849 int commitTransaction( osrfMethodContext* ctx ) {
850 if(osrfMethodVerifyContext( ctx )) {
851 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
855 if( enforce_pcrud ) {
856 timeout_needs_resetting = 1;
857 const jsonObject* user = verifyUserPCRUD( ctx );
862 // Verify that a transaction is pending
863 const char* trans_id = getXactId( ctx );
864 if( NULL == trans_id ) {
865 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
866 "osrfMethodException", ctx->request, "No active transaction to commit" );
870 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
872 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction", modulename );
873 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
874 "osrfMethodException", ctx->request, "Error committing transaction" );
877 jsonObject* ret = jsonNewObject( trans_id );
878 osrfAppRespondComplete( ctx, ret );
879 jsonObjectFree( ret );
886 @brief Implement the transaction.rollback method.
887 @param ctx Pointer to the method context.
888 @return Zero if successful, or -1 if not.
890 Issue a ROLLBACK to the database server.
893 - authkey (PCRUD only)
895 Return to client: Transaction ID
897 int rollbackTransaction( osrfMethodContext* ctx ) {
898 if( osrfMethodVerifyContext( ctx )) {
899 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
903 if( enforce_pcrud ) {
904 timeout_needs_resetting = 1;
905 const jsonObject* user = verifyUserPCRUD( ctx );
910 // Verify that a transaction is pending
911 const char* trans_id = getXactId( ctx );
912 if( NULL == trans_id ) {
913 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
914 "osrfMethodException", ctx->request, "No active transaction to roll back" );
918 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
920 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction", modulename );
921 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
922 "osrfMethodException", ctx->request, "Error rolling back transaction" );
925 jsonObject* ret = jsonNewObject( trans_id );
926 osrfAppRespondComplete( ctx, ret );
927 jsonObjectFree( ret );
934 @brief Implement the "search" method.
935 @param ctx Pointer to the method context.
936 @return Zero if successful, or -1 if not.
939 - authkey (PCRUD only)
940 - WHERE clause, as jsonObject
941 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
943 Return to client: rows of the specified class that satisfy a specified WHERE clause.
944 Optionally flesh linked fields.
946 int doSearch( osrfMethodContext* ctx ) {
947 if( osrfMethodVerifyContext( ctx )) {
948 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
953 timeout_needs_resetting = 1;
955 jsonObject* where_clause;
956 jsonObject* rest_of_query;
958 if( enforce_pcrud ) {
959 where_clause = jsonObjectGetIndex( ctx->params, 1 );
960 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
962 where_clause = jsonObjectGetIndex( ctx->params, 0 );
963 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
966 // Get the class metadata
967 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
968 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
972 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
974 osrfAppRespondComplete( ctx, NULL );
978 // Return each row to the client (except that some may be suppressed by PCRUD)
980 unsigned long res_idx = 0;
981 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
982 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
984 osrfAppRespond( ctx, cur );
986 jsonObjectFree( obj );
988 osrfAppRespondComplete( ctx, NULL );
993 @brief Implement the "id_list" method.
994 @param ctx Pointer to the method context.
995 @param err Pointer through which to return an error code.
996 @return Zero if successful, or -1 if not.
999 - authkey (PCRUD only)
1000 - WHERE clause, as jsonObject
1001 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1003 Return to client: The primary key values for all rows of the relevant class that
1004 satisfy a specified WHERE clause.
1006 This method relies on the assumption that every class has a primary key consisting of
1009 int doIdList( osrfMethodContext* ctx ) {
1010 if( osrfMethodVerifyContext( ctx )) {
1011 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1016 timeout_needs_resetting = 1;
1018 jsonObject* where_clause;
1019 jsonObject* rest_of_query;
1021 // We use the where clause without change. But we need to massage the rest of the
1022 // query, so we work with a copy of it instead of modifying the original.
1024 if( enforce_pcrud ) {
1025 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1026 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1028 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1029 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1032 // Eliminate certain SQL clauses, if present.
1033 if( rest_of_query ) {
1034 jsonObjectRemoveKey( rest_of_query, "select" );
1035 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1036 jsonObjectRemoveKey( rest_of_query, "flesh" );
1037 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
1039 rest_of_query = jsonNewObjectType( JSON_HASH );
1042 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1044 // Get the class metadata
1045 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1046 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1048 // Build a SELECT list containing just the primary key,
1049 // i.e. like { "classname":["keyname"] }
1050 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1052 // Load array with name of primary key
1053 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1054 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1055 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1057 jsonObjectSetKey( rest_of_query, "select", select_clause );
1062 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1064 jsonObjectFree( rest_of_query );
1066 osrfAppRespondComplete( ctx, NULL );
1070 // Return each primary key value to the client
1072 unsigned long res_idx = 0;
1073 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1074 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1075 continue; // Suppress due to lack of permission
1077 osrfAppRespond( ctx,
1078 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1081 jsonObjectFree( obj );
1082 osrfAppRespondComplete( ctx, NULL );
1087 @brief Verify that we have a valid class reference.
1088 @param ctx Pointer to the method context.
1089 @param param Pointer to the method parameters.
1090 @return 1 if the class reference is valid, or zero if it isn't.
1092 The class of the method params must match the class to which the method id devoted.
1093 For PCRUD there are additional restrictions.
1095 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1097 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1098 osrfHash* class = osrfHashGet( method_meta, "class" );
1100 // Compare the method's class to the parameters' class
1101 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1103 // Oops -- they don't match. Complain.
1104 growing_buffer* msg = buffer_init( 128 );
1107 "%s: %s method for type %s was passed a %s",
1109 osrfHashGet( method_meta, "methodtype" ),
1110 osrfHashGet( class, "classname" ),
1111 param->classname ? param->classname : "(null)"
1114 char* m = buffer_release( msg );
1115 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1123 return verifyObjectPCRUD( ctx, param );
1129 @brief (PCRUD only) Verify that the user is properly logged in.
1130 @param ctx Pointer to the method context.
1131 @return If the user is logged in, a pointer to the user object from the authentication
1132 server; otherwise NULL.
1134 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1136 // Get the authkey (the first method parameter)
1137 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1139 // See if we have the same authkey, and a user object,
1140 // locally cached from a previous call
1141 const char* cached_authkey = getAuthkey( ctx );
1142 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1143 const jsonObject* cached_user = getUserLogin( ctx );
1148 // We have no matching authentication data in the cache. Authenticate from scratch.
1149 jsonObject* auth_object = jsonNewObject( auth );
1151 // Fetch the user object from the authentication server
1152 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1154 jsonObjectFree( auth_object );
1156 if( !user->classname || strcmp(user->classname, "au" )) {
1158 growing_buffer* msg = buffer_init( 128 );
1161 "%s: permacrud received a bad auth token: %s",
1166 char* m = buffer_release( msg );
1167 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1171 jsonObjectFree( user );
1175 setUserLogin( ctx, user );
1176 setAuthkey( ctx, auth );
1178 // Allow ourselves up to a second before we have to reset the login timeout.
1179 // It would be nice to use some fraction of the timeout interval enforced by the
1180 // authentication server, but that value is not readily available at this point.
1181 // Instead, we use a conservative default interval.
1182 time_next_reset = time( NULL ) + 1;
1187 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1189 dbhandle = writehandle;
1191 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1192 osrfHash* class = osrfHashGet( method_metadata, "class" );
1193 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1196 if( *method_type == 's' || *method_type == 'i' ) {
1197 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1198 } else if( *method_type == 'u' || *method_type == 'd' ) {
1199 fetch = 1; // MUST go to the db for the object for update and delete
1202 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1205 // No permacrud for this method type on this class
1207 growing_buffer* msg = buffer_init( 128 );
1210 "%s: %s on class %s has no permacrud IDL entry",
1212 osrfHashGet( method_metadata, "methodtype" ),
1213 osrfHashGet( class, "classname" )
1216 char* m = buffer_release( msg );
1217 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1218 "osrfMethodException", ctx->request, m );
1225 const jsonObject* user = verifyUserPCRUD( ctx );
1229 int userid = atoi( oilsFMGetString( user, "id" ) );
1231 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1232 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1233 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1235 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1238 char* pkey_value = NULL;
1239 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1240 osrfLogDebug( OSRF_LOG_MARK,
1241 "global-level permissions required, fetching top of the org tree" );
1243 // check for perm at top of org tree
1244 char* org_tree_root_id = org_tree_root( ctx );
1245 if( org_tree_root_id ) {
1246 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1247 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1249 osrfStringArrayFree( context_org_array );
1254 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1255 "fetching context org ids" );
1256 const char* pkey = osrfHashGet( class, "primarykey" );
1257 jsonObject *param = NULL;
1259 if( obj->classname ) {
1260 pkey_value = oilsFMGetString( obj, pkey );
1262 param = jsonObjectClone( obj );
1263 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1266 pkey_value = jsonObjectToSimpleString( obj );
1268 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1269 "of %s and retrieving from the database", pkey_value );
1273 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1274 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1275 jsonObjectFree( _tmp_params );
1277 param = jsonObjectExtractIndex( _list, 0 );
1278 jsonObjectFree( _list );
1282 osrfLogDebug( OSRF_LOG_MARK,
1283 "Object not found in the database with primary key %s of %s",
1286 growing_buffer* msg = buffer_init( 128 );
1289 "%s: no object found with primary key %s of %s",
1295 char* m = buffer_release( msg );
1296 osrfAppSessionStatus(
1298 OSRF_STATUS_INTERNALSERVERERROR,
1299 "osrfMethodException",
1311 if( local_context->size > 0 ) {
1312 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1313 local_context->size );
1315 const char* lcontext = NULL;
1316 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1317 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1320 "adding class-local field %s (value: %s) to the context org list",
1322 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1327 if( foreign_context ) {
1328 unsigned long class_count = osrfHashGetCount( foreign_context );
1329 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1331 if( class_count > 0 ) {
1333 osrfHash* fcontext = NULL;
1334 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1335 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1336 const char* class_name = osrfHashIteratorKey( class_itr );
1337 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1341 "%d foreign context fields(s) specified for class %s",
1342 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1346 char* foreign_pkey = osrfHashGet( fcontext, "field" );
1347 char* foreign_pkey_value =
1348 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1350 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1352 jsonObject* _list = doFieldmapperSearch(
1353 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1355 jsonObject* _fparam = jsonObjectClone( jsonObjectGetIndex( _list, 0 ));
1356 jsonObjectFree( _tmp_params );
1357 jsonObjectFree( _list );
1359 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1361 if( _fparam && jump_list ) {
1362 const char* flink = NULL;
1364 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1365 free( foreign_pkey_value );
1367 osrfHash* foreign_link_hash =
1368 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1370 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1371 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1373 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1375 _list = doFieldmapperSearch(
1377 osrfHashGet( oilsIDL(),
1378 osrfHashGet( foreign_link_hash, "class" ) ),
1384 _fparam = jsonObjectClone( jsonObjectGetIndex( _list, 0 ));
1385 jsonObjectFree( _tmp_params );
1386 jsonObjectFree( _list );
1392 growing_buffer* msg = buffer_init( 128 );
1395 "%s: no object found with primary key %s of %s",
1401 char* m = buffer_release( msg );
1402 osrfAppSessionStatus(
1404 OSRF_STATUS_INTERNALSERVERERROR,
1405 "osrfMethodException",
1411 osrfHashIteratorFree( class_itr );
1412 free( foreign_pkey_value );
1413 jsonObjectFree( param );
1418 free( foreign_pkey_value );
1421 const char* foreign_field = NULL;
1422 while ( (foreign_field = osrfStringArrayGetString(
1423 osrfHashGet(fcontext,"context" ), j++ )) ) {
1424 osrfStringArrayAdd( context_org_array,
1425 oilsFMGetString( _fparam, foreign_field ) );
1428 "adding foreign class %s field %s (value: %s) to the context org list",
1431 osrfStringArrayGetString(
1432 context_org_array, context_org_array->size - 1 )
1436 jsonObjectFree( _fparam );
1439 osrfHashIteratorFree( class_itr );
1443 jsonObjectFree( param );
1446 const char* context_org = NULL;
1447 const char* perm = NULL;
1450 if( permission->size == 0 ) {
1451 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1456 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1458 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1464 "Checking object permission [%s] for user %d "
1465 "on object %s (class %s) at org %d",
1469 osrfHashGet( class, "classname" ),
1473 result = dbi_conn_queryf(
1475 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1478 osrfHashGet( class, "classname" ),
1486 "Received a result for object permission [%s] "
1487 "for user %d on object %s (class %s) at org %d",
1491 osrfHashGet( class, "classname" ),
1495 if( dbi_result_first_row( result )) {
1496 jsonObject* return_val = oilsMakeJSONFromResult( result );
1497 const char* has_perm = jsonObjectGetString(
1498 jsonObjectGetKeyConst( return_val, "has_perm" ));
1502 "Status of object permission [%s] for user %d "
1503 "on object %s (class %s) at org %d is %s",
1507 osrfHashGet(class, "classname"),
1512 if( *has_perm == 't' )
1514 jsonObjectFree( return_val );
1517 dbi_result_free( result );
1523 osrfLogDebug( OSRF_LOG_MARK,
1524 "Checking non-object permission [%s] for user %d at org %d",
1525 perm, userid, atoi(context_org) );
1526 result = dbi_conn_queryf(
1528 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1535 osrfLogDebug( OSRF_LOG_MARK,
1536 "Received a result for permission [%s] for user %d at org %d",
1537 perm, userid, atoi( context_org ));
1538 if( dbi_result_first_row( result )) {
1539 jsonObject* return_val = oilsMakeJSONFromResult( result );
1540 const char* has_perm = jsonObjectGetString(
1541 jsonObjectGetKeyConst( return_val, "has_perm" ));
1542 osrfLogDebug( OSRF_LOG_MARK,
1543 "Status of permission [%s] for user %d at org %d is [%s]",
1544 perm, userid, atoi( context_org ), has_perm );
1545 if( *has_perm == 't' )
1547 jsonObjectFree( return_val );
1550 dbi_result_free( result );
1562 osrfStringArrayFree( context_org_array );
1568 @brief Look up the root of the org_unit tree.
1569 @param ctx Pointer to the method context.
1570 @return The id of the root org unit, as a character string.
1572 Query actor.org_unit where parent_ou is null, and return the id as a string.
1574 This function assumes that there is only one root org unit, i.e. that we
1575 have a single tree, not a forest.
1577 The calling code is responsible for freeing the returned string.
1579 static char* org_tree_root( osrfMethodContext* ctx ) {
1581 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1582 static time_t last_lookup_time = 0;
1583 time_t current_time = time( NULL );
1585 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1586 // We successfully looked this up less than an hour ago.
1587 // It's not likely to have changed since then.
1588 return strdup( cached_root_id );
1590 last_lookup_time = current_time;
1593 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1594 jsonObject* result = doFieldmapperSearch(
1595 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1596 jsonObjectFree( where_clause );
1598 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1601 jsonObjectFree( result );
1603 growing_buffer* msg = buffer_init( 128 );
1604 OSRF_BUFFER_ADD( msg, modulename );
1605 OSRF_BUFFER_ADD( msg,
1606 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1608 char* m = buffer_release( msg );
1609 osrfAppSessionStatus( ctx->session,
1610 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1613 cached_root_id[ 0 ] = '\0';
1617 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1618 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1620 jsonObjectFree( result );
1622 strcpy( cached_root_id, root_org_unit_id );
1623 return root_org_unit_id;
1627 @brief Create a JSON_HASH with a single key/value pair.
1628 @param key The key of the key/value pair.
1629 @param value the value of the key/value pair.
1630 @return Pointer to a newly created jsonObject of type JSON_HASH.
1632 The value of the key/value is either a string or (if @a value is NULL) a null.
1634 static jsonObject* single_hash( const char* key, const char* value ) {
1636 if( ! key ) key = "";
1638 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1639 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1644 int doCreate( osrfMethodContext* ctx ) {
1645 if(osrfMethodVerifyContext( ctx )) {
1646 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1651 timeout_needs_resetting = 1;
1653 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1654 jsonObject* target = NULL;
1655 jsonObject* options = NULL;
1657 if( enforce_pcrud ) {
1658 target = jsonObjectGetIndex( ctx->params, 1 );
1659 options = jsonObjectGetIndex( ctx->params, 2 );
1661 target = jsonObjectGetIndex( ctx->params, 0 );
1662 options = jsonObjectGetIndex( ctx->params, 1 );
1665 if( !verifyObjectClass( ctx, target )) {
1666 osrfAppRespondComplete( ctx, NULL );
1670 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1672 const char* trans_id = getXactId( ctx );
1674 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1676 osrfAppSessionStatus(
1678 OSRF_STATUS_BADREQUEST,
1679 "osrfMethodException",
1681 "No active transaction -- required for CREATE"
1683 osrfAppRespondComplete( ctx, NULL );
1687 // The following test is harmless but redundant. If a class is
1688 // readonly, we don't register a create method for it.
1689 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1690 osrfAppSessionStatus(
1692 OSRF_STATUS_BADREQUEST,
1693 "osrfMethodException",
1695 "Cannot INSERT readonly class"
1697 osrfAppRespondComplete( ctx, NULL );
1701 // Set the last_xact_id
1702 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1704 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1705 trans_id, target->classname, index);
1706 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1709 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1711 dbhandle = writehandle;
1713 osrfHash* fields = osrfHashGet( meta, "fields" );
1714 char* pkey = osrfHashGet( meta, "primarykey" );
1715 char* seq = osrfHashGet( meta, "sequence" );
1717 growing_buffer* table_buf = buffer_init( 128 );
1718 growing_buffer* col_buf = buffer_init( 128 );
1719 growing_buffer* val_buf = buffer_init( 128 );
1721 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1722 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1723 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1724 buffer_add( val_buf,"VALUES (" );
1728 osrfHash* field = NULL;
1729 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1730 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1732 const char* field_name = osrfHashIteratorKey( field_itr );
1734 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1737 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1740 if( field_object && field_object->classname ) {
1741 value = oilsFMGetString(
1743 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
1745 } else if( field_object && JSON_BOOL == field_object->type ) {
1746 if( jsonBoolIsTrue( field_object ) )
1747 value = strdup( "t" );
1749 value = strdup( "f" );
1751 value = jsonObjectToSimpleString( field_object );
1757 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1758 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1761 buffer_add( col_buf, field_name );
1763 if( !field_object || field_object->type == JSON_NULL ) {
1764 buffer_add( val_buf, "DEFAULT" );
1766 } else if( !strcmp( get_primitive( field ), "number" )) {
1767 const char* numtype = get_datatype( field );
1768 if( !strcmp( numtype, "INT8" )) {
1769 buffer_fadd( val_buf, "%lld", atoll( value ));
1771 } else if( !strcmp( numtype, "INT" )) {
1772 buffer_fadd( val_buf, "%d", atoi( value ));
1774 } else if( !strcmp( numtype, "NUMERIC" )) {
1775 buffer_fadd( val_buf, "%f", atof( value ));
1778 if( dbi_conn_quote_string( writehandle, &value )) {
1779 OSRF_BUFFER_ADD( val_buf, value );
1782 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
1783 osrfAppSessionStatus(
1785 OSRF_STATUS_INTERNALSERVERERROR,
1786 "osrfMethodException",
1788 "Error quoting string -- please see the error log for more details"
1791 buffer_free( table_buf );
1792 buffer_free( col_buf );
1793 buffer_free( val_buf );
1794 osrfAppRespondComplete( ctx, NULL );
1802 osrfHashIteratorFree( field_itr );
1804 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1805 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1807 char* table_str = buffer_release( table_buf );
1808 char* col_str = buffer_release( col_buf );
1809 char* val_str = buffer_release( val_buf );
1810 growing_buffer* sql = buffer_init( 128 );
1811 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1816 char* query = buffer_release( sql );
1818 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
1820 jsonObject* obj = NULL;
1823 dbi_result result = dbi_conn_query( writehandle, query );
1825 obj = jsonNewObject( NULL );
1828 "%s ERROR inserting %s object using query [%s]",
1830 osrfHashGet(meta, "fieldmapper"),
1833 osrfAppSessionStatus(
1835 OSRF_STATUS_INTERNALSERVERERROR,
1836 "osrfMethodException",
1838 "INSERT error -- please see the error log for more details"
1843 char* id = oilsFMGetString( target, pkey );
1845 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
1846 growing_buffer* _id = buffer_init( 10 );
1847 buffer_fadd( _id, "%lld", new_id );
1848 id = buffer_release( _id );
1851 // Find quietness specification, if present
1852 const char* quiet_str = NULL;
1854 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1856 quiet_str = jsonObjectGetString( quiet_obj );
1859 if( str_is_true( quiet_str )) { // if quietness is specified
1860 obj = jsonNewObject( id );
1864 // Fetch the row that we just inserted, so that we can return it to the client
1865 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1866 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
1869 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
1873 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
1875 jsonObjectFree( list );
1876 jsonObjectFree( where_clause );
1883 osrfAppRespondComplete( ctx, obj );
1884 jsonObjectFree( obj );
1889 @brief Implement the retrieve method.
1890 @param ctx Pointer to the method context.
1891 @param err Pointer through which to return an error code.
1892 @return If successful, a pointer to the result to be returned to the client;
1895 From the method's class, fetch a row with a specified value in the primary key. This
1896 method relies on the database design convention that a primary key consists of a single
1900 - authkey (PCRUD only)
1901 - value of the primary key for the desired row, for building the WHERE clause
1902 - a JSON_HASH containing any other SQL clauses: select, join, etc.
1904 Return to client: One row from the query.
1906 int doRetrieve( osrfMethodContext* ctx ) {
1907 if(osrfMethodVerifyContext( ctx )) {
1908 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1913 timeout_needs_resetting = 1;
1918 if( enforce_pcrud ) {
1923 // Get the class metadata
1924 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1926 // Get the value of the primary key, from a method parameter
1927 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
1931 "%s retrieving %s object with primary key value of %s",
1933 osrfHashGet( class_def, "fieldmapper" ),
1934 jsonObjectGetString( id_obj )
1937 // Build a WHERE clause based on the key value
1938 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1941 osrfHashGet( class_def, "primarykey" ), // name of key column
1942 jsonObjectClone( id_obj ) // value of key column
1945 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
1949 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
1951 jsonObjectFree( where_clause );
1953 osrfAppRespondComplete( ctx, NULL );
1957 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
1958 jsonObjectFree( list );
1960 if( enforce_pcrud ) {
1961 if(!verifyObjectPCRUD( ctx, obj )) {
1962 jsonObjectFree( obj );
1964 growing_buffer* msg = buffer_init( 128 );
1965 OSRF_BUFFER_ADD( msg, modulename );
1966 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1968 char* m = buffer_release( msg );
1969 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
1973 osrfAppRespondComplete( ctx, NULL );
1978 osrfAppRespondComplete( ctx, obj );
1979 jsonObjectFree( obj );
1983 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
1984 growing_buffer* val_buf = buffer_init( 32 );
1985 const char* numtype = get_datatype( field );
1987 if( !strncmp( numtype, "INT", 3 ) ) {
1988 if( value->type == JSON_NUMBER )
1989 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1990 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1992 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1995 } else if( !strcmp( numtype, "NUMERIC" )) {
1996 if( value->type == JSON_NUMBER )
1997 buffer_fadd( val_buf, jsonObjectGetString( value ));
1999 buffer_fadd( val_buf, jsonObjectGetString( value ));
2003 // Presumably this was really intended ot be a string, so quote it
2004 char* str = jsonObjectToSimpleString( value );
2005 if( dbi_conn_quote_string( dbhandle, &str )) {
2006 OSRF_BUFFER_ADD( val_buf, str );
2009 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2011 buffer_free( val_buf );
2016 return buffer_release( val_buf );
2019 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2020 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2021 growing_buffer* sql_buf = buffer_init( 32 );
2027 osrfHashGet( field, "name" )
2031 buffer_add( sql_buf, "IN (" );
2032 } else if( !strcasecmp( op,"not in" )) {
2033 buffer_add( sql_buf, "NOT IN (" );
2035 buffer_add( sql_buf, "IN (" );
2038 if( node->type == JSON_HASH ) {
2039 // subquery predicate
2040 char* subpred = buildQuery( ctx, node, SUBSELECT );
2042 buffer_free( sql_buf );
2046 buffer_add( sql_buf, subpred );
2049 } else if( node->type == JSON_ARRAY ) {
2050 // literal value list
2051 int in_item_index = 0;
2052 int in_item_first = 1;
2053 const jsonObject* in_item;
2054 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2059 buffer_add( sql_buf, ", " );
2062 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2063 osrfLogError( OSRF_LOG_MARK,
2064 "%s: Expected string or number within IN list; found %s",
2065 modulename, json_type( in_item->type ) );
2066 buffer_free( sql_buf );
2070 // Append the literal value -- quoted if not a number
2071 if( JSON_NUMBER == in_item->type ) {
2072 char* val = jsonNumberToDBString( field, in_item );
2073 OSRF_BUFFER_ADD( sql_buf, val );
2076 } else if( !strcmp( get_primitive( field ), "number" )) {
2077 char* val = jsonNumberToDBString( field, in_item );
2078 OSRF_BUFFER_ADD( sql_buf, val );
2082 char* key_string = jsonObjectToSimpleString( in_item );
2083 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2084 OSRF_BUFFER_ADD( sql_buf, key_string );
2087 osrfLogError( OSRF_LOG_MARK,
2088 "%s: Error quoting key string [%s]", modulename, key_string );
2090 buffer_free( sql_buf );
2096 if( in_item_first ) {
2097 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2098 buffer_free( sql_buf );
2102 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2103 modulename, json_type( node->type ));
2104 buffer_free( sql_buf );
2108 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2110 return buffer_release( sql_buf );
2113 // Receive a JSON_ARRAY representing a function call. The first
2114 // entry in the array is the function name. The rest are parameters.
2115 static char* searchValueTransform( const jsonObject* array ) {
2117 if( array->size < 1 ) {
2118 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2122 // Get the function name
2123 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2124 if( func_item->type != JSON_STRING ) {
2125 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2126 modulename, json_type( func_item->type ));
2130 growing_buffer* sql_buf = buffer_init( 32 );
2132 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2133 OSRF_BUFFER_ADD( sql_buf, "( " );
2135 // Get the parameters
2136 int func_item_index = 1; // We already grabbed the zeroth entry
2137 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2139 // Add a separator comma, if we need one
2140 if( func_item_index > 2 )
2141 buffer_add( sql_buf, ", " );
2143 // Add the current parameter
2144 if( func_item->type == JSON_NULL ) {
2145 buffer_add( sql_buf, "NULL" );
2147 char* val = jsonObjectToSimpleString( func_item );
2148 if( dbi_conn_quote_string( dbhandle, &val )) {
2149 OSRF_BUFFER_ADD( sql_buf, val );
2152 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2154 buffer_free( sql_buf );
2161 buffer_add( sql_buf, " )" );
2163 return buffer_release( sql_buf );
2166 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2167 const jsonObject* node, const char* op ) {
2169 if( ! is_good_operator( op ) ) {
2170 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2174 char* val = searchValueTransform( node );
2178 growing_buffer* sql_buf = buffer_init( 32 );
2183 osrfHashGet( field, "name" ),
2190 return buffer_release( sql_buf );
2193 // class_alias is a class name or other table alias
2194 // field is a field definition as stored in the IDL
2195 // node comes from the method parameter, and may represent an entry in the SELECT list
2196 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2197 const jsonObject* node ) {
2198 growing_buffer* sql_buf = buffer_init( 32 );
2200 const char* field_transform = jsonObjectGetString(
2201 jsonObjectGetKeyConst( node, "transform" ) );
2202 const char* transform_subcolumn = jsonObjectGetString(
2203 jsonObjectGetKeyConst( node, "result_field" ) );
2205 if( transform_subcolumn ) {
2206 if( ! is_identifier( transform_subcolumn ) ) {
2207 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2208 modulename, transform_subcolumn );
2209 buffer_free( sql_buf );
2212 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2215 if( field_transform ) {
2217 if( ! is_identifier( field_transform ) ) {
2218 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2219 modulename, field_transform );
2220 buffer_free( sql_buf );
2224 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2225 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2226 field_transform, class_alias, osrfHashGet( field, "name" ));
2228 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2229 field_transform, class_alias, osrfHashGet( field, "name" ));
2232 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2235 if( array->type != JSON_ARRAY ) {
2236 osrfLogError( OSRF_LOG_MARK,
2237 "%s: Expected JSON_ARRAY for function params; found %s",
2238 modulename, json_type( array->type ) );
2239 buffer_free( sql_buf );
2242 int func_item_index = 0;
2243 jsonObject* func_item;
2244 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2246 char* val = jsonObjectToSimpleString( func_item );
2249 buffer_add( sql_buf, ",NULL" );
2250 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2251 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2252 OSRF_BUFFER_ADD( sql_buf, val );
2254 osrfLogError( OSRF_LOG_MARK,
2255 "%s: Error quoting key string [%s]", modulename, val );
2257 buffer_free( sql_buf );
2264 buffer_add( sql_buf, " )" );
2267 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2270 if( transform_subcolumn )
2271 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2273 return buffer_release( sql_buf );
2276 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2277 const jsonObject* node, const char* op ) {
2279 if( ! is_good_operator( op ) ) {
2280 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2284 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2285 if( ! field_transform )
2288 int extra_parens = 0; // boolean
2290 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2292 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2294 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2296 free( field_transform );
2300 } else if( value_obj->type == JSON_ARRAY ) {
2301 value = searchValueTransform( value_obj );
2303 osrfLogError( OSRF_LOG_MARK,
2304 "%s: Error building value transform for field transform", modulename );
2305 free( field_transform );
2308 } else if( value_obj->type == JSON_HASH ) {
2309 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2311 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2313 free( field_transform );
2317 } else if( value_obj->type == JSON_NUMBER ) {
2318 value = jsonNumberToDBString( field, value_obj );
2319 } else if( value_obj->type == JSON_NULL ) {
2320 osrfLogError( OSRF_LOG_MARK,
2321 "%s: Error building predicate for field transform: null value", modulename );
2322 free( field_transform );
2324 } else if( value_obj->type == JSON_BOOL ) {
2325 osrfLogError( OSRF_LOG_MARK,
2326 "%s: Error building predicate for field transform: boolean value", modulename );
2327 free( field_transform );
2330 if( !strcmp( get_primitive( field ), "number") ) {
2331 value = jsonNumberToDBString( field, value_obj );
2333 value = jsonObjectToSimpleString( value_obj );
2334 if( !dbi_conn_quote_string( dbhandle, &value )) {
2335 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2336 modulename, value );
2338 free( field_transform );
2344 const char* left_parens = "";
2345 const char* right_parens = "";
2347 if( extra_parens ) {
2352 growing_buffer* sql_buf = buffer_init( 32 );
2356 "%s%s %s %s %s %s%s",
2367 free( field_transform );
2369 return buffer_release( sql_buf );
2372 static char* searchSimplePredicate( const char* op, const char* class_alias,
2373 osrfHash* field, const jsonObject* node ) {
2375 if( ! is_good_operator( op ) ) {
2376 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2382 // Get the value to which we are comparing the specified column
2383 if( node->type != JSON_NULL ) {
2384 if( node->type == JSON_NUMBER ) {
2385 val = jsonNumberToDBString( field, node );
2386 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2387 val = jsonNumberToDBString( field, node );
2389 val = jsonObjectToSimpleString( node );
2394 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2395 // Value is not numeric; enclose it in quotes
2396 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2397 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2404 // Compare to a null value
2405 val = strdup( "NULL" );
2406 if( strcmp( op, "=" ))
2412 growing_buffer* sql_buf = buffer_init( 32 );
2413 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2414 char* pred = buffer_release( sql_buf );
2421 static char* searchBETWEENPredicate( const char* class_alias,
2422 osrfHash* field, const jsonObject* node ) {
2424 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2425 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2427 if( NULL == y_node ) {
2428 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2431 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2432 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2439 if( !strcmp( get_primitive( field ), "number") ) {
2440 x_string = jsonNumberToDBString( field, x_node );
2441 y_string = jsonNumberToDBString( field, y_node );
2444 x_string = jsonObjectToSimpleString( x_node );
2445 y_string = jsonObjectToSimpleString( y_node );
2446 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2447 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2448 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2449 modulename, x_string, y_string );
2456 growing_buffer* sql_buf = buffer_init( 32 );
2457 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2458 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2462 return buffer_release( sql_buf );
2465 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2466 jsonObject* node, osrfMethodContext* ctx ) {
2469 if( node->type == JSON_ARRAY ) { // equality IN search
2470 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2471 } else if( node->type == JSON_HASH ) { // other search
2472 jsonIterator* pred_itr = jsonNewIterator( node );
2473 if( !jsonIteratorHasNext( pred_itr ) ) {
2474 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2475 modulename, osrfHashGet(field, "name" ));
2477 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2479 // Verify that there are no additional predicates
2480 if( jsonIteratorHasNext( pred_itr ) ) {
2481 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2482 modulename, osrfHashGet(field, "name" ));
2483 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2484 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2485 else if( !(strcasecmp( pred_itr->key,"in" ))
2486 || !(strcasecmp( pred_itr->key,"not in" )) )
2487 pred = searchINPredicate(
2488 class_info->alias, field, pred_node, pred_itr->key, ctx );
2489 else if( pred_node->type == JSON_ARRAY )
2490 pred = searchFunctionPredicate(
2491 class_info->alias, field, pred_node, pred_itr->key );
2492 else if( pred_node->type == JSON_HASH )
2493 pred = searchFieldTransformPredicate(
2494 class_info, field, pred_node, pred_itr->key );
2496 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2498 jsonIteratorFree( pred_itr );
2500 } else if( node->type == JSON_NULL ) { // IS NULL search
2501 growing_buffer* _p = buffer_init( 64 );
2504 "\"%s\".%s IS NULL",
2505 class_info->class_name,
2506 osrfHashGet( field, "name" )
2508 pred = buffer_release( _p );
2509 } else { // equality search
2510 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2529 field : call_number,
2545 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2547 const jsonObject* working_hash;
2548 jsonObject* freeable_hash = NULL;
2550 if( join_hash->type == JSON_HASH ) {
2551 working_hash = join_hash;
2552 } else if( join_hash->type == JSON_STRING ) {
2553 // turn it into a JSON_HASH by creating a wrapper
2554 // around a copy of the original
2555 const char* _tmp = jsonObjectGetString( join_hash );
2556 freeable_hash = jsonNewObjectType( JSON_HASH );
2557 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2558 working_hash = freeable_hash;
2562 "%s: JOIN failed; expected JSON object type not found",
2568 growing_buffer* join_buf = buffer_init( 128 );
2569 const char* leftclass = left_info->class_name;
2571 jsonObject* snode = NULL;
2572 jsonIterator* search_itr = jsonNewIterator( working_hash );
2574 while ( (snode = jsonIteratorNext( search_itr )) ) {
2575 const char* right_alias = search_itr->key;
2577 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2579 class = right_alias;
2581 const ClassInfo* right_info = add_joined_class( right_alias, class );
2585 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2589 jsonIteratorFree( search_itr );
2590 buffer_free( join_buf );
2592 jsonObjectFree( freeable_hash );
2595 osrfHash* links = right_info->links;
2596 const char* table = right_info->source_def;
2598 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2599 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2601 if( field && !fkey ) {
2602 // Look up the corresponding join column in the IDL.
2603 // The link must be defined in the child table,
2604 // and point to the right parent table.
2605 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2606 const char* reltype = NULL;
2607 const char* other_class = NULL;
2608 reltype = osrfHashGet( idl_link, "reltype" );
2609 if( reltype && strcmp( reltype, "has_many" ) )
2610 other_class = osrfHashGet( idl_link, "class" );
2611 if( other_class && !strcmp( other_class, leftclass ) )
2612 fkey = osrfHashGet( idl_link, "key" );
2616 "%s: JOIN failed. No link defined from %s.%s to %s",
2622 buffer_free( join_buf );
2624 jsonObjectFree( freeable_hash );
2625 jsonIteratorFree( search_itr );
2629 } else if( !field && fkey ) {
2630 // Look up the corresponding join column in the IDL.
2631 // The link must be defined in the child table,
2632 // and point to the right parent table.
2633 osrfHash* left_links = left_info->links;
2634 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2635 const char* reltype = NULL;
2636 const char* other_class = NULL;
2637 reltype = osrfHashGet( idl_link, "reltype" );
2638 if( reltype && strcmp( reltype, "has_many" ) )
2639 other_class = osrfHashGet( idl_link, "class" );
2640 if( other_class && !strcmp( other_class, class ) )
2641 field = osrfHashGet( idl_link, "key" );
2645 "%s: JOIN failed. No link defined from %s.%s to %s",
2651 buffer_free( join_buf );
2653 jsonObjectFree( freeable_hash );
2654 jsonIteratorFree( search_itr );
2658 } else if( !field && !fkey ) {
2659 osrfHash* left_links = left_info->links;
2661 // For each link defined for the left class:
2662 // see if the link references the joined class
2663 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2664 osrfHash* curr_link = NULL;
2665 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2666 const char* other_class = osrfHashGet( curr_link, "class" );
2667 if( other_class && !strcmp( other_class, class ) ) {
2669 // In the IDL, the parent class doesn't always know then names of the child
2670 // columns that are pointing to it, so don't use that end of the link
2671 const char* reltype = osrfHashGet( curr_link, "reltype" );
2672 if( reltype && strcmp( reltype, "has_many" ) ) {
2673 // Found a link between the classes
2674 fkey = osrfHashIteratorKey( itr );
2675 field = osrfHashGet( curr_link, "key" );
2680 osrfHashIteratorFree( itr );
2682 if( !field || !fkey ) {
2683 // Do another such search, with the classes reversed
2685 // For each link defined for the joined class:
2686 // see if the link references the left class
2687 osrfHashIterator* itr = osrfNewHashIterator( links );
2688 osrfHash* curr_link = NULL;
2689 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2690 const char* other_class = osrfHashGet( curr_link, "class" );
2691 if( other_class && !strcmp( other_class, leftclass ) ) {
2693 // In the IDL, the parent class doesn't know then names of the child
2694 // columns that are pointing to it, so don't use that end of the link
2695 const char* reltype = osrfHashGet( curr_link, "reltype" );
2696 if( reltype && strcmp( reltype, "has_many" ) ) {
2697 // Found a link between the classes
2698 field = osrfHashIteratorKey( itr );
2699 fkey = osrfHashGet( curr_link, "key" );
2704 osrfHashIteratorFree( itr );
2707 if( !field || !fkey ) {
2710 "%s: JOIN failed. No link defined between %s and %s",
2715 buffer_free( join_buf );
2717 jsonObjectFree( freeable_hash );
2718 jsonIteratorFree( search_itr );
2723 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2725 if( !strcasecmp( type,"left" )) {
2726 buffer_add( join_buf, " LEFT JOIN" );
2727 } else if( !strcasecmp( type,"right" )) {
2728 buffer_add( join_buf, " RIGHT JOIN" );
2729 } else if( !strcasecmp( type,"full" )) {
2730 buffer_add( join_buf, " FULL JOIN" );
2732 buffer_add( join_buf, " INNER JOIN" );
2735 buffer_add( join_buf, " INNER JOIN" );
2738 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2739 table, right_alias, right_alias, field, left_info->alias, fkey );
2741 // Add any other join conditions as specified by "filter"
2742 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2744 const char* filter_op = jsonObjectGetString(
2745 jsonObjectGetKeyConst( snode, "filter_op" ) );
2746 if( filter_op && !strcasecmp( "or",filter_op )) {
2747 buffer_add( join_buf, " OR " );
2749 buffer_add( join_buf, " AND " );
2752 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2754 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2755 OSRF_BUFFER_ADD( join_buf, jpred );
2760 "%s: JOIN failed. Invalid conditional expression.",
2763 jsonIteratorFree( search_itr );
2764 buffer_free( join_buf );
2766 jsonObjectFree( freeable_hash );
2771 buffer_add( join_buf, " ) " );
2773 // Recursively add a nested join, if one is present
2774 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2776 char* jpred = searchJOIN( join_filter, right_info );
2778 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2779 OSRF_BUFFER_ADD( join_buf, jpred );
2782 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
2783 jsonIteratorFree( search_itr );
2784 buffer_free( join_buf );
2786 jsonObjectFree( freeable_hash );
2793 jsonObjectFree( freeable_hash );
2794 jsonIteratorFree( search_itr );
2796 return buffer_release( join_buf );
2801 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2802 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2803 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2805 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2807 search_hash is the JSON expression of the conditions.
2808 meta is the class definition from the IDL, for the relevant table.
2809 opjoin_type indicates whether multiple conditions, if present, should be
2810 connected by AND or OR.
2811 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2812 to pass it to other functions -- and all they do with it is to use the session
2813 and request members to send error messages back to the client.
2817 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
2818 int opjoin_type, osrfMethodContext* ctx ) {
2822 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
2823 "opjoin_type = %d, ctx addr = %p",
2826 class_info->class_def,
2831 growing_buffer* sql_buf = buffer_init( 128 );
2833 jsonObject* node = NULL;
2836 if( search_hash->type == JSON_ARRAY ) {
2837 osrfLogDebug( OSRF_LOG_MARK,
2838 "%s: In WHERE clause, condition type is JSON_ARRAY", modulename );
2839 if( 0 == search_hash->size ) {
2842 "%s: Invalid predicate structure: empty JSON array",
2845 buffer_free( sql_buf );
2849 unsigned long i = 0;
2850 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
2854 if( opjoin_type == OR_OP_JOIN )
2855 buffer_add( sql_buf, " OR " );
2857 buffer_add( sql_buf, " AND " );
2860 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2862 buffer_free( sql_buf );
2866 buffer_fadd( sql_buf, "( %s )", subpred );
2870 } else if( search_hash->type == JSON_HASH ) {
2871 osrfLogDebug( OSRF_LOG_MARK,
2872 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
2873 jsonIterator* search_itr = jsonNewIterator( search_hash );
2874 if( !jsonIteratorHasNext( search_itr ) ) {
2877 "%s: Invalid predicate structure: empty JSON object",
2880 jsonIteratorFree( search_itr );
2881 buffer_free( sql_buf );
2885 while( (node = jsonIteratorNext( search_itr )) ) {
2890 if( opjoin_type == OR_OP_JOIN )
2891 buffer_add( sql_buf, " OR " );
2893 buffer_add( sql_buf, " AND " );
2896 if( '+' == search_itr->key[ 0 ] ) {
2898 // This plus sign prefixes a class name or other table alias;
2899 // make sure the table alias is in scope
2900 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2901 if( ! alias_info ) {
2904 "%s: Invalid table alias \"%s\" in WHERE clause",
2908 jsonIteratorFree( search_itr );
2909 buffer_free( sql_buf );
2913 if( node->type == JSON_STRING ) {
2914 // It's the name of a column; make sure it belongs to the class
2915 const char* fieldname = jsonObjectGetString( node );
2916 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2919 "%s: Invalid column name \"%s\" in WHERE clause "
2920 "for table alias \"%s\"",
2925 jsonIteratorFree( search_itr );
2926 buffer_free( sql_buf );
2930 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
2932 // It's something more complicated
2933 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
2935 jsonIteratorFree( search_itr );
2936 buffer_free( sql_buf );
2940 buffer_fadd( sql_buf, "( %s )", subpred );
2943 } else if( '-' == search_itr->key[ 0 ] ) {
2944 if( !strcasecmp( "-or", search_itr->key )) {
2945 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
2947 jsonIteratorFree( search_itr );
2948 buffer_free( sql_buf );
2952 buffer_fadd( sql_buf, "( %s )", subpred );
2954 } else if( !strcasecmp( "-and", search_itr->key )) {
2955 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2957 jsonIteratorFree( search_itr );
2958 buffer_free( sql_buf );
2962 buffer_fadd( sql_buf, "( %s )", subpred );
2964 } else if( !strcasecmp("-not",search_itr->key) ) {
2965 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2967 jsonIteratorFree( search_itr );
2968 buffer_free( sql_buf );
2972 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
2974 } else if( !strcasecmp( "-exists", search_itr->key )) {
2975 char* subpred = buildQuery( ctx, node, SUBSELECT );
2977 jsonIteratorFree( search_itr );
2978 buffer_free( sql_buf );
2982 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
2984 } else if( !strcasecmp("-not-exists", search_itr->key )) {
2985 char* subpred = buildQuery( ctx, node, SUBSELECT );
2987 jsonIteratorFree( search_itr );
2988 buffer_free( sql_buf );
2992 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
2994 } else { // Invalid "minus" operator
2997 "%s: Invalid operator \"%s\" in WHERE clause",
3001 jsonIteratorFree( search_itr );
3002 buffer_free( sql_buf );
3008 const char* class = class_info->class_name;
3009 osrfHash* fields = class_info->fields;
3010 osrfHash* field = osrfHashGet( fields, search_itr->key );
3013 const char* table = class_info->source_def;
3016 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3019 table ? table : "?",
3022 jsonIteratorFree( search_itr );
3023 buffer_free( sql_buf );
3027 char* subpred = searchPredicate( class_info, field, node, ctx );
3029 buffer_free( sql_buf );
3030 jsonIteratorFree( search_itr );
3034 buffer_add( sql_buf, subpred );
3038 jsonIteratorFree( search_itr );
3041 // ERROR ... only hash and array allowed at this level
3042 char* predicate_string = jsonObjectToJSON( search_hash );
3045 "%s: Invalid predicate structure: %s",
3049 buffer_free( sql_buf );
3050 free( predicate_string );
3054 return buffer_release( sql_buf );
3057 /* Build a JSON_ARRAY of field names for a given table alias
3059 static jsonObject* defaultSelectList( const char* table_alias ) {
3064 ClassInfo* class_info = search_all_alias( table_alias );
3065 if( ! class_info ) {
3068 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3075 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3076 osrfHash* field_def = NULL;
3077 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3078 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3079 const char* field_name = osrfHashIteratorKey( field_itr );
3080 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3081 jsonObjectPush( array, jsonNewObject( field_name ) );
3084 osrfHashIteratorFree( field_itr );
3089 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3090 // The jsonObject must be a JSON_HASH with an single entry for "union",
3091 // "intersect", or "except". The data associated with this key must be an
3092 // array of hashes, each hash being a query.
3093 // Also allowed but currently ignored: entries for "order_by" and "alias".
3094 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3096 if( ! combo || combo->type != JSON_HASH )
3097 return NULL; // should be impossible; validated by caller
3099 const jsonObject* query_array = NULL; // array of subordinate queries
3100 const char* op = NULL; // name of operator, e.g. UNION
3101 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3102 int op_count = 0; // for detecting conflicting operators
3103 int excepting = 0; // boolean
3104 int all = 0; // boolean
3105 jsonObject* order_obj = NULL;
3107 // Identify the elements in the hash
3108 jsonIterator* query_itr = jsonNewIterator( combo );
3109 jsonObject* curr_obj = NULL;
3110 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3111 if( ! strcmp( "union", query_itr->key ) ) {
3114 query_array = curr_obj;
3115 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3118 query_array = curr_obj;
3119 } else if( ! strcmp( "except", query_itr->key ) ) {
3123 query_array = curr_obj;
3124 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3127 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3130 order_obj = curr_obj;
3131 } else if( ! strcmp( "alias", query_itr->key ) ) {
3132 if( curr_obj->type != JSON_STRING ) {
3133 jsonIteratorFree( query_itr );
3136 alias = jsonObjectGetString( curr_obj );
3137 } else if( ! strcmp( "all", query_itr->key ) ) {
3138 if( obj_is_true( curr_obj ) )
3142 osrfAppSessionStatus(
3144 OSRF_STATUS_INTERNALSERVERERROR,
3145 "osrfMethodException",
3147 "Malformed query; unexpected entry in query object"
3151 "%s: Unexpected entry for \"%s\" in%squery",
3156 jsonIteratorFree( query_itr );
3160 jsonIteratorFree( query_itr );
3162 // More sanity checks
3163 if( ! query_array ) {
3165 osrfAppSessionStatus(
3167 OSRF_STATUS_INTERNALSERVERERROR,
3168 "osrfMethodException",
3170 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3174 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3177 return NULL; // should be impossible...
3178 } else if( op_count > 1 ) {
3180 osrfAppSessionStatus(
3182 OSRF_STATUS_INTERNALSERVERERROR,
3183 "osrfMethodException",
3185 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3189 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3193 } if( query_array->type != JSON_ARRAY ) {
3195 osrfAppSessionStatus(
3197 OSRF_STATUS_INTERNALSERVERERROR,
3198 "osrfMethodException",
3200 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3204 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3207 json_type( query_array->type )
3210 } if( query_array->size < 2 ) {
3212 osrfAppSessionStatus(
3214 OSRF_STATUS_INTERNALSERVERERROR,
3215 "osrfMethodException",
3217 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3221 "%s:%srequires multiple queries as operands",
3226 } else if( excepting && query_array->size > 2 ) {
3228 osrfAppSessionStatus(
3230 OSRF_STATUS_INTERNALSERVERERROR,
3231 "osrfMethodException",
3233 "EXCEPT operator has too many queries as operands"
3237 "%s:EXCEPT operator has too many queries as operands",
3241 } else if( order_obj && ! alias ) {
3243 osrfAppSessionStatus(
3245 OSRF_STATUS_INTERNALSERVERERROR,
3246 "osrfMethodException",
3248 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3252 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3258 // So far so good. Now build the SQL.
3259 growing_buffer* sql = buffer_init( 256 );
3261 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3262 // Add a layer of parentheses
3263 if( flags & SUBCOMBO )
3264 OSRF_BUFFER_ADD( sql, "( " );
3266 // Traverse the query array. Each entry should be a hash.
3267 int first = 1; // boolean
3269 jsonObject* query = NULL;
3270 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3271 if( query->type != JSON_HASH ) {
3273 osrfAppSessionStatus(
3275 OSRF_STATUS_INTERNALSERVERERROR,
3276 "osrfMethodException",
3278 "Malformed query under UNION, INTERSECT or EXCEPT"
3282 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3285 json_type( query->type )
3294 OSRF_BUFFER_ADD( sql, op );
3296 OSRF_BUFFER_ADD( sql, "ALL " );
3299 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3303 "%s: Error building query under%s",
3311 OSRF_BUFFER_ADD( sql, query_str );
3314 if( flags & SUBCOMBO )
3315 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3317 if( !(flags & SUBSELECT) )
3318 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3320 return buffer_release( sql );
3323 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3324 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3325 // or "except" to indicate the type of query.
3326 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3330 osrfAppSessionStatus(
3332 OSRF_STATUS_INTERNALSERVERERROR,
3333 "osrfMethodException",
3335 "Malformed query; no query object"
3337 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3339 } else if( query->type != JSON_HASH ) {
3341 osrfAppSessionStatus(
3343 OSRF_STATUS_INTERNALSERVERERROR,
3344 "osrfMethodException",
3346 "Malformed query object"
3350 "%s: Query object is %s instead of JSON_HASH",
3352 json_type( query->type )
3357 // Determine what kind of query it purports to be, and dispatch accordingly.
3358 if( jsonObjectGetKey( query, "union" ) ||
3359 jsonObjectGetKey( query, "intersect" ) ||
3360 jsonObjectGetKey( query, "except" ) ) {
3361 return doCombo( ctx, query, flags );
3363 // It is presumably a SELECT query
3365 // Push a node onto the stack for the current query. Every level of
3366 // subquery gets its own QueryFrame on the Stack.
3369 // Build an SQL SELECT statement
3372 jsonObjectGetKey( query, "select" ),
3373 jsonObjectGetKey( query, "from" ),
3374 jsonObjectGetKey( query, "where" ),
3375 jsonObjectGetKey( query, "having" ),
3376 jsonObjectGetKey( query, "order_by" ),
3377 jsonObjectGetKey( query, "limit" ),
3378 jsonObjectGetKey( query, "offset" ),
3387 /* method context */ osrfMethodContext* ctx,
3389 /* SELECT */ jsonObject* selhash,
3390 /* FROM */ jsonObject* join_hash,
3391 /* WHERE */ jsonObject* search_hash,
3392 /* HAVING */ jsonObject* having_hash,
3393 /* ORDER BY */ jsonObject* order_hash,
3394 /* LIMIT */ jsonObject* limit,
3395 /* OFFSET */ jsonObject* offset,
3396 /* flags */ int flags
3398 const char* locale = osrf_message_get_last_locale();
3400 // general tmp objects
3401 const jsonObject* tmp_const;
3402 jsonObject* selclass = NULL;
3403 jsonObject* snode = NULL;
3404 jsonObject* onode = NULL;
3406 char* string = NULL;
3407 int from_function = 0;
3412 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3414 // punt if there's no FROM clause
3415 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3418 "%s: FROM clause is missing or empty",
3422 osrfAppSessionStatus(
3424 OSRF_STATUS_INTERNALSERVERERROR,
3425 "osrfMethodException",
3427 "FROM clause is missing or empty in JSON query"
3432 // the core search class
3433 const char* core_class = NULL;
3435 // get the core class -- the only key of the top level FROM clause, or a string
3436 if( join_hash->type == JSON_HASH ) {
3437 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3438 snode = jsonIteratorNext( tmp_itr );
3440 // Populate the current QueryFrame with information
3441 // about the core class
3442 if( add_query_core( NULL, tmp_itr->key ) ) {
3444 osrfAppSessionStatus(
3446 OSRF_STATUS_INTERNALSERVERERROR,
3447 "osrfMethodException",
3449 "Unable to look up core class"
3453 core_class = curr_query->core.class_name;
3456 jsonObject* extra = jsonIteratorNext( tmp_itr );
3458 jsonIteratorFree( tmp_itr );
3461 // There shouldn't be more than one entry in join_hash
3465 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3469 osrfAppSessionStatus(
3471 OSRF_STATUS_INTERNALSERVERERROR,
3472 "osrfMethodException",
3474 "Malformed FROM clause in JSON query"
3476 return NULL; // Malformed join_hash; extra entry
3478 } else if( join_hash->type == JSON_ARRAY ) {
3479 // We're selecting from a function, not from a table
3481 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3484 } else if( join_hash->type == JSON_STRING ) {
3485 // Populate the current QueryFrame with information
3486 // about the core class
3487 core_class = jsonObjectGetString( join_hash );
3489 if( add_query_core( NULL, core_class ) ) {
3491 osrfAppSessionStatus(
3493 OSRF_STATUS_INTERNALSERVERERROR,
3494 "osrfMethodException",
3496 "Unable to look up core class"
3504 "%s: FROM clause is unexpected JSON type: %s",
3506 json_type( join_hash->type )
3509 osrfAppSessionStatus(
3511 OSRF_STATUS_INTERNALSERVERERROR,
3512 "osrfMethodException",
3514 "Ill-formed FROM clause in JSON query"
3519 // Build the join clause, if any, while filling out the list
3520 // of joined classes in the current QueryFrame.
3521 char* join_clause = NULL;
3522 if( join_hash && ! from_function ) {
3524 join_clause = searchJOIN( join_hash, &curr_query->core );
3525 if( ! join_clause ) {
3527 osrfAppSessionStatus(
3529 OSRF_STATUS_INTERNALSERVERERROR,
3530 "osrfMethodException",
3532 "Unable to construct JOIN clause(s)"
3538 // For in case we don't get a select list
3539 jsonObject* defaultselhash = NULL;
3541 // if there is no select list, build a default select list ...
3542 if( !selhash && !from_function ) {
3543 jsonObject* default_list = defaultSelectList( core_class );
3544 if( ! default_list ) {
3546 osrfAppSessionStatus(
3548 OSRF_STATUS_INTERNALSERVERERROR,
3549 "osrfMethodException",
3551 "Unable to build default SELECT clause in JSON query"
3553 free( join_clause );
3558 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3559 jsonObjectSetKey( selhash, core_class, default_list );
3562 // The SELECT clause can be encoded only by a hash
3563 if( !from_function && selhash->type != JSON_HASH ) {
3566 "%s: Expected JSON_HASH for SELECT clause; found %s",
3568 json_type( selhash->type )
3572 osrfAppSessionStatus(
3574 OSRF_STATUS_INTERNALSERVERERROR,
3575 "osrfMethodException",
3577 "Malformed SELECT clause in JSON query"
3579 free( join_clause );
3583 // If you see a null or wild card specifier for the core class, or an
3584 // empty array, replace it with a default SELECT list
3585 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3587 int default_needed = 0; // boolean
3588 if( JSON_STRING == tmp_const->type
3589 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3591 else if( JSON_NULL == tmp_const->type )
3594 if( default_needed ) {
3595 // Build a default SELECT list
3596 jsonObject* default_list = defaultSelectList( core_class );
3597 if( ! default_list ) {
3599 osrfAppSessionStatus(
3601 OSRF_STATUS_INTERNALSERVERERROR,
3602 "osrfMethodException",
3604 "Can't build default SELECT clause in JSON query"
3606 free( join_clause );
3611 jsonObjectSetKey( selhash, core_class, default_list );
3615 // temp buffers for the SELECT list and GROUP BY clause
3616 growing_buffer* select_buf = buffer_init( 128 );
3617 growing_buffer* group_buf = buffer_init( 128 );
3619 int aggregate_found = 0; // boolean
3621 // Build a select list
3622 if( from_function ) // From a function we select everything
3623 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3626 // Build the SELECT list as SQL
3630 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3631 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3633 const char* cname = selclass_itr->key;
3635 // Make sure the target relation is in the FROM clause.
3637 // At this point join_hash is a step down from the join_hash we
3638 // received as a parameter. If the original was a JSON_STRING,
3639 // then json_hash is now NULL. If the original was a JSON_HASH,
3640 // then json_hash is now the first (and only) entry in it,
3641 // denoting the core class. We've already excluded the
3642 // possibility that the original was a JSON_ARRAY, because in
3643 // that case from_function would be non-NULL, and we wouldn't
3646 // If the current table alias isn't in scope, bail out
3647 ClassInfo* class_info = search_alias( cname );
3648 if( ! class_info ) {
3651 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3656 osrfAppSessionStatus(
3658 OSRF_STATUS_INTERNALSERVERERROR,
3659 "osrfMethodException",
3661 "Selected class not in FROM clause in JSON query"
3663 jsonIteratorFree( selclass_itr );
3664 buffer_free( select_buf );
3665 buffer_free( group_buf );
3666 if( defaultselhash )
3667 jsonObjectFree( defaultselhash );
3668 free( join_clause );
3672 if( selclass->type != JSON_ARRAY ) {
3675 "%s: Malformed SELECT list for class \"%s\"; not an array",
3680 osrfAppSessionStatus(
3682 OSRF_STATUS_INTERNALSERVERERROR,
3683 "osrfMethodException",
3685 "Selected class not in FROM clause in JSON query"
3688 jsonIteratorFree( selclass_itr );
3689 buffer_free( select_buf );
3690 buffer_free( group_buf );
3691 if( defaultselhash )
3692 jsonObjectFree( defaultselhash );
3693 free( join_clause );
3697 // Look up some attributes of the current class
3698 osrfHash* idlClass = class_info->class_def;
3699 osrfHash* class_field_set = class_info->fields;
3700 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3701 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3703 if( 0 == selclass->size ) {
3706 "%s: No columns selected from \"%s\"",
3712 // stitch together the column list for the current table alias...
3713 unsigned long field_idx = 0;
3714 jsonObject* selfield = NULL;
3715 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3717 // If we need a separator comma, add one
3721 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3724 // if the field specification is a string, add it to the list
3725 if( selfield->type == JSON_STRING ) {
3727 // Look up the field in the IDL
3728 const char* col_name = jsonObjectGetString( selfield );
3729 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3731 // No such field in current class
3734 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3740 osrfAppSessionStatus(
3742 OSRF_STATUS_INTERNALSERVERERROR,
3743 "osrfMethodException",
3745 "Selected column not defined in JSON query"
3747 jsonIteratorFree( selclass_itr );
3748 buffer_free( select_buf );
3749 buffer_free( group_buf );
3750 if( defaultselhash )
3751 jsonObjectFree( defaultselhash );
3752 free( join_clause );
3754 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3755 // Virtual field not allowed
3758 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3764 osrfAppSessionStatus(
3766 OSRF_STATUS_INTERNALSERVERERROR,
3767 "osrfMethodException",
3769 "Selected column may not be virtual in JSON query"
3771 jsonIteratorFree( selclass_itr );
3772 buffer_free( select_buf );
3773 buffer_free( group_buf );
3774 if( defaultselhash )
3775 jsonObjectFree( defaultselhash );
3776 free( join_clause );
3782 if( flags & DISABLE_I18N )
3785 i18n = osrfHashGet( field_def, "i18n" );
3787 if( str_is_true( i18n ) ) {
3788 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
3789 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3790 class_tname, cname, col_name, class_pkey,
3791 cname, class_pkey, locale, col_name );
3793 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3794 cname, col_name, col_name );
3797 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3798 cname, col_name, col_name );
3801 // ... but it could be an object, in which case we check for a Field Transform
3802 } else if( selfield->type == JSON_HASH ) {
3804 const char* col_name = jsonObjectGetString(
3805 jsonObjectGetKeyConst( selfield, "column" ) );
3807 // Get the field definition from the IDL
3808 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3810 // No such field in current class
3813 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3819 osrfAppSessionStatus(
3821 OSRF_STATUS_INTERNALSERVERERROR,
3822 "osrfMethodException",
3824 "Selected column is not defined in JSON query"
3826 jsonIteratorFree( selclass_itr );
3827 buffer_free( select_buf );
3828 buffer_free( group_buf );
3829 if( defaultselhash )
3830 jsonObjectFree( defaultselhash );
3831 free( join_clause );
3833 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
3834 // No such field in current class
3837 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3843 osrfAppSessionStatus(
3845 OSRF_STATUS_INTERNALSERVERERROR,
3846 "osrfMethodException",
3848 "Selected column is virtual in JSON query"
3850 jsonIteratorFree( selclass_itr );
3851 buffer_free( select_buf );
3852 buffer_free( group_buf );
3853 if( defaultselhash )
3854 jsonObjectFree( defaultselhash );
3855 free( join_clause );
3859 // Decide what to use as a column alias
3861 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3862 _alias = jsonObjectGetString( tmp_const );
3863 } else { // Use field name as the alias
3867 if( jsonObjectGetKeyConst( selfield, "transform" )) {
3868 char* transform_str = searchFieldTransform(
3869 class_info->alias, field_def, selfield );
3870 if( transform_str ) {
3871 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
3872 free( transform_str );
3875 osrfAppSessionStatus(
3877 OSRF_STATUS_INTERNALSERVERERROR,
3878 "osrfMethodException",
3880 "Unable to generate transform function in JSON query"
3882 jsonIteratorFree( selclass_itr );
3883 buffer_free( select_buf );
3884 buffer_free( group_buf );
3885 if( defaultselhash )
3886 jsonObjectFree( defaultselhash );
3887 free( join_clause );
3894 if( flags & DISABLE_I18N )
3897 i18n = osrfHashGet( field_def, "i18n" );
3899 if( str_is_true( i18n ) ) {
3900 buffer_fadd( select_buf,
3901 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
3902 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
3903 class_tname, cname, col_name, class_pkey, cname,
3904 class_pkey, locale, _alias );
3906 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3907 cname, col_name, _alias );
3910 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3911 cname, col_name, _alias );
3918 "%s: Selected item is unexpected JSON type: %s",
3920 json_type( selfield->type )
3923 osrfAppSessionStatus(
3925 OSRF_STATUS_INTERNALSERVERERROR,
3926 "osrfMethodException",
3928 "Ill-formed SELECT item in JSON query"
3930 jsonIteratorFree( selclass_itr );
3931 buffer_free( select_buf );
3932 buffer_free( group_buf );
3933 if( defaultselhash )
3934 jsonObjectFree( defaultselhash );
3935 free( join_clause );
3939 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3940 if( obj_is_true( agg_obj ) )
3941 aggregate_found = 1;
3943 // Append a comma (except for the first one)
3944 // and add the column to a GROUP BY clause
3948 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3950 buffer_fadd( group_buf, " %d", sel_pos );
3954 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3956 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3957 if ( ! obj_is_true( aggregate_obj ) ) {
3961 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3964 buffer_fadd(group_buf, " %d", sel_pos);
3967 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3971 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3974 _column = searchFieldTransform(class_info->alias, field, selfield);
3975 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3976 OSRF_BUFFER_ADD(group_buf, _column);
3977 _column = searchFieldTransform(class_info->alias, field, selfield);
3984 } // end while -- iterating across SELECT columns
3986 } // end while -- iterating across classes
3988 jsonIteratorFree( selclass_itr );
3992 char* col_list = buffer_release( select_buf );
3994 // Make sure the SELECT list isn't empty. This can happen, for example,
3995 // if we try to build a default SELECT clause from a non-core table.
3998 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4000 osrfAppSessionStatus(
4002 OSRF_STATUS_INTERNALSERVERERROR,
4003 "osrfMethodException",
4005 "SELECT list is empty"
4008 buffer_free( group_buf );
4009 if( defaultselhash )
4010 jsonObjectFree( defaultselhash );
4011 free( join_clause );
4017 table = searchValueTransform( join_hash );
4019 table = strdup( curr_query->core.source_def );
4023 osrfAppSessionStatus(
4025 OSRF_STATUS_INTERNALSERVERERROR,
4026 "osrfMethodException",
4028 "Unable to identify table for core class"
4031 buffer_free( group_buf );
4032 if( defaultselhash )
4033 jsonObjectFree( defaultselhash );
4034 free( join_clause );
4038 // Put it all together
4039 growing_buffer* sql_buf = buffer_init( 128 );
4040 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4044 // Append the join clause, if any
4046 buffer_add(sql_buf, join_clause );
4047 free( join_clause );
4050 char* order_by_list = NULL;
4051 char* having_buf = NULL;
4053 if( !from_function ) {
4055 // Build a WHERE clause, if there is one
4057 buffer_add( sql_buf, " WHERE " );
4059 // and it's on the WHERE clause
4060 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4063 osrfAppSessionStatus(
4065 OSRF_STATUS_INTERNALSERVERERROR,
4066 "osrfMethodException",
4068 "Severe query error in WHERE predicate -- see error log for more details"
4071 buffer_free( group_buf );
4072 buffer_free( sql_buf );
4073 if( defaultselhash )
4074 jsonObjectFree( defaultselhash );
4078 buffer_add( sql_buf, pred );
4082 // Build a HAVING clause, if there is one
4085 // and it's on the the WHERE clause
4086 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4088 if( ! having_buf ) {
4090 osrfAppSessionStatus(
4092 OSRF_STATUS_INTERNALSERVERERROR,
4093 "osrfMethodException",
4095 "Severe query error in HAVING predicate -- see error log for more details"
4098 buffer_free( group_buf );
4099 buffer_free( sql_buf );
4100 if( defaultselhash )
4101 jsonObjectFree( defaultselhash );
4106 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4108 // Build an ORDER BY clause, if there is one
4109 if( NULL == order_hash )
4110 ; // No ORDER BY? do nothing
4111 else if( JSON_ARRAY == order_hash->type ) {
4112 // Array of field specifications, each specification being a
4113 // hash to define the class, field, and other details
4115 jsonObject* order_spec;
4116 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4118 if( JSON_HASH != order_spec->type ) {
4119 osrfLogError( OSRF_LOG_MARK,
4120 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4121 modulename, json_type( order_spec->type ) );
4123 osrfAppSessionStatus(
4125 OSRF_STATUS_INTERNALSERVERERROR,
4126 "osrfMethodException",
4128 "Malformed ORDER BY clause -- see error log for more details"
4130 buffer_free( order_buf );
4132 buffer_free( group_buf );
4133 buffer_free( sql_buf );
4134 if( defaultselhash )
4135 jsonObjectFree( defaultselhash );
4139 const char* class_alias =
4140 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4142 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4145 OSRF_BUFFER_ADD( order_buf, ", " );
4147 order_buf = buffer_init( 128 );
4149 if( !field || !class_alias ) {
4150 osrfLogError( OSRF_LOG_MARK,
4151 "%s: Missing class or field name in field specification "
4152 "of ORDER BY clause",
4155 osrfAppSessionStatus(
4157 OSRF_STATUS_INTERNALSERVERERROR,
4158 "osrfMethodException",
4160 "Malformed ORDER BY clause -- see error log for more details"
4162 buffer_free( order_buf );
4164 buffer_free( group_buf );
4165 buffer_free( sql_buf );
4166 if( defaultselhash )
4167 jsonObjectFree( defaultselhash );
4171 ClassInfo* order_class_info = search_alias( class_alias );
4172 if( ! order_class_info ) {
4173 osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4174 "not in FROM clause", modulename, class_alias );
4176 osrfAppSessionStatus(
4178 OSRF_STATUS_INTERNALSERVERERROR,
4179 "osrfMethodException",
4181 "Invalid class referenced in ORDER BY clause -- "
4182 "see error log for more details"
4185 buffer_free( group_buf );
4186 buffer_free( sql_buf );
4187 if( defaultselhash )
4188 jsonObjectFree( defaultselhash );
4192 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4194 osrfLogError( OSRF_LOG_MARK,
4195 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4196 modulename, class_alias, field );
4198 osrfAppSessionStatus(
4200 OSRF_STATUS_INTERNALSERVERERROR,
4201 "osrfMethodException",
4203 "Invalid field referenced in ORDER BY clause -- "
4204 "see error log for more details"
4207 buffer_free( group_buf );
4208 buffer_free( sql_buf );
4209 if( defaultselhash )
4210 jsonObjectFree( defaultselhash );
4212 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4213 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4214 modulename, field );
4216 osrfAppSessionStatus(
4218 OSRF_STATUS_INTERNALSERVERERROR,
4219 "osrfMethodException",
4221 "Virtual field in ORDER BY clause -- see error log for more details"
4223 buffer_free( order_buf );
4225 buffer_free( group_buf );
4226 buffer_free( sql_buf );
4227 if( defaultselhash )
4228 jsonObjectFree( defaultselhash );
4232 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4233 char* transform_str = searchFieldTransform(
4234 class_alias, field_def, order_spec );
4235 if( ! transform_str ) {
4237 osrfAppSessionStatus(
4239 OSRF_STATUS_INTERNALSERVERERROR,
4240 "osrfMethodException",
4242 "Severe query error in ORDER BY clause -- "
4243 "see error log for more details"
4245 buffer_free( order_buf );
4247 buffer_free( group_buf );
4248 buffer_free( sql_buf );
4249 if( defaultselhash )
4250 jsonObjectFree( defaultselhash );
4254 OSRF_BUFFER_ADD( order_buf, transform_str );
4255 free( transform_str );
4258 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4260 const char* direction =
4261 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4263 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4264 OSRF_BUFFER_ADD( order_buf, " DESC" );
4266 OSRF_BUFFER_ADD( order_buf, " ASC" );
4269 } else if( JSON_HASH == order_hash->type ) {
4270 // This hash is keyed on class alias. Each class has either
4271 // an array of field names or a hash keyed on field name.
4272 jsonIterator* class_itr = jsonNewIterator( order_hash );
4273 while( (snode = jsonIteratorNext( class_itr )) ) {
4275 ClassInfo* order_class_info = search_alias( class_itr->key );
4276 if( ! order_class_info ) {
4277 osrfLogError( OSRF_LOG_MARK,
4278 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4279 modulename, class_itr->key );
4281 osrfAppSessionStatus(
4283 OSRF_STATUS_INTERNALSERVERERROR,
4284 "osrfMethodException",
4286 "Invalid class referenced in ORDER BY clause -- "
4287 "see error log for more details"
4289 jsonIteratorFree( class_itr );
4290 buffer_free( order_buf );
4292 buffer_free( group_buf );
4293 buffer_free( sql_buf );
4294 if( defaultselhash )
4295 jsonObjectFree( defaultselhash );
4299 osrfHash* field_list_def = order_class_info->fields;
4301 if( snode->type == JSON_HASH ) {
4303 // Hash is keyed on field names from the current class. For each field
4304 // there is another layer of hash to define the sorting details, if any,
4305 // or a string to indicate direction of sorting.
4306 jsonIterator* order_itr = jsonNewIterator( snode );
4307 while( (onode = jsonIteratorNext( order_itr )) ) {
4309 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4311 osrfLogError( OSRF_LOG_MARK,
4312 "%s: Invalid field \"%s\" in ORDER BY clause",
4313 modulename, order_itr->key );
4315 osrfAppSessionStatus(
4317 OSRF_STATUS_INTERNALSERVERERROR,
4318 "osrfMethodException",
4320 "Invalid field in ORDER BY clause -- "
4321 "see error log for more details"
4323 jsonIteratorFree( order_itr );
4324 jsonIteratorFree( class_itr );
4325 buffer_free( order_buf );
4327 buffer_free( group_buf );
4328 buffer_free( sql_buf );
4329 if( defaultselhash )
4330 jsonObjectFree( defaultselhash );
4332 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4333 osrfLogError( OSRF_LOG_MARK,
4334 "%s: Virtual field \"%s\" in ORDER BY clause",
4335 modulename, order_itr->key );
4337 osrfAppSessionStatus(
4339 OSRF_STATUS_INTERNALSERVERERROR,
4340 "osrfMethodException",
4342 "Virtual field in ORDER BY clause -- "
4343 "see error log for more details"
4345 jsonIteratorFree( order_itr );
4346 jsonIteratorFree( class_itr );
4347 buffer_free( order_buf );
4349 buffer_free( group_buf );
4350 buffer_free( sql_buf );
4351 if( defaultselhash )
4352 jsonObjectFree( defaultselhash );
4356 const char* direction = NULL;
4357 if( onode->type == JSON_HASH ) {
4358 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4359 string = searchFieldTransform(
4361 osrfHashGet( field_list_def, order_itr->key ),
4365 if( ctx ) osrfAppSessionStatus(
4367 OSRF_STATUS_INTERNALSERVERERROR,
4368 "osrfMethodException",
4370 "Severe query error in ORDER BY clause -- "
4371 "see error log for more details"
4373 jsonIteratorFree( order_itr );
4374 jsonIteratorFree( class_itr );
4376 buffer_free( group_buf );
4377 buffer_free( order_buf);
4378 buffer_free( sql_buf );
4379 if( defaultselhash )
4380 jsonObjectFree( defaultselhash );
4384 growing_buffer* field_buf = buffer_init( 16 );
4385 buffer_fadd( field_buf, "\"%s\".%s",
4386 class_itr->key, order_itr->key );
4387 string = buffer_release( field_buf );
4390 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4391 const char* dir = jsonObjectGetString( tmp_const );
4392 if(!strncasecmp( dir, "d", 1 )) {
4393 direction = " DESC";
4399 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4400 osrfLogError( OSRF_LOG_MARK,
4401 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4402 modulename, json_type( onode->type ) );
4404 osrfAppSessionStatus(
4406 OSRF_STATUS_INTERNALSERVERERROR,
4407 "osrfMethodException",
4409 "Malformed ORDER BY clause -- see error log for more details"
4411 jsonIteratorFree( order_itr );
4412 jsonIteratorFree( class_itr );
4414 buffer_free( group_buf );
4415 buffer_free( order_buf );
4416 buffer_free( sql_buf );
4417 if( defaultselhash )
4418 jsonObjectFree( defaultselhash );
4422 string = strdup( order_itr->key );
4423 const char* dir = jsonObjectGetString( onode );
4424 if( !strncasecmp( dir, "d", 1 )) {
4425 direction = " DESC";
4432 OSRF_BUFFER_ADD( order_buf, ", " );
4434 order_buf = buffer_init( 128 );
4436 OSRF_BUFFER_ADD( order_buf, string );
4440 OSRF_BUFFER_ADD( order_buf, direction );
4444 jsonIteratorFree( order_itr );
4446 } else if( snode->type == JSON_ARRAY ) {
4448 // Array is a list of fields from the current class
4449 unsigned long order_idx = 0;
4450 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4452 const char* _f = jsonObjectGetString( onode );
4454 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4456 osrfLogError( OSRF_LOG_MARK,
4457 "%s: Invalid field \"%s\" in ORDER BY clause",
4460 osrfAppSessionStatus(
4462 OSRF_STATUS_INTERNALSERVERERROR,
4463 "osrfMethodException",
4465 "Invalid field in ORDER BY clause -- "
4466 "see error log for more details"
4468 jsonIteratorFree( class_itr );
4469 buffer_free( order_buf );
4471 buffer_free( group_buf );
4472 buffer_free( sql_buf );
4473 if( defaultselhash )
4474 jsonObjectFree( defaultselhash );
4476 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4477 osrfLogError( OSRF_LOG_MARK,
4478 "%s: Virtual field \"%s\" in ORDER BY clause",
4481 osrfAppSessionStatus(
4483 OSRF_STATUS_INTERNALSERVERERROR,
4484 "osrfMethodException",
4486 "Virtual field in ORDER BY clause -- "
4487 "see error log for more details"
4489 jsonIteratorFree( class_itr );
4490 buffer_free( order_buf );
4492 buffer_free( group_buf );
4493 buffer_free( sql_buf );
4494 if( defaultselhash )
4495 jsonObjectFree( defaultselhash );
4500 OSRF_BUFFER_ADD( order_buf, ", " );
4502 order_buf = buffer_init( 128 );
4504 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4508 // IT'S THE OOOOOOOOOOOLD STYLE!
4510 osrfLogError( OSRF_LOG_MARK,
4511 "%s: Possible SQL injection attempt; direct order by is not allowed",
4514 osrfAppSessionStatus(
4516 OSRF_STATUS_INTERNALSERVERERROR,
4517 "osrfMethodException",
4519 "Severe query error -- see error log for more details"
4524 buffer_free( group_buf );
4525 buffer_free( order_buf );
4526 buffer_free( sql_buf );
4527 if( defaultselhash )
4528 jsonObjectFree( defaultselhash );
4529 jsonIteratorFree( class_itr );
4533 jsonIteratorFree( class_itr );
4535 osrfLogError( OSRF_LOG_MARK,
4536 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4537 modulename, json_type( order_hash->type ) );
4539 osrfAppSessionStatus(
4541 OSRF_STATUS_INTERNALSERVERERROR,
4542 "osrfMethodException",
4544 "Malformed ORDER BY clause -- see error log for more details"
4546 buffer_free( order_buf );
4548 buffer_free( group_buf );
4549 buffer_free( sql_buf );
4550 if( defaultselhash )
4551 jsonObjectFree( defaultselhash );
4556 order_by_list = buffer_release( order_buf );
4560 string = buffer_release( group_buf );
4562 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4563 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4564 OSRF_BUFFER_ADD( sql_buf, string );
4569 if( having_buf && *having_buf ) {
4570 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4571 OSRF_BUFFER_ADD( sql_buf, having_buf );
4575 if( order_by_list ) {
4577 if( *order_by_list ) {
4578 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4579 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4582 free( order_by_list );
4586 const char* str = jsonObjectGetString( limit );
4587 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4591 const char* str = jsonObjectGetString( offset );
4592 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4595 if( !(flags & SUBSELECT) )
4596 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4598 if( defaultselhash )
4599 jsonObjectFree( defaultselhash );
4601 return buffer_release( sql_buf );
4603 } // end of SELECT()
4605 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4607 const char* locale = osrf_message_get_last_locale();
4609 osrfHash* fields = osrfHashGet( meta, "fields" );
4610 char* core_class = osrfHashGet( meta, "classname" );
4612 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4614 jsonObject* node = NULL;
4615 jsonObject* snode = NULL;
4616 jsonObject* onode = NULL;
4617 const jsonObject* _tmp = NULL;
4618 jsonObject* selhash = NULL;
4619 jsonObject* defaultselhash = NULL;
4621 growing_buffer* sql_buf = buffer_init( 128 );
4622 growing_buffer* select_buf = buffer_init( 128 );
4624 if( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4625 defaultselhash = jsonNewObjectType( JSON_HASH );
4626 selhash = defaultselhash;
4629 // If there's no SELECT list for the core class, build one
4630 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4631 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4633 // Add every non-virtual field to the field list
4634 osrfHash* field_def = NULL;
4635 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4636 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4637 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4638 const char* field = osrfHashIteratorKey( field_itr );
4639 jsonObjectPush( field_list, jsonNewObject( field ) );
4642 osrfHashIteratorFree( field_itr );
4643 jsonObjectSetKey( selhash, core_class, field_list );
4647 jsonIterator* class_itr = jsonNewIterator( selhash );
4648 while( (snode = jsonIteratorNext( class_itr )) ) {
4650 const char* cname = class_itr->key;
4651 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4655 if( strcmp(core_class,class_itr->key )) {
4659 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4660 if( !found->size ) {
4661 jsonObjectFree( found );
4665 jsonObjectFree( found );
4668 jsonIterator* select_itr = jsonNewIterator( snode );
4669 while( (node = jsonIteratorNext( select_itr )) ) {
4670 const char* item_str = jsonObjectGetString( node );
4671 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4672 char* fname = osrfHashGet( field, "name" );
4680 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4685 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4686 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4689 i18n = osrfHashGet( field, "i18n" );
4691 if( str_is_true( i18n ) ) {
4692 char* pkey = osrfHashGet( idlClass, "primarykey" );
4693 char* tname = osrfHashGet( idlClass, "tablename" );
4695 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4696 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4697 tname, cname, fname, pkey, cname, pkey, locale, fname );
4699 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4702 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4706 jsonIteratorFree( select_itr );
4709 jsonIteratorFree( class_itr );
4711 char* col_list = buffer_release( select_buf );
4712 char* table = getRelation( meta );
4714 table = strdup( "(null)" );
4716 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4720 // Clear the query stack (as a fail-safe precaution against possible
4721 // leftover garbage); then push the first query frame onto the stack.
4722 clear_query_stack();
4724 if( add_query_core( NULL, core_class ) ) {
4726 osrfAppSessionStatus(
4728 OSRF_STATUS_INTERNALSERVERERROR,
4729 "osrfMethodException",
4731 "Unable to build query frame for core class"
4737 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4738 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
4739 OSRF_BUFFER_ADD( sql_buf, join_clause );
4740 free( join_clause );
4743 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4744 modulename, OSRF_BUFFER_C_STR( sql_buf ));
4746 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
4748 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4750 osrfAppSessionStatus(
4752 OSRF_STATUS_INTERNALSERVERERROR,
4753 "osrfMethodException",
4755 "Severe query error -- see error log for more details"
4757 buffer_free( sql_buf );
4758 if( defaultselhash )
4759 jsonObjectFree( defaultselhash );
4760 clear_query_stack();
4763 buffer_add( sql_buf, pred );
4768 char* string = NULL;
4769 if( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4771 growing_buffer* order_buf = buffer_init( 128 );
4774 jsonIterator* class_itr = jsonNewIterator( _tmp );
4775 while( (snode = jsonIteratorNext( class_itr )) ) {
4777 if( !jsonObjectGetKeyConst( selhash,class_itr->key ))
4780 if( snode->type == JSON_HASH ) {
4782 jsonIterator* order_itr = jsonNewIterator( snode );
4783 while( (onode = jsonIteratorNext( order_itr )) ) {
4785 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4786 class_itr->key, order_itr->key );
4790 char* direction = NULL;
4791 if( onode->type == JSON_HASH ) {
4792 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4793 string = searchFieldTransform( class_itr->key, field_def, onode );
4795 osrfAppSessionStatus(
4797 OSRF_STATUS_INTERNALSERVERERROR,
4798 "osrfMethodException",
4800 "Severe query error in ORDER BY clause -- "
4801 "see error log for more details"
4803 jsonIteratorFree( order_itr );
4804 jsonIteratorFree( class_itr );
4805 buffer_free( order_buf );
4806 buffer_free( sql_buf );
4807 if( defaultselhash )
4808 jsonObjectFree( defaultselhash );
4809 clear_query_stack();
4813 growing_buffer* field_buf = buffer_init( 16 );
4814 buffer_fadd( field_buf, "\"%s\".%s",
4815 class_itr->key, order_itr->key );
4816 string = buffer_release( field_buf );
4819 if( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4820 const char* dir = jsonObjectGetString( _tmp );
4821 if(!strncasecmp( dir, "d", 1 )) {
4822 direction = " DESC";
4826 string = strdup( order_itr->key );
4827 const char* dir = jsonObjectGetString( onode );
4828 if( !strncasecmp( dir, "d", 1 )) {
4829 direction = " DESC";
4838 buffer_add( order_buf, ", " );
4841 buffer_add( order_buf, string );
4845 buffer_add( order_buf, direction );
4849 jsonIteratorFree( order_itr );
4852 const char* str = jsonObjectGetString( snode );
4853 buffer_add( order_buf, str );
4859 jsonIteratorFree( class_itr );
4861 string = buffer_release( order_buf );
4864 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4865 OSRF_BUFFER_ADD( sql_buf, string );
4871 if( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ) {
4872 const char* str = jsonObjectGetString( _tmp );
4880 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4882 const char* str = jsonObjectGetString( _tmp );
4891 if( defaultselhash )
4892 jsonObjectFree( defaultselhash );
4893 clear_query_stack();
4895 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4896 return buffer_release( sql_buf );
4899 int doJSONSearch ( osrfMethodContext* ctx ) {
4900 if(osrfMethodVerifyContext( ctx )) {
4901 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4905 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
4910 dbhandle = writehandle;
4912 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
4916 if( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4917 flags |= SELECT_DISTINCT;
4919 if( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4920 flags |= DISABLE_I18N;
4922 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
4923 clear_query_stack(); // a possibly needless precaution
4924 char* sql = buildQuery( ctx, hash, flags );
4925 clear_query_stack();
4932 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
4933 dbi_result result = dbi_conn_query( dbhandle, sql );
4936 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
4938 if( dbi_result_first_row( result )) {
4939 /* JSONify the result */
4940 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
4943 jsonObject* return_val = oilsMakeJSONFromResult( result );
4944 osrfAppRespond( ctx, return_val );
4945 jsonObjectFree( return_val );
4946 } while( dbi_result_next_row( result ));
4949 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
4952 osrfAppRespondComplete( ctx, NULL );
4954 /* clean up the query */
4955 dbi_result_free( result );
4959 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]", modulename, sql );
4960 osrfAppSessionStatus(
4962 OSRF_STATUS_INTERNALSERVERERROR,
4963 "osrfMethodException",
4965 "Severe query error -- see error log for more details"
4973 // The last parameter, err, is used to report an error condition by updating an int owned by
4974 // the calling code.
4976 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
4977 // It is the responsibility of the calling code to initialize *err before the
4978 // call, so that it will be able to make sense of the result.
4980 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
4981 // redundant anyway.
4982 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
4983 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4986 dbhandle = writehandle;
4988 char* core_class = osrfHashGet( class_meta, "classname" );
4989 char* pkey = osrfHashGet( class_meta, "primarykey" );
4991 const jsonObject* _tmp;
4993 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
4995 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5000 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5002 dbi_result result = dbi_conn_query( dbhandle, sql );
5003 if( NULL == result ) {
5004 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
5005 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql );
5006 osrfAppSessionStatus(
5008 OSRF_STATUS_INTERNALSERVERERROR,
5009 "osrfMethodException",
5011 "Severe query error -- see error log for more details"
5018 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5021 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5022 jsonObject* row_obj = NULL;
5024 if( dbi_result_first_row( result )) {
5026 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5027 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5028 // eliminate the duplicates.
5029 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5030 osrfHash* dedup = osrfNewHash();
5032 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5033 char* pkey_val = oilsFMGetString( row_obj, pkey );
5034 if( osrfHashGet( dedup, pkey_val ) ) {
5035 jsonObjectFree( row_obj );
5038 osrfHashSet( dedup, pkey_val, pkey_val );
5039 jsonObjectPush( res_list, row_obj );
5041 } while( dbi_result_next_row( result ));
5042 osrfHashFree( dedup );
5045 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5049 /* clean up the query */
5050 dbi_result_free( result );
5053 // If we're asked to flesh, and there's anything to flesh, then flesh.
5054 if( res_list->size && query_hash ) {
5055 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5057 // Get the flesh depth
5058 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5059 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5060 flesh_depth = max_flesh_depth;
5062 // We need a non-zero flesh depth, and a list of fields to flesh
5063 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5064 if( temp_blob && flesh_depth > 0 ) {
5066 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5067 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5069 osrfStringArray* link_fields = NULL;
5070 osrfHash* links = osrfHashGet( class_meta, "links" );
5072 // Make an osrfStringArray of the names of fields to be fleshed
5073 if( flesh_fields ) {
5074 if( flesh_fields->size == 1 ) {
5075 const char* _t = jsonObjectGetString(
5076 jsonObjectGetIndex( flesh_fields, 0 ) );
5077 if( !strcmp( _t, "*" ))
5078 link_fields = osrfHashKeys( links );
5081 if( !link_fields ) {
5083 link_fields = osrfNewStringArray( 1 );
5084 jsonIterator* _i = jsonNewIterator( flesh_fields );
5085 while ((_f = jsonIteratorNext( _i ))) {
5086 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5088 jsonIteratorFree( _i );
5092 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5094 // Iterate over the JSON_ARRAY of rows
5096 unsigned long res_idx = 0;
5097 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5100 const char* link_field;
5102 // Iterate over the list of fleshable fields
5103 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5105 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5107 osrfHash* kid_link = osrfHashGet( links, link_field );
5109 continue; // Not a link field; skip it
5111 osrfHash* field = osrfHashGet( fields, link_field );
5113 continue; // Not a field at all; skip it (IDL is ill-formed)
5115 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5116 osrfHashGet( kid_link, "class" ));
5118 continue; // The class it links to doesn't exist; skip it
5120 const char* reltype = osrfHashGet( kid_link, "reltype" );
5122 continue; // No reltype; skip it (IDL is ill-formed)
5124 osrfHash* value_field = field;
5126 if( !strcmp( reltype, "has_many" )
5127 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5128 value_field = osrfHashGet(
5129 fields, osrfHashGet( class_meta, "primarykey" ) );
5132 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5134 if( link_map->size > 0 ) {
5135 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5138 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5143 osrfHashGet( kid_link, "class" ),
5150 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5151 osrfHashGet( kid_link, "field" ),
5152 osrfHashGet( kid_link, "class" ),
5153 osrfHashGet( kid_link, "key" ),
5154 osrfHashGet( kid_link, "reltype" )
5157 const char* search_key = jsonObjectGetString(
5158 jsonObjectGetIndex( cur,
5159 atoi( osrfHashGet( value_field, "array_position" ) )
5164 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5168 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5170 // construct WHERE clause
5171 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5174 osrfHashGet( kid_link, "key" ),
5175 jsonNewObject( search_key )
5178 // construct the rest of the query, mostly
5179 // by copying pieces of the previous level of query
5180 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5181 jsonObjectSetKey( rest_of_query, "flesh",
5182 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5186 jsonObjectSetKey( rest_of_query, "flesh_fields",
5187 jsonObjectClone( flesh_blob ));
5189 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5190 jsonObjectSetKey( rest_of_query, "order_by",
5191 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5195 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5196 jsonObjectSetKey( rest_of_query, "select",
5197 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5201 // do the query, recursively, to expand the fleshable field
5202 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5203 where_clause, rest_of_query, err );
5205 jsonObjectFree( where_clause );
5206 jsonObjectFree( rest_of_query );
5209 osrfStringArrayFree( link_fields );
5210 jsonObjectFree( res_list );
5211 jsonObjectFree( flesh_blob );
5215 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5216 osrfHashGet( kid_link, "class" ), kids->size );
5218 // Traverse the result set
5219 jsonObject* X = NULL;
5220 if( link_map->size > 0 && kids->size > 0 ) {
5222 kids = jsonNewObjectType( JSON_ARRAY );
5224 jsonObject* _k_node;
5225 unsigned long res_idx = 0;
5226 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5232 (unsigned long) atoi(
5238 osrfHashGet( kid_link, "class" )
5242 osrfStringArrayGetString( link_map, 0 )
5250 } // end while loop traversing X
5253 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5254 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5255 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5256 osrfHashGet( kid_link, "field" ));
5259 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5260 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5264 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5266 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5267 osrfHashGet( kid_link, "field" ) );
5270 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5271 jsonObjectClone( kids )
5276 jsonObjectFree( kids );
5280 jsonObjectFree( kids );
5282 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5283 osrfHashGet( kid_link, "field" ) );
5284 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5286 } // end while loop traversing list of fleshable fields
5287 } // end while loop traversing res_list
5288 jsonObjectFree( flesh_blob );
5289 osrfStringArrayFree( link_fields );
5298 int doUpdate( osrfMethodContext* ctx ) {
5299 if( osrfMethodVerifyContext( ctx )) {
5300 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5305 timeout_needs_resetting = 1;
5307 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5309 jsonObject* target = NULL;
5311 target = jsonObjectGetIndex( ctx->params, 1 );
5313 target = jsonObjectGetIndex( ctx->params, 0 );
5315 if(!verifyObjectClass( ctx, target )) {
5316 osrfAppRespondComplete( ctx, NULL );
5320 if( getXactId( ctx ) == NULL ) {
5321 osrfAppSessionStatus(
5323 OSRF_STATUS_BADREQUEST,
5324 "osrfMethodException",
5326 "No active transaction -- required for UPDATE"
5328 osrfAppRespondComplete( ctx, NULL );
5332 // The following test is harmless but redundant. If a class is
5333 // readonly, we don't register an update method for it.
5334 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5335 osrfAppSessionStatus(
5337 OSRF_STATUS_BADREQUEST,
5338 "osrfMethodException",
5340 "Cannot UPDATE readonly class"
5342 osrfAppRespondComplete( ctx, NULL );
5346 dbhandle = writehandle;
5347 const char* trans_id = getXactId( ctx );
5349 // Set the last_xact_id
5350 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5352 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5353 trans_id, target->classname, index );
5354 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5357 char* pkey = osrfHashGet( meta, "primarykey" );
5358 osrfHash* fields = osrfHashGet( meta, "fields" );
5360 char* id = oilsFMGetString( target, pkey );
5364 "%s updating %s object with %s = %s",
5366 osrfHashGet( meta, "fieldmapper" ),
5371 growing_buffer* sql = buffer_init( 128 );
5372 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5375 osrfHash* field_def = NULL;
5376 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5377 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5379 // Skip virtual fields, and the primary key
5380 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5383 const char* field_name = osrfHashIteratorKey( field_itr );
5384 if( ! strcmp( field_name, pkey ) )
5387 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5389 int value_is_numeric = 0; // boolean
5391 if( field_object && field_object->classname ) {
5392 value = oilsFMGetString(
5394 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5396 } else if( field_object && JSON_BOOL == field_object->type ) {
5397 if( jsonBoolIsTrue( field_object ) )
5398 value = strdup( "t" );
5400 value = strdup( "f" );
5402 value = jsonObjectToSimpleString( field_object );
5403 if( field_object && JSON_NUMBER == field_object->type )
5404 value_is_numeric = 1;
5407 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5408 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5410 if( !field_object || field_object->type == JSON_NULL ) {
5411 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5412 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5416 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5417 buffer_fadd( sql, " %s = NULL", field_name );
5420 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5424 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5426 const char* numtype = get_datatype( field_def );
5427 if( !strncmp( numtype, "INT", 3 ) ) {
5428 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5429 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5430 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5432 // Must really be intended as a string, so quote it
5433 if( dbi_conn_quote_string( dbhandle, &value )) {
5434 buffer_fadd( sql, " %s = %s", field_name, value );
5436 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5437 modulename, value );
5438 osrfAppSessionStatus(
5440 OSRF_STATUS_INTERNALSERVERERROR,
5441 "osrfMethodException",
5443 "Error quoting string -- please see the error log for more details"
5447 osrfHashIteratorFree( field_itr );
5449 osrfAppRespondComplete( ctx, NULL );
5454 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5457 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5461 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5462 buffer_fadd( sql, " %s = %s", field_name, value );
5464 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5465 osrfAppSessionStatus(
5467 OSRF_STATUS_INTERNALSERVERERROR,
5468 "osrfMethodException",
5470 "Error quoting string -- please see the error log for more details"
5474 osrfHashIteratorFree( field_itr );
5476 osrfAppRespondComplete( ctx, NULL );
5485 osrfHashIteratorFree( field_itr );
5487 jsonObject* obj = jsonNewObject( id );
5489 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5490 dbi_conn_quote_string( dbhandle, &id );
5492 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5494 char* query = buffer_release( sql );
5495 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5497 dbi_result result = dbi_conn_query( dbhandle, query );
5501 jsonObjectFree( obj );
5502 obj = jsonNewObject( NULL );
5505 "%s ERROR updating %s object with %s = %s",
5507 osrfHashGet( meta, "fieldmapper" ),
5514 osrfAppRespondComplete( ctx, obj );
5515 jsonObjectFree( obj );
5519 int doDelete( osrfMethodContext* ctx ) {
5520 if( osrfMethodVerifyContext( ctx )) {
5521 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5526 timeout_needs_resetting = 1;
5528 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5530 if( getXactId( ctx ) == NULL ) {
5531 osrfAppSessionStatus(
5533 OSRF_STATUS_BADREQUEST,
5534 "osrfMethodException",
5536 "No active transaction -- required for DELETE"
5538 osrfAppRespondComplete( ctx, NULL );
5542 // The following test is harmless but redundant. If a class is
5543 // readonly, we don't register a delete method for it.
5544 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5545 osrfAppSessionStatus(
5547 OSRF_STATUS_BADREQUEST,
5548 "osrfMethodException",
5550 "Cannot DELETE readonly class"
5552 osrfAppRespondComplete( ctx, NULL );
5556 dbhandle = writehandle;
5558 char* pkey = osrfHashGet( meta, "primarykey" );
5565 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5566 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5567 osrfAppRespondComplete( ctx, NULL );
5571 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5573 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5574 osrfAppRespondComplete( ctx, NULL );
5577 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5582 "%s deleting %s object with %s = %s",
5584 osrfHashGet( meta, "fieldmapper" ),
5589 jsonObject* obj = jsonNewObject( id );
5591 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5592 dbi_conn_quote_string( writehandle, &id );
5594 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
5595 osrfHashGet( meta, "tablename" ), pkey, id );
5598 jsonObjectFree( obj );
5599 obj = jsonNewObject( NULL );
5602 "%s ERROR deleting %s object with %s = %s",
5604 osrfHashGet( meta, "fieldmapper" ),
5612 osrfAppRespondComplete( ctx, obj );
5613 jsonObjectFree( obj );
5618 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5619 @param result An iterator for a result set; we only look at the current row.
5620 @param @meta Pointer to the class metadata for the core class.
5621 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5623 If a column is not defined in the IDL, or if it has no array_position defined for it in
5624 the IDL, or if it is defined as virtual, ignore it.
5626 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5627 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5628 array_position in the IDL.
5630 A field defined in the IDL but not represented in the returned row will leave a hole
5631 in the JSON_ARRAY. In effect it will be treated as a null value.
5633 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5634 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5635 classname corresponding to the @a meta argument.
5637 The calling code is responsible for freeing the the resulting jsonObject by calling
5640 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5641 if( !( result && meta )) return NULL;
5643 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5644 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
5645 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
5647 osrfHash* fields = osrfHashGet( meta, "fields" );
5649 int columnIndex = 1;
5650 const char* columnName;
5652 /* cycle through the columns in the row returned from the database */
5653 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
5655 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5657 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5659 /* determine the field type and storage attributes */
5660 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
5661 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5663 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5664 // or if it has no sequence number there, or if it's virtual, skip it.
5665 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
5668 if( str_is_true( osrfHashGet( _f, "virtual" )))
5669 continue; // skip this column: IDL says it's virtual
5671 const char* pos = (char*) osrfHashGet( _f, "array_position" );
5672 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
5673 continue; // since we assign sequence numbers dynamically as we load the IDL.
5675 fmIndex = atoi( pos );
5676 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
5678 continue; // This field is not defined in the IDL
5681 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
5682 // sequence number from the IDL (which is likely to be different from the sequence
5683 // of columns in the SELECT clause).
5684 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5685 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
5690 case DBI_TYPE_INTEGER :
5692 if( attr & DBI_INTEGER_SIZE8 )
5693 jsonObjectSetIndex( object, fmIndex,
5694 jsonNewNumberObject(
5695 dbi_result_get_longlong_idx( result, columnIndex )));
5697 jsonObjectSetIndex( object, fmIndex,
5698 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
5702 case DBI_TYPE_DECIMAL :
5703 jsonObjectSetIndex( object, fmIndex,
5704 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
5707 case DBI_TYPE_STRING :
5712 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
5717 case DBI_TYPE_DATETIME : {
5719 char dt_string[ 256 ] = "";
5722 // Fetch the date column as a time_t
5723 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
5725 // Translate the time_t to a human-readable string
5726 if( !( attr & DBI_DATETIME_DATE )) {
5727 gmtime_r( &_tmp_dt, &gmdt );
5728 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
5729 } else if( !( attr & DBI_DATETIME_TIME )) {
5730 localtime_r( &_tmp_dt, &gmdt );
5731 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
5733 localtime_r( &_tmp_dt, &gmdt );
5734 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
5737 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
5741 case DBI_TYPE_BINARY :
5742 osrfLogError( OSRF_LOG_MARK,
5743 "Can't do binary at column %s : index %d", columnName, columnIndex );
5752 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5753 if( !result ) return NULL;
5755 jsonObject* object = jsonNewObject( NULL );
5758 char dt_string[ 256 ];
5762 int columnIndex = 1;
5764 unsigned short type;
5765 const char* columnName;
5767 /* cycle through the column list */
5768 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
5770 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5772 fmIndex = -1; // reset the position
5774 /* determine the field type and storage attributes */
5775 type = dbi_result_get_field_type_idx( result, columnIndex );
5776 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5778 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5779 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
5784 case DBI_TYPE_INTEGER :
5786 if( attr & DBI_INTEGER_SIZE8 )
5787 jsonObjectSetKey( object, columnName,
5788 jsonNewNumberObject( dbi_result_get_longlong_idx(
5789 result, columnIndex )) );
5791 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
5792 dbi_result_get_int_idx( result, columnIndex )) );
5795 case DBI_TYPE_DECIMAL :
5796 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
5797 dbi_result_get_double_idx( result, columnIndex )) );
5800 case DBI_TYPE_STRING :
5801 jsonObjectSetKey( object, columnName,
5802 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
5805 case DBI_TYPE_DATETIME :
5807 memset( dt_string, '\0', sizeof( dt_string ));
5808 memset( &gmdt, '\0', sizeof( gmdt ));
5810 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
5812 if( !( attr & DBI_DATETIME_DATE )) {
5813 gmtime_r( &_tmp_dt, &gmdt );
5814 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
5815 } else if( !( attr & DBI_DATETIME_TIME )) {
5816 localtime_r( &_tmp_dt, &gmdt );
5817 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
5819 localtime_r( &_tmp_dt, &gmdt );
5820 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
5823 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
5826 case DBI_TYPE_BINARY :
5827 osrfLogError( OSRF_LOG_MARK,
5828 "Can't do binary at column %s : index %d", columnName, columnIndex );
5832 } // end while loop traversing result
5837 // Interpret a string as true or false
5838 int str_is_true( const char* str ) {
5839 if( NULL == str || strcasecmp( str, "true" ) )
5845 // Interpret a jsonObject as true or false
5846 static int obj_is_true( const jsonObject* obj ) {
5849 else switch( obj->type )
5857 if( strcasecmp( obj->value.s, "true" ) )
5861 case JSON_NUMBER : // Support 1/0 for perl's sake
5862 if( jsonObjectGetNumber( obj ) == 1.0 )
5871 // Translate a numeric code into a text string identifying a type of
5872 // jsonObject. To be used for building error messages.
5873 static const char* json_type( int code ) {
5879 return "JSON_ARRAY";
5881 return "JSON_STRING";
5883 return "JSON_NUMBER";
5889 return "(unrecognized)";
5893 // Extract the "primitive" attribute from an IDL field definition.
5894 // If we haven't initialized the app, then we must be running in
5895 // some kind of testbed. In that case, default to "string".
5896 static const char* get_primitive( osrfHash* field ) {
5897 const char* s = osrfHashGet( field, "primitive" );
5899 if( child_initialized )
5902 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5904 osrfHashGet( field, "name" )
5912 // Extract the "datatype" attribute from an IDL field definition.
5913 // If we haven't initialized the app, then we must be running in
5914 // some kind of testbed. In that case, default to to NUMERIC,
5915 // since we look at the datatype only for numbers.
5916 static const char* get_datatype( osrfHash* field ) {
5917 const char* s = osrfHashGet( field, "datatype" );
5919 if( child_initialized )
5922 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5924 osrfHashGet( field, "name" )
5933 @brief Determine whether a string is potentially a valid SQL identifier.
5934 @param s The identifier to be tested.
5935 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
5937 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
5938 need to follow all the rules exactly, such as requiring that the first character not
5941 We allow leading and trailing white space. In between, we do not allow punctuation
5942 (except for underscores and dollar signs), control characters, or embedded white space.
5944 More pedantically we should allow quoted identifiers containing arbitrary characters, but
5945 for the foreseeable future such quoted identifiers are not likely to be an issue.
5947 static int is_identifier( const char* s) {
5951 // Skip leading white space
5952 while( isspace( (unsigned char) *s ) )
5956 return 0; // Nothing but white space? Not okay.
5958 // Check each character until we reach white space or
5959 // end-of-string. Letters, digits, underscores, and
5960 // dollar signs are okay. With the exception of periods
5961 // (as in schema.identifier), control characters and other
5962 // punctuation characters are not okay. Anything else
5963 // is okay -- it could for example be part of a multibyte
5964 // UTF8 character such as a letter with diacritical marks,
5965 // and those are allowed.
5967 if( isalnum( (unsigned char) *s )
5971 ; // Fine; keep going
5972 else if( ispunct( (unsigned char) *s )
5973 || iscntrl( (unsigned char) *s ) )
5976 } while( *s && ! isspace( (unsigned char) *s ) );
5978 // If we found any white space in the above loop,
5979 // the rest had better be all white space.
5981 while( isspace( (unsigned char) *s ) )
5985 return 0; // White space was embedded within non-white space
5991 @brief Determine whether to accept a character string as a comparison operator.
5992 @param op The candidate comparison operator.
5993 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
5995 We don't validate the operator for real. We just make sure that it doesn't contain
5996 any semicolons or white space (with special exceptions for a few specific operators).
5997 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
5998 space but it's still not a valid operator, then the database will complain.
6000 Another approach would be to compare the string against a short list of approved operators.
6001 We don't do that because we want to allow custom operators like ">100*", which at this
6002 writing would be difficult or impossible to express otherwise in a JSON query.
6004 static int is_good_operator( const char* op ) {
6005 if( !op ) return 0; // Sanity check
6009 if( isspace( (unsigned char) *s ) ) {
6010 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6011 // and IS NOT DISTINCT FROM.
6012 if( !strcasecmp( op, "similar to" ) )
6014 else if( !strcasecmp( op, "is distinct from" ) )
6016 else if( !strcasecmp( op, "is not distinct from" ) )
6021 else if( ';' == *s )
6029 @name Query Frame Management
6031 The following machinery supports a stack of query frames for use by SELECT().
6033 A query frame caches information about one level of a SELECT query. When we enter
6034 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6036 The query frame stores information about the core class, and about any joined classes
6039 The main purpose is to map table aliases to classes and tables, so that a query can
6040 join to the same table more than once. A secondary goal is to reduce the number of
6041 lookups in the IDL by caching the results.
6045 #define STATIC_CLASS_INFO_COUNT 3
6047 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6050 @brief Allocate a ClassInfo as raw memory.
6051 @return Pointer to the newly allocated ClassInfo.
6053 Except for the in_use flag, which is used only by the allocation and deallocation
6054 logic, we don't initialize the ClassInfo here.
6056 static ClassInfo* allocate_class_info( void ) {
6057 // In order to reduce the number of mallocs and frees, we return a static
6058 // instance of ClassInfo, if we can find one that we're not already using.
6059 // We rely on the fact that the compiler will implicitly initialize the
6060 // static instances so that in_use == 0.
6063 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6064 if( ! static_class_info[ i ].in_use ) {
6065 static_class_info[ i ].in_use = 1;
6066 return static_class_info + i;
6070 // The static ones are all in use. Malloc one.
6072 return safe_malloc( sizeof( ClassInfo ) );
6076 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6077 @param info Pointer to the ClassInfo to be cleared.
6079 static void clear_class_info( ClassInfo* info ) {
6084 // Free any malloc'd strings
6086 if( info->alias != info->alias_store )
6087 free( info->alias );
6089 if( info->class_name != info->class_name_store )
6090 free( info->class_name );
6092 free( info->source_def );
6094 info->alias = info->class_name = info->source_def = NULL;
6099 @brief Free a ClassInfo and everything it owns.
6100 @param info Pointer to the ClassInfo to be freed.
6102 static void free_class_info( ClassInfo* info ) {
6107 clear_class_info( info );
6109 // If it's one of the static instances, just mark it as not in use
6112 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6113 if( info == static_class_info + i ) {
6114 static_class_info[ i ].in_use = 0;
6119 // Otherwise it must have been malloc'd, so free it
6125 @brief Populate an already-allocated ClassInfo.
6126 @param info Pointer to the ClassInfo to be populated.
6127 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6129 @param class Name of the class.
6130 @return Zero if successful, or 1 if not.
6132 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6133 the relevant portions of the IDL for the specified class.
6135 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6138 osrfLogError( OSRF_LOG_MARK,
6139 "%s ERROR: No ClassInfo available to populate", modulename );
6140 info->alias = info->class_name = info->source_def = NULL;
6141 info->class_def = info->fields = info->links = NULL;
6146 osrfLogError( OSRF_LOG_MARK,
6147 "%s ERROR: No class name provided for lookup", modulename );
6148 info->alias = info->class_name = info->source_def = NULL;
6149 info->class_def = info->fields = info->links = NULL;
6153 // Alias defaults to class name if not supplied
6154 if( ! alias || ! alias[ 0 ] )
6157 // Look up class info in the IDL
6158 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6160 osrfLogError( OSRF_LOG_MARK,
6161 "%s ERROR: Class %s not defined in IDL", modulename, class );
6162 info->alias = info->class_name = info->source_def = NULL;
6163 info->class_def = info->fields = info->links = NULL;
6165 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6166 osrfLogError( OSRF_LOG_MARK,
6167 "%s ERROR: Class %s is defined as virtual", modulename, class );
6168 info->alias = info->class_name = info->source_def = NULL;
6169 info->class_def = info->fields = info->links = NULL;
6173 osrfHash* links = osrfHashGet( class_def, "links" );
6175 osrfLogError( OSRF_LOG_MARK,
6176 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6177 info->alias = info->class_name = info->source_def = NULL;
6178 info->class_def = info->fields = info->links = NULL;
6182 osrfHash* fields = osrfHashGet( class_def, "fields" );
6184 osrfLogError( OSRF_LOG_MARK,
6185 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6186 info->alias = info->class_name = info->source_def = NULL;
6187 info->class_def = info->fields = info->links = NULL;
6191 char* source_def = getRelation( class_def );
6195 // We got everything we need, so populate the ClassInfo
6196 if( strlen( alias ) > ALIAS_STORE_SIZE )
6197 info->alias = strdup( alias );
6199 strcpy( info->alias_store, alias );
6200 info->alias = info->alias_store;
6203 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6204 info->class_name = strdup( class );
6206 strcpy( info->class_name_store, class );
6207 info->class_name = info->class_name_store;
6210 info->source_def = source_def;
6212 info->class_def = class_def;
6213 info->links = links;
6214 info->fields = fields;
6219 #define STATIC_FRAME_COUNT 3
6221 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6224 @brief Allocate a QueryFrame as raw memory.
6225 @return Pointer to the newly allocated QueryFrame.
6227 Except for the in_use flag, which is used only by the allocation and deallocation
6228 logic, we don't initialize the QueryFrame here.
6230 static QueryFrame* allocate_frame( void ) {
6231 // In order to reduce the number of mallocs and frees, we return a static
6232 // instance of QueryFrame, if we can find one that we're not already using.
6233 // We rely on the fact that the compiler will implicitly initialize the
6234 // static instances so that in_use == 0.
6237 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6238 if( ! static_frame[ i ].in_use ) {
6239 static_frame[ i ].in_use = 1;
6240 return static_frame + i;
6244 // The static ones are all in use. Malloc one.
6246 return safe_malloc( sizeof( QueryFrame ) );
6250 @brief Free a QueryFrame, and all the memory it owns.
6251 @param frame Pointer to the QueryFrame to be freed.
6253 static void free_query_frame( QueryFrame* frame ) {
6258 clear_class_info( &frame->core );
6260 // Free the join list
6262 ClassInfo* info = frame->join_list;
6265 free_class_info( info );
6269 frame->join_list = NULL;
6272 // If the frame is a static instance, just mark it as unused
6274 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6275 if( frame == static_frame + i ) {
6276 static_frame[ i ].in_use = 0;
6281 // Otherwise it must have been malloc'd, so free it
6287 @brief Search a given QueryFrame for a specified alias.
6288 @param frame Pointer to the QueryFrame to be searched.
6289 @param target The alias for which to search.
6290 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6292 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6293 if( ! frame || ! target ) {
6297 ClassInfo* found_class = NULL;
6299 if( !strcmp( target, frame->core.alias ) )
6300 return &(frame->core);
6302 ClassInfo* curr_class = frame->join_list;
6303 while( curr_class ) {
6304 if( strcmp( target, curr_class->alias ) )
6305 curr_class = curr_class->next;
6307 found_class = curr_class;
6317 @brief Push a new (blank) QueryFrame onto the stack.
6319 static void push_query_frame( void ) {
6320 QueryFrame* frame = allocate_frame();
6321 frame->join_list = NULL;
6322 frame->next = curr_query;
6324 // Initialize the ClassInfo for the core class
6325 ClassInfo* core = &frame->core;
6326 core->alias = core->class_name = core->source_def = NULL;
6327 core->class_def = core->fields = core->links = NULL;
6333 @brief Pop a QueryFrame off the stack and destroy it.
6335 static void pop_query_frame( void ) {
6340 QueryFrame* popped = curr_query;
6341 curr_query = popped->next;
6343 free_query_frame( popped );
6347 @brief Populate the ClassInfo for the core class.
6348 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6349 class name as an alias.
6350 @param class_name Name of the core class.
6351 @return Zero if successful, or 1 if not.
6353 Populate the ClassInfo of the core class with copies of the alias and class name, and
6354 with pointers to the relevant portions of the IDL for the core class.
6356 static int add_query_core( const char* alias, const char* class_name ) {
6359 if( ! curr_query ) {
6360 osrfLogError( OSRF_LOG_MARK,
6361 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6363 } else if( curr_query->core.alias ) {
6364 osrfLogError( OSRF_LOG_MARK,
6365 "%s ERROR: Core class %s already populated as %s",
6366 modulename, curr_query->core.class_name, curr_query->core.alias );
6370 build_class_info( &curr_query->core, alias, class_name );
6371 if( curr_query->core.alias )
6374 osrfLogError( OSRF_LOG_MARK,
6375 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6381 @brief Search the current QueryFrame for a specified alias.
6382 @param target The alias for which to search.
6383 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6385 static inline ClassInfo* search_alias( const char* target ) {
6386 return search_alias_in_frame( curr_query, target );
6390 @brief Search all levels of query for a specified alias, starting with the current query.
6391 @param target The alias for which to search.
6392 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6394 static ClassInfo* search_all_alias( const char* target ) {
6395 ClassInfo* found_class = NULL;
6396 QueryFrame* curr_frame = curr_query;
6398 while( curr_frame ) {
6399 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6402 curr_frame = curr_frame->next;
6409 @brief Add a class to the list of classes joined to the current query.
6410 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6411 the class name as an alias.
6412 @param classname The name of the class to be added.
6413 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6415 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6417 if( ! classname || ! *classname ) { // sanity check
6418 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6425 const ClassInfo* conflict = search_alias( alias );
6427 osrfLogError( OSRF_LOG_MARK,
6428 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6429 modulename, alias, conflict->class_name );
6433 ClassInfo* info = allocate_class_info();
6435 if( build_class_info( info, alias, classname ) ) {
6436 free_class_info( info );
6440 // Add the new ClassInfo to the join list of the current QueryFrame
6441 info->next = curr_query->join_list;
6442 curr_query->join_list = info;
6448 @brief Destroy all nodes on the query stack.
6450 static void clear_query_stack( void ) {