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 Connect to the database.
142 @return A database connection if successful, or NULL if not.
144 dbi_conn oilsConnectDB( const char* mod_name ) {
146 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
147 if( dbi_initialize( NULL ) == -1 ) {
148 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
151 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
153 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
154 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
155 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
156 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
157 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
158 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
160 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
161 dbi_conn handle = dbi_conn_new( driver );
164 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
167 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
169 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
170 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
172 if( host ) dbi_conn_set_option( handle, "host", host );
173 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
174 if( user ) dbi_conn_set_option( handle, "username", user );
175 if( pw ) dbi_conn_set_option( handle, "password", pw );
176 if( db ) dbi_conn_set_option( handle, "dbname", db );
184 if( dbi_conn_connect( handle ) < 0 ) {
186 if( dbi_conn_connect( handle ) < 0 ) {
188 dbi_conn_error( handle, &errmsg );
189 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", errmsg );
194 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
200 @brief Select some options.
201 @param module_name: Name of the server.
202 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
204 This source file is used (at this writing) to implement three different servers:
205 - open-ils.reporter-store
209 These servers behave mostly the same, but they implement different combinations of
210 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
212 Here we use the server name in messages to identify which kind of server issued them.
213 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
215 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
217 module_name = "open-ils.cstore"; // bulletproofing with a default
222 modulename = strdup( module_name );
223 enforce_pcrud = do_pcrud;
224 max_flesh_depth = flesh_depth;
228 @brief Install a database connection.
229 @param conn Pointer to a database connection.
231 In some contexts, @a conn may merely provide a driver so that we can process strings
232 properly, without providing an open database connection.
234 void oilsSetDBConnection( dbi_conn conn ) {
235 dbhandle = writehandle = conn;
239 @brief Get a table name, view name, or subquery for use in a FROM clause.
240 @param class Pointer to the IDL class entry.
241 @return A table name, a view name, or a subquery in parentheses.
243 In some cases the IDL defines a class, not with a table name or a view name, but with
244 a SELECT statement, which may be used as a subquery.
246 static char* getRelation( osrfHash* class ) {
248 char* source_def = NULL;
249 const char* tabledef = osrfHashGet( class, "tablename" );
252 source_def = strdup( tabledef ); // Return the name of a table or view
254 tabledef = osrfHashGet( class, "source_definition" );
256 // Return a subquery, enclosed in parentheses
257 source_def = safe_malloc( strlen( tabledef ) + 3 );
258 source_def[ 0 ] = '(';
259 strcpy( source_def + 1, tabledef );
260 strcat( source_def, ")" );
262 // Not found: return an error
263 const char* classname = osrfHashGet( class, "classname" );
268 "%s ERROR No tablename or source_definition for class \"%s\"",
279 @brief Add datatypes from the database to the fields in the IDL.
280 @return Zero if successful, or 1 upon error.
282 For each relevant class in the IDL: ask the database for the datatype of every field.
283 In particular, determine which fields are text fields and which fields are numeric
284 fields, so that we know whether to enclose their values in quotes.
286 At this writing this function does not detect any errors, so it always returns zero.
288 int oilsExtendIDL( void ) {
289 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
290 osrfHash* class = NULL;
291 growing_buffer* query_buf = buffer_init( 64 );
293 // For each class in the IDL...
294 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
295 const char* classname = osrfHashIteratorKey( class_itr );
296 osrfHash* fields = osrfHashGet( class, "fields" );
298 // If the class is virtual, ignore it
299 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
300 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
304 char* tabledef = getRelation( class );
306 continue; // No such relation -- a query of it would be doomed to failure
308 buffer_reset( query_buf );
309 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
313 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
314 modulename, OSRF_BUFFER_C_STR( query_buf ) );
316 dbi_result result = dbi_conn_query( writehandle, OSRF_BUFFER_C_STR( query_buf ) );
320 const char* columnName;
321 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
323 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
326 /* fetch the fieldmapper index */
327 osrfHash* _f = osrfHashGet(fields, columnName);
330 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
332 /* determine the field type and storage attributes */
334 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
336 case DBI_TYPE_INTEGER : {
338 if( !osrfHashGet(_f, "primitive") )
339 osrfHashSet(_f, "number", "primitive");
341 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
342 if( attr & DBI_INTEGER_SIZE8 )
343 osrfHashSet( _f, "INT8", "datatype" );
345 osrfHashSet( _f, "INT", "datatype" );
348 case DBI_TYPE_DECIMAL :
349 if( !osrfHashGet( _f, "primitive" ))
350 osrfHashSet( _f, "number", "primitive" );
352 osrfHashSet( _f, "NUMERIC", "datatype" );
355 case DBI_TYPE_STRING :
356 if( !osrfHashGet( _f, "primitive" ))
357 osrfHashSet( _f, "string", "primitive" );
359 osrfHashSet( _f,"TEXT", "datatype" );
362 case DBI_TYPE_DATETIME :
363 if( !osrfHashGet( _f, "primitive" ))
364 osrfHashSet( _f, "string", "primitive" );
366 osrfHashSet( _f, "TIMESTAMP", "datatype" );
369 case DBI_TYPE_BINARY :
370 if( !osrfHashGet( _f, "primitive" ))
371 osrfHashSet( _f, "string", "primitive" );
373 osrfHashSet( _f, "BYTEA", "datatype" );
378 "Setting [%s] to primitive [%s] and datatype [%s]...",
380 osrfHashGet( _f, "primitive" ),
381 osrfHashGet( _f, "datatype" )
385 } // end while loop for traversing columns of result
386 dbi_result_free( result );
388 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]...", classname );
390 } // end for each class in IDL
392 buffer_free( query_buf );
393 osrfHashIteratorFree( class_itr );
394 child_initialized = 1;
399 @brief Free an osrfHash that stores a transaction ID.
400 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
402 This function is a callback, to be called by the application session when it ends.
403 The application session stores the osrfHash via an opaque pointer.
405 If the osrfHash contains an entry for the key "xact_id", it means that an
406 uncommitted transaction is pending. Roll it back.
408 void userDataFree( void* blob ) {
409 osrfHash* hash = (osrfHash*) blob;
410 if( osrfHashGet( hash, "xact_id" ) && writehandle )
411 dbi_conn_query( writehandle, "ROLLBACK;" );
413 osrfHashFree( hash );
417 @name Managing session data
418 @brief Maintain data stored via the userData pointer of the application session.
420 Currently, session-level data is stored in an osrfHash. Other arrangements are
421 possible, and some would be more efficient. The application session calls a
422 callback function to free userData before terminating.
424 Currently, the only data we store at the session level is the transaction id. By this
425 means we can ensure that any pending transactions are rolled back before the application
431 @brief Free an item in the application session's userData.
432 @param key The name of a key for an osrfHash.
433 @param item An opaque pointer to the item associated with the key.
435 We store an osrfHash as userData with the application session, and arrange (by
436 installing userDataFree() as a different callback) for the session to free that
437 osrfHash before terminating.
439 This function is a callback for freeing items in the osrfHash. Currently we store
441 - Transaction id of a pending transaction; a character string. Key: "xact_id".
442 - Authkey; a character string. Key: "authkey".
443 - User object from the authentication server; a jsonObject. Key: "user_login".
445 If we ever store anything else in userData, we will need to revisit this function so
446 that it will free whatever else needs freeing.
448 static void sessionDataFree( char* key, void* item ) {
449 if( !strcmp( key, "xact_id" )
450 || !strcmp( key, "authkey" ) ) {
452 } else if( !strcmp( key, "user_login" ) )
453 jsonObjectFree( (jsonObject*) item );
457 @brief Save a transaction id.
458 @param ctx Pointer to the method context.
460 Save the session_id of the current application session as a transaction id.
462 static void setXactId( osrfMethodContext* ctx ) {
463 if( ctx && ctx->session ) {
464 osrfAppSession* session = ctx->session;
466 osrfHash* cache = session->userData;
468 // If the session doesn't already have a hash, create one. Make sure
469 // that the application session frees the hash when it terminates.
470 if( NULL == cache ) {
471 session->userData = cache = osrfNewHash();
472 osrfHashSetCallback( cache, &sessionDataFree );
473 ctx->session->userDataFree = &userDataFree;
476 // Save the transaction id in the hash, with the key "xact_id"
477 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
482 @brief Get the transaction ID for the current transaction, if any.
483 @param ctx Pointer to the method context.
484 @return Pointer to the transaction ID.
486 The return value points to an internal buffer, and will become invalid upon issuing
487 a commit or rollback.
489 static inline const char* getXactId( osrfMethodContext* ctx ) {
490 if( ctx && ctx->session && ctx->session->userData )
491 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
497 @brief Clear the current transaction id.
498 @param ctx Pointer to the method context.
500 static inline void clearXactId( osrfMethodContext* ctx ) {
501 if( ctx && ctx->session && ctx->session->userData )
502 osrfHashRemove( ctx->session->userData, "xact_id" );
507 @brief Save the user's login in the userData for the current application session.
508 @param ctx Pointer to the method context.
509 @param user_login Pointer to the user login object to be cached (we cache the original,
512 If @a user_login is NULL, remove the user login if one is already cached.
514 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
515 if( ctx && ctx->session ) {
516 osrfAppSession* session = ctx->session;
518 osrfHash* cache = session->userData;
520 // If the session doesn't already have a hash, create one. Make sure
521 // that the application session frees the hash when it terminates.
522 if( NULL == cache ) {
523 session->userData = cache = osrfNewHash();
524 osrfHashSetCallback( cache, &sessionDataFree );
525 ctx->session->userDataFree = &userDataFree;
529 osrfHashSet( cache, user_login, "user_login" );
531 osrfHashRemove( cache, "user_login" );
536 @brief Get the user login object for the current application session, if any.
537 @param ctx Pointer to the method context.
538 @return Pointer to the user login object if found; otherwise NULL.
540 The user login object was returned from the authentication server, and then cached so
541 we don't have to call the authentication server again for the same user.
543 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
544 if( ctx && ctx->session && ctx->session->userData )
545 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
551 @brief Save a copy of an authkey in the userData of the current application session.
552 @param ctx Pointer to the method context.
553 @param authkey The authkey to be saved.
555 If @a authkey is NULL, remove the authkey if one is already cached.
557 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
558 if( ctx && ctx->session && authkey ) {
559 osrfAppSession* session = ctx->session;
560 osrfHash* cache = session->userData;
562 // If the session doesn't already have a hash, create one. Make sure
563 // that the application session frees the hash when it terminates.
564 if( NULL == cache ) {
565 session->userData = cache = osrfNewHash();
566 osrfHashSetCallback( cache, &sessionDataFree );
567 ctx->session->userDataFree = &userDataFree;
570 // Save the transaction id in the hash, with the key "xact_id"
571 if( authkey && *authkey )
572 osrfHashSet( cache, strdup( authkey ), "authkey" );
574 osrfHashRemove( cache, "authkey" );
579 @brief Reset the login timeout.
580 @param authkey The authentication key for the current login session.
581 @param now The current time.
582 @return Zero if successful, or 1 if not.
584 Tell the authentication server to reset the timeout so that the login session won't
585 expire for a while longer.
587 We could dispense with the @a now parameter by calling time(). But we just called
588 time() in order to decide whether to reset the timeout, so we might as well reuse
589 the result instead of calling time() again.
591 static int reset_timeout( const char* authkey, time_t now ) {
592 jsonObject* auth_object = jsonNewObject( authkey );
594 // Ask the authentication server to reset the timeout. It returns an event
595 // indicating success or failure.
596 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
597 "open-ils.auth.session.reset_timeout", auth_object );
598 jsonObjectFree( auth_object );
600 if( !result || result->type != JSON_HASH ) {
601 osrfLogError( OSRF_LOG_MARK,
602 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
603 jsonObjectFree( result );
604 return 1; // Not the right sort of object returned
607 const jsonObject* ilsevent = jsonObjectGetKey( result, "ilsevent" );
608 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
609 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
610 jsonObjectFree( result );
611 return 1; // Return code from method not available
614 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
615 const char* desc = jsonObjectGetString( jsonObjectGetKey( result, "desc" ) );
617 desc = "(No reason available)"; // failsafe; shouldn't happen
618 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
619 jsonObjectFree( result );
623 // Revise our local proxy for the timeout deadline
624 // by a smallish fraction of the timeout interval
625 const char* timeout = jsonObjectGetString( jsonObjectGetKey( result, "payload" ) );
627 timeout = "1"; // failsafe; shouldn't happen
628 time_next_reset = now + atoi( timeout ) / 15;
630 jsonObjectFree( result );
631 return 0; // Successfully reset timeout
635 @brief Get the authkey string for the current application session, if any.
636 @param ctx Pointer to the method context.
637 @return Pointer to the cached authkey if found; otherwise NULL.
639 If present, the authkey string was cached from a previous method call.
641 static const char* getAuthkey( osrfMethodContext* ctx ) {
642 if( ctx && ctx->session && ctx->session->userData ) {
643 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
645 // Possibly reset the authentication timeout to keep the login alive. We do so
646 // no more than once per method call, and not at all if it has been only a short
647 // time since the last reset.
649 // Here we reset explicitly, if at all. We also implicitly reset the timeout
650 // whenever we call the "open-ils.auth.session.retrieve" method.
651 if( timeout_needs_resetting ) {
652 time_t now = time( NULL );
653 if( now >= time_next_reset && reset_timeout( authkey, now ) )
654 authkey = NULL; // timeout has apparently expired already
657 timeout_needs_resetting = 0;
665 @brief Implement the transaction.begin method.
666 @param ctx Pointer to the method context.
667 @return Zero if successful, or -1 upon error.
669 Start a transaction. Save a transaction ID for future reference.
672 - authkey (PCRUD only)
674 Return to client: Transaction ID
676 int beginTransaction( osrfMethodContext* ctx ) {
677 if(osrfMethodVerifyContext( ctx )) {
678 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
682 if( enforce_pcrud ) {
683 timeout_needs_resetting = 1;
684 const jsonObject* user = verifyUserPCRUD( ctx );
689 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
691 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction", modulename );
692 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
693 "osrfMethodException", ctx->request, "Error starting transaction" );
697 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
698 osrfAppRespondComplete( ctx, ret );
699 jsonObjectFree( ret );
705 @brief Implement the savepoint.set method.
706 @param ctx Pointer to the method context.
707 @return Zero if successful, or -1 if not.
709 Issue a SAVEPOINT to the database server.
712 - authkey (PCRUD only)
715 Return to client: Savepoint name
717 int setSavepoint( osrfMethodContext* ctx ) {
718 if(osrfMethodVerifyContext( ctx )) {
719 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
724 if( enforce_pcrud ) {
726 timeout_needs_resetting = 1;
727 const jsonObject* user = verifyUserPCRUD( ctx );
732 // Verify that a transaction is pending
733 const char* trans_id = getXactId( ctx );
734 if( NULL == trans_id ) {
735 osrfAppSessionStatus(
737 OSRF_STATUS_INTERNALSERVERERROR,
738 "osrfMethodException",
740 "No active transaction -- required for savepoints"
745 // Get the savepoint name from the method params
746 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
748 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
752 "%s: Error creating savepoint %s in transaction %s",
757 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
758 "osrfMethodException", ctx->request, "Error creating savepoint" );
761 jsonObject* ret = jsonNewObject( spName );
762 osrfAppRespondComplete( ctx, ret );
763 jsonObjectFree( ret );
769 @brief Implement the savepoint.release method.
770 @param ctx Pointer to the method context.
771 @return Zero if successful, or -1 if not.
773 Issue a RELEASE SAVEPOINT to the database server.
776 - authkey (PCRUD only)
779 Return to client: Savepoint name
781 int releaseSavepoint( osrfMethodContext* ctx ) {
782 if(osrfMethodVerifyContext( ctx )) {
783 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
788 if( enforce_pcrud ) {
790 timeout_needs_resetting = 1;
791 const jsonObject* user = verifyUserPCRUD( ctx );
796 // Verify that a transaction is pending
797 const char* trans_id = getXactId( ctx );
798 if( NULL == trans_id ) {
799 osrfAppSessionStatus(
801 OSRF_STATUS_INTERNALSERVERERROR,
802 "osrfMethodException",
804 "No active transaction -- required for savepoints"
809 // Get the savepoint name from the method params
810 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
812 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
816 "%s: Error releasing savepoint %s in transaction %s",
821 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
822 "osrfMethodException", ctx->request, "Error releasing savepoint" );
825 jsonObject* ret = jsonNewObject( spName );
826 osrfAppRespondComplete( ctx, ret );
827 jsonObjectFree( ret );
833 @brief Implement the savepoint.rollback method.
834 @param ctx Pointer to the method context.
835 @return Zero if successful, or -1 if not.
837 Issue a ROLLBACK TO SAVEPOINT to the database server.
840 - authkey (PCRUD only)
843 Return to client: Savepoint name
845 int rollbackSavepoint( osrfMethodContext* ctx ) {
846 if(osrfMethodVerifyContext( ctx )) {
847 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
852 if( enforce_pcrud ) {
854 timeout_needs_resetting = 1;
855 const jsonObject* user = verifyUserPCRUD( ctx );
860 // Verify that a transaction is pending
861 const char* trans_id = getXactId( ctx );
862 if( NULL == trans_id ) {
863 osrfAppSessionStatus(
865 OSRF_STATUS_INTERNALSERVERERROR,
866 "osrfMethodException",
868 "No active transaction -- required for savepoints"
873 // Get the savepoint name from the method params
874 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
876 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
880 "%s: Error rolling back savepoint %s in transaction %s",
885 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
886 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
889 jsonObject* ret = jsonNewObject( spName );
890 osrfAppRespondComplete( ctx, ret );
891 jsonObjectFree( ret );
897 @brief Implement the transaction.commit method.
898 @param ctx Pointer to the method context.
899 @return Zero if successful, or -1 if not.
901 Issue a COMMIT to the database server.
904 - authkey (PCRUD only)
906 Return to client: Transaction ID.
908 int commitTransaction( osrfMethodContext* ctx ) {
909 if(osrfMethodVerifyContext( ctx )) {
910 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
914 if( enforce_pcrud ) {
915 timeout_needs_resetting = 1;
916 const jsonObject* user = verifyUserPCRUD( ctx );
921 // Verify that a transaction is pending
922 const char* trans_id = getXactId( ctx );
923 if( NULL == trans_id ) {
924 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
925 "osrfMethodException", ctx->request, "No active transaction to commit" );
929 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
931 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction", modulename );
932 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
933 "osrfMethodException", ctx->request, "Error committing transaction" );
936 jsonObject* ret = jsonNewObject( trans_id );
937 osrfAppRespondComplete( ctx, ret );
938 jsonObjectFree( ret );
945 @brief Implement the transaction.rollback method.
946 @param ctx Pointer to the method context.
947 @return Zero if successful, or -1 if not.
949 Issue a ROLLBACK to the database server.
952 - authkey (PCRUD only)
954 Return to client: Transaction ID
956 int rollbackTransaction( osrfMethodContext* ctx ) {
957 if( osrfMethodVerifyContext( ctx )) {
958 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
962 if( enforce_pcrud ) {
963 timeout_needs_resetting = 1;
964 const jsonObject* user = verifyUserPCRUD( ctx );
969 // Verify that a transaction is pending
970 const char* trans_id = getXactId( ctx );
971 if( NULL == trans_id ) {
972 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
973 "osrfMethodException", ctx->request, "No active transaction to roll back" );
977 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
979 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction", modulename );
980 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
981 "osrfMethodException", ctx->request, "Error rolling back transaction" );
984 jsonObject* ret = jsonNewObject( trans_id );
985 osrfAppRespondComplete( ctx, ret );
986 jsonObjectFree( ret );
993 @brief Implement the "search" method.
994 @param ctx Pointer to the method context.
995 @return Zero if successful, or -1 if not.
998 - authkey (PCRUD only)
999 - WHERE clause, as jsonObject
1000 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1002 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1003 Optionally flesh linked fields.
1005 int doSearch( osrfMethodContext* ctx ) {
1006 if( osrfMethodVerifyContext( ctx )) {
1007 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1012 timeout_needs_resetting = 1;
1014 jsonObject* where_clause;
1015 jsonObject* rest_of_query;
1017 if( enforce_pcrud ) {
1018 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1019 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1021 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1022 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1025 // Get the class metadata
1026 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1027 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1031 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1033 osrfAppRespondComplete( ctx, NULL );
1037 // Return each row to the client (except that some may be suppressed by PCRUD)
1038 jsonObject* cur = 0;
1039 unsigned long res_idx = 0;
1040 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1041 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1043 osrfAppRespond( ctx, cur );
1045 jsonObjectFree( obj );
1047 osrfAppRespondComplete( ctx, NULL );
1052 @brief Implement the "id_list" method.
1053 @param ctx Pointer to the method context.
1054 @param err Pointer through which to return an error code.
1055 @return Zero if successful, or -1 if not.
1058 - authkey (PCRUD only)
1059 - WHERE clause, as jsonObject
1060 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1062 Return to client: The primary key values for all rows of the relevant class that
1063 satisfy a specified WHERE clause.
1065 This method relies on the assumption that every class has a primary key consisting of
1068 int doIdList( osrfMethodContext* ctx ) {
1069 if( osrfMethodVerifyContext( ctx )) {
1070 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1075 timeout_needs_resetting = 1;
1077 jsonObject* where_clause;
1078 jsonObject* rest_of_query;
1080 // We use the where clause without change. But we need to massage the rest of the
1081 // query, so we work with a copy of it instead of modifying the original.
1083 if( enforce_pcrud ) {
1084 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1085 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1087 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1088 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1091 // Eliminate certain SQL clauses, if present.
1092 if( rest_of_query ) {
1093 jsonObjectRemoveKey( rest_of_query, "select" );
1094 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1095 jsonObjectRemoveKey( rest_of_query, "flesh" );
1096 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
1098 rest_of_query = jsonNewObjectType( JSON_HASH );
1101 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1103 // Get the class metadata
1104 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1105 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1107 // Build a SELECT list containing just the primary key,
1108 // i.e. like { "classname":["keyname"] }
1109 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1111 // Load array with name of primary key
1112 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1113 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1114 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1116 jsonObjectSetKey( rest_of_query, "select", select_clause );
1121 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1123 jsonObjectFree( rest_of_query );
1125 osrfAppRespondComplete( ctx, NULL );
1129 // Return each primary key value to the client
1131 unsigned long res_idx = 0;
1132 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1133 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1134 continue; // Suppress due to lack of permission
1136 osrfAppRespond( ctx,
1137 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1140 jsonObjectFree( obj );
1141 osrfAppRespondComplete( ctx, NULL );
1146 @brief Verify that we have a valid class reference.
1147 @param ctx Pointer to the method context.
1148 @param param Pointer to the method parameters.
1149 @return 1 if the class reference is valid, or zero if it isn't.
1151 The class of the method params must match the class to which the method id devoted.
1152 For PCRUD there are additional restrictions.
1154 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1156 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1157 osrfHash* class = osrfHashGet( method_meta, "class" );
1159 // Compare the method's class to the parameters' class
1160 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1162 // Oops -- they don't match. Complain.
1163 growing_buffer* msg = buffer_init( 128 );
1166 "%s: %s method for type %s was passed a %s",
1168 osrfHashGet( method_meta, "methodtype" ),
1169 osrfHashGet( class, "classname" ),
1170 param->classname ? param->classname : "(null)"
1173 char* m = buffer_release( msg );
1174 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1182 return verifyObjectPCRUD( ctx, param );
1188 @brief (PCRUD only) Verify that the user is properly logged in.
1189 @param ctx Pointer to the method context.
1190 @return If the user is logged in, a pointer to the user object from the authentication
1191 server; otherwise NULL.
1193 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1195 // Get the authkey (the first method parameter)
1196 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1198 // See if we have the same authkey, and a user object,
1199 // locally cached from a previous call
1200 const char* cached_authkey = getAuthkey( ctx );
1201 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1202 const jsonObject* cached_user = getUserLogin( ctx );
1207 // We have no matching authentication data in the cache. Authenticate from scratch.
1208 jsonObject* auth_object = jsonNewObject( auth );
1210 // Fetch the user object from the authentication server
1211 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1213 jsonObjectFree( auth_object );
1215 if( !user->classname || strcmp(user->classname, "au" )) {
1217 growing_buffer* msg = buffer_init( 128 );
1220 "%s: permacrud received a bad auth token: %s",
1225 char* m = buffer_release( msg );
1226 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1230 jsonObjectFree( user );
1234 setUserLogin( ctx, user );
1235 setAuthkey( ctx, auth );
1237 // Allow ourselves up to a second before we have to reset the login timeout.
1238 // It would be nice to use some fraction of the timeout interval enforced by the
1239 // authentication server, but that value is not readily available at this point.
1240 // Instead, we use a conservative default interval.
1241 time_next_reset = time( NULL ) + 1;
1246 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1248 dbhandle = writehandle;
1250 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1251 osrfHash* class = osrfHashGet( method_metadata, "class" );
1252 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1255 if( *method_type == 's' || *method_type == 'i' ) {
1256 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1257 } else if( *method_type == 'u' || *method_type == 'd' ) {
1258 fetch = 1; // MUST go to the db for the object for update and delete
1261 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1264 // No permacrud for this method type on this class
1266 growing_buffer* msg = buffer_init( 128 );
1269 "%s: %s on class %s has no permacrud IDL entry",
1271 osrfHashGet( method_metadata, "methodtype" ),
1272 osrfHashGet( class, "classname" )
1275 char* m = buffer_release( msg );
1276 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1277 "osrfMethodException", ctx->request, m );
1284 const jsonObject* user = verifyUserPCRUD( ctx );
1288 int userid = atoi( oilsFMGetString( user, "id" ) );
1290 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1291 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1292 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1294 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1297 char* pkey_value = NULL;
1298 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1299 osrfLogDebug( OSRF_LOG_MARK,
1300 "global-level permissions required, fetching top of the org tree" );
1302 // check for perm at top of org tree
1303 char* org_tree_root_id = org_tree_root( ctx );
1304 if( org_tree_root_id ) {
1305 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1306 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1308 osrfStringArrayFree( context_org_array );
1313 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1314 "fetching context org ids" );
1315 const char* pkey = osrfHashGet( class, "primarykey" );
1316 jsonObject *param = NULL;
1318 if( obj->classname ) {
1319 pkey_value = oilsFMGetString( obj, pkey );
1321 param = jsonObjectClone( obj );
1322 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1325 pkey_value = jsonObjectToSimpleString( obj );
1327 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1328 "of %s and retrieving from the database", pkey_value );
1332 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1333 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1334 jsonObjectFree( _tmp_params );
1336 param = jsonObjectExtractIndex( _list, 0 );
1337 jsonObjectFree( _list );
1341 osrfLogDebug( OSRF_LOG_MARK,
1342 "Object not found in the database with primary key %s of %s",
1345 growing_buffer* msg = buffer_init( 128 );
1348 "%s: no object found with primary key %s of %s",
1354 char* m = buffer_release( msg );
1355 osrfAppSessionStatus(
1357 OSRF_STATUS_INTERNALSERVERERROR,
1358 "osrfMethodException",
1370 if( local_context->size > 0 ) {
1371 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1372 local_context->size );
1374 const char* lcontext = NULL;
1375 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1376 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1379 "adding class-local field %s (value: %s) to the context org list",
1381 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1386 if( foreign_context ) {
1387 unsigned long class_count = osrfHashGetCount( foreign_context );
1388 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1390 if( class_count > 0 ) {
1392 osrfHash* fcontext = NULL;
1393 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1394 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1395 const char* class_name = osrfHashIteratorKey( class_itr );
1396 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1400 "%d foreign context fields(s) specified for class %s",
1401 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1405 char* foreign_pkey = osrfHashGet( fcontext, "field" );
1406 char* foreign_pkey_value =
1407 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1409 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1411 jsonObject* _list = doFieldmapperSearch(
1412 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1414 jsonObject* _fparam = jsonObjectClone( jsonObjectGetIndex( _list, 0 ));
1415 jsonObjectFree( _tmp_params );
1416 jsonObjectFree( _list );
1418 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1420 if( _fparam && jump_list ) {
1421 const char* flink = NULL;
1423 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1424 free( foreign_pkey_value );
1426 osrfHash* foreign_link_hash =
1427 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1429 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1430 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1432 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1434 _list = doFieldmapperSearch(
1436 osrfHashGet( oilsIDL(),
1437 osrfHashGet( foreign_link_hash, "class" ) ),
1443 _fparam = jsonObjectClone( jsonObjectGetIndex( _list, 0 ));
1444 jsonObjectFree( _tmp_params );
1445 jsonObjectFree( _list );
1451 growing_buffer* msg = buffer_init( 128 );
1454 "%s: no object found with primary key %s of %s",
1460 char* m = buffer_release( msg );
1461 osrfAppSessionStatus(
1463 OSRF_STATUS_INTERNALSERVERERROR,
1464 "osrfMethodException",
1470 osrfHashIteratorFree( class_itr );
1471 free( foreign_pkey_value );
1472 jsonObjectFree( param );
1477 free( foreign_pkey_value );
1480 const char* foreign_field = NULL;
1481 while ( (foreign_field = osrfStringArrayGetString(
1482 osrfHashGet(fcontext,"context" ), j++ )) ) {
1483 osrfStringArrayAdd( context_org_array,
1484 oilsFMGetString( _fparam, foreign_field ) );
1487 "adding foreign class %s field %s (value: %s) to the context org list",
1490 osrfStringArrayGetString(
1491 context_org_array, context_org_array->size - 1 )
1495 jsonObjectFree( _fparam );
1498 osrfHashIteratorFree( class_itr );
1502 jsonObjectFree( param );
1505 const char* context_org = NULL;
1506 const char* perm = NULL;
1509 if( permission->size == 0 ) {
1510 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1515 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1517 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1523 "Checking object permission [%s] for user %d "
1524 "on object %s (class %s) at org %d",
1528 osrfHashGet( class, "classname" ),
1532 result = dbi_conn_queryf(
1534 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1537 osrfHashGet( class, "classname" ),
1545 "Received a result for object permission [%s] "
1546 "for user %d on object %s (class %s) at org %d",
1550 osrfHashGet( class, "classname" ),
1554 if( dbi_result_first_row( result )) {
1555 jsonObject* return_val = oilsMakeJSONFromResult( result );
1556 const char* has_perm = jsonObjectGetString(
1557 jsonObjectGetKeyConst( return_val, "has_perm" ));
1561 "Status of object permission [%s] for user %d "
1562 "on object %s (class %s) at org %d is %s",
1566 osrfHashGet(class, "classname"),
1571 if( *has_perm == 't' )
1573 jsonObjectFree( return_val );
1576 dbi_result_free( result );
1582 osrfLogDebug( OSRF_LOG_MARK,
1583 "Checking non-object permission [%s] for user %d at org %d",
1584 perm, userid, atoi(context_org) );
1585 result = dbi_conn_queryf(
1587 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1594 osrfLogDebug( OSRF_LOG_MARK,
1595 "Received a result for permission [%s] for user %d at org %d",
1596 perm, userid, atoi( context_org ));
1597 if( dbi_result_first_row( result )) {
1598 jsonObject* return_val = oilsMakeJSONFromResult( result );
1599 const char* has_perm = jsonObjectGetString(
1600 jsonObjectGetKeyConst( return_val, "has_perm" ));
1601 osrfLogDebug( OSRF_LOG_MARK,
1602 "Status of permission [%s] for user %d at org %d is [%s]",
1603 perm, userid, atoi( context_org ), has_perm );
1604 if( *has_perm == 't' )
1606 jsonObjectFree( return_val );
1609 dbi_result_free( result );
1621 osrfStringArrayFree( context_org_array );
1627 @brief Look up the root of the org_unit tree.
1628 @param ctx Pointer to the method context.
1629 @return The id of the root org unit, as a character string.
1631 Query actor.org_unit where parent_ou is null, and return the id as a string.
1633 This function assumes that there is only one root org unit, i.e. that we
1634 have a single tree, not a forest.
1636 The calling code is responsible for freeing the returned string.
1638 static char* org_tree_root( osrfMethodContext* ctx ) {
1640 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1641 static time_t last_lookup_time = 0;
1642 time_t current_time = time( NULL );
1644 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1645 // We successfully looked this up less than an hour ago.
1646 // It's not likely to have changed since then.
1647 return strdup( cached_root_id );
1649 last_lookup_time = current_time;
1652 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1653 jsonObject* result = doFieldmapperSearch(
1654 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1655 jsonObjectFree( where_clause );
1657 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1660 jsonObjectFree( result );
1662 growing_buffer* msg = buffer_init( 128 );
1663 OSRF_BUFFER_ADD( msg, modulename );
1664 OSRF_BUFFER_ADD( msg,
1665 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1667 char* m = buffer_release( msg );
1668 osrfAppSessionStatus( ctx->session,
1669 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1672 cached_root_id[ 0 ] = '\0';
1676 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1677 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1679 jsonObjectFree( result );
1681 strcpy( cached_root_id, root_org_unit_id );
1682 return root_org_unit_id;
1686 @brief Create a JSON_HASH with a single key/value pair.
1687 @param key The key of the key/value pair.
1688 @param value the value of the key/value pair.
1689 @return Pointer to a newly created jsonObject of type JSON_HASH.
1691 The value of the key/value is either a string or (if @a value is NULL) a null.
1693 static jsonObject* single_hash( const char* key, const char* value ) {
1695 if( ! key ) key = "";
1697 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1698 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1703 int doCreate( osrfMethodContext* ctx ) {
1704 if(osrfMethodVerifyContext( ctx )) {
1705 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1710 timeout_needs_resetting = 1;
1712 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1713 jsonObject* target = NULL;
1714 jsonObject* options = NULL;
1716 if( enforce_pcrud ) {
1717 target = jsonObjectGetIndex( ctx->params, 1 );
1718 options = jsonObjectGetIndex( ctx->params, 2 );
1720 target = jsonObjectGetIndex( ctx->params, 0 );
1721 options = jsonObjectGetIndex( ctx->params, 1 );
1724 if( !verifyObjectClass( ctx, target )) {
1725 osrfAppRespondComplete( ctx, NULL );
1729 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1731 const char* trans_id = getXactId( ctx );
1733 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1735 osrfAppSessionStatus(
1737 OSRF_STATUS_BADREQUEST,
1738 "osrfMethodException",
1740 "No active transaction -- required for CREATE"
1742 osrfAppRespondComplete( ctx, NULL );
1746 // The following test is harmless but redundant. If a class is
1747 // readonly, we don't register a create method for it.
1748 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1749 osrfAppSessionStatus(
1751 OSRF_STATUS_BADREQUEST,
1752 "osrfMethodException",
1754 "Cannot INSERT readonly class"
1756 osrfAppRespondComplete( ctx, NULL );
1760 // Set the last_xact_id
1761 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1763 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1764 trans_id, target->classname, index);
1765 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1768 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1770 dbhandle = writehandle;
1772 osrfHash* fields = osrfHashGet( meta, "fields" );
1773 char* pkey = osrfHashGet( meta, "primarykey" );
1774 char* seq = osrfHashGet( meta, "sequence" );
1776 growing_buffer* table_buf = buffer_init( 128 );
1777 growing_buffer* col_buf = buffer_init( 128 );
1778 growing_buffer* val_buf = buffer_init( 128 );
1780 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1781 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1782 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1783 buffer_add( val_buf,"VALUES (" );
1787 osrfHash* field = NULL;
1788 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1789 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1791 const char* field_name = osrfHashIteratorKey( field_itr );
1793 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1796 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1799 if( field_object && field_object->classname ) {
1800 value = oilsFMGetString(
1802 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
1804 } else if( field_object && JSON_BOOL == field_object->type ) {
1805 if( jsonBoolIsTrue( field_object ) )
1806 value = strdup( "t" );
1808 value = strdup( "f" );
1810 value = jsonObjectToSimpleString( field_object );
1816 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1817 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1820 buffer_add( col_buf, field_name );
1822 if( !field_object || field_object->type == JSON_NULL ) {
1823 buffer_add( val_buf, "DEFAULT" );
1825 } else if( !strcmp( get_primitive( field ), "number" )) {
1826 const char* numtype = get_datatype( field );
1827 if( !strcmp( numtype, "INT8" )) {
1828 buffer_fadd( val_buf, "%lld", atoll( value ));
1830 } else if( !strcmp( numtype, "INT" )) {
1831 buffer_fadd( val_buf, "%d", atoi( value ));
1833 } else if( !strcmp( numtype, "NUMERIC" )) {
1834 buffer_fadd( val_buf, "%f", atof( value ));
1837 if( dbi_conn_quote_string( writehandle, &value )) {
1838 OSRF_BUFFER_ADD( val_buf, value );
1841 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
1842 osrfAppSessionStatus(
1844 OSRF_STATUS_INTERNALSERVERERROR,
1845 "osrfMethodException",
1847 "Error quoting string -- please see the error log for more details"
1850 buffer_free( table_buf );
1851 buffer_free( col_buf );
1852 buffer_free( val_buf );
1853 osrfAppRespondComplete( ctx, NULL );
1861 osrfHashIteratorFree( field_itr );
1863 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1864 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1866 char* table_str = buffer_release( table_buf );
1867 char* col_str = buffer_release( col_buf );
1868 char* val_str = buffer_release( val_buf );
1869 growing_buffer* sql = buffer_init( 128 );
1870 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1875 char* query = buffer_release( sql );
1877 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
1879 jsonObject* obj = NULL;
1882 dbi_result result = dbi_conn_query( writehandle, query );
1884 obj = jsonNewObject( NULL );
1887 "%s ERROR inserting %s object using query [%s]",
1889 osrfHashGet(meta, "fieldmapper"),
1892 osrfAppSessionStatus(
1894 OSRF_STATUS_INTERNALSERVERERROR,
1895 "osrfMethodException",
1897 "INSERT error -- please see the error log for more details"
1902 char* id = oilsFMGetString( target, pkey );
1904 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
1905 growing_buffer* _id = buffer_init( 10 );
1906 buffer_fadd( _id, "%lld", new_id );
1907 id = buffer_release( _id );
1910 // Find quietness specification, if present
1911 const char* quiet_str = NULL;
1913 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1915 quiet_str = jsonObjectGetString( quiet_obj );
1918 if( str_is_true( quiet_str )) { // if quietness is specified
1919 obj = jsonNewObject( id );
1923 // Fetch the row that we just inserted, so that we can return it to the client
1924 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1925 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
1928 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
1932 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
1934 jsonObjectFree( list );
1935 jsonObjectFree( where_clause );
1942 osrfAppRespondComplete( ctx, obj );
1943 jsonObjectFree( obj );
1948 @brief Implement the retrieve method.
1949 @param ctx Pointer to the method context.
1950 @param err Pointer through which to return an error code.
1951 @return If successful, a pointer to the result to be returned to the client;
1954 From the method's class, fetch a row with a specified value in the primary key. This
1955 method relies on the database design convention that a primary key consists of a single
1959 - authkey (PCRUD only)
1960 - value of the primary key for the desired row, for building the WHERE clause
1961 - a JSON_HASH containing any other SQL clauses: select, join, etc.
1963 Return to client: One row from the query.
1965 int doRetrieve( osrfMethodContext* ctx ) {
1966 if(osrfMethodVerifyContext( ctx )) {
1967 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1972 timeout_needs_resetting = 1;
1977 if( enforce_pcrud ) {
1982 // Get the class metadata
1983 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1985 // Get the value of the primary key, from a method parameter
1986 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
1990 "%s retrieving %s object with primary key value of %s",
1992 osrfHashGet( class_def, "fieldmapper" ),
1993 jsonObjectGetString( id_obj )
1996 // Build a WHERE clause based on the key value
1997 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2000 osrfHashGet( class_def, "primarykey" ), // name of key column
2001 jsonObjectClone( id_obj ) // value of key column
2004 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2008 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2010 jsonObjectFree( where_clause );
2012 osrfAppRespondComplete( ctx, NULL );
2016 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2017 jsonObjectFree( list );
2019 if( enforce_pcrud ) {
2020 if(!verifyObjectPCRUD( ctx, obj )) {
2021 jsonObjectFree( obj );
2023 growing_buffer* msg = buffer_init( 128 );
2024 OSRF_BUFFER_ADD( msg, modulename );
2025 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2027 char* m = buffer_release( msg );
2028 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2032 osrfAppRespondComplete( ctx, NULL );
2037 osrfAppRespondComplete( ctx, obj );
2038 jsonObjectFree( obj );
2042 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2043 growing_buffer* val_buf = buffer_init( 32 );
2044 const char* numtype = get_datatype( field );
2046 if( !strncmp( numtype, "INT", 3 ) ) {
2047 if( value->type == JSON_NUMBER )
2048 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2049 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2051 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2054 } else if( !strcmp( numtype, "NUMERIC" )) {
2055 if( value->type == JSON_NUMBER )
2056 buffer_fadd( val_buf, jsonObjectGetString( value ));
2058 buffer_fadd( val_buf, jsonObjectGetString( value ));
2062 // Presumably this was really intended ot be a string, so quote it
2063 char* str = jsonObjectToSimpleString( value );
2064 if( dbi_conn_quote_string( dbhandle, &str )) {
2065 OSRF_BUFFER_ADD( val_buf, str );
2068 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2070 buffer_free( val_buf );
2075 return buffer_release( val_buf );
2078 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2079 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2080 growing_buffer* sql_buf = buffer_init( 32 );
2086 osrfHashGet( field, "name" )
2090 buffer_add( sql_buf, "IN (" );
2091 } else if( !strcasecmp( op,"not in" )) {
2092 buffer_add( sql_buf, "NOT IN (" );
2094 buffer_add( sql_buf, "IN (" );
2097 if( node->type == JSON_HASH ) {
2098 // subquery predicate
2099 char* subpred = buildQuery( ctx, node, SUBSELECT );
2101 buffer_free( sql_buf );
2105 buffer_add( sql_buf, subpred );
2108 } else if( node->type == JSON_ARRAY ) {
2109 // literal value list
2110 int in_item_index = 0;
2111 int in_item_first = 1;
2112 const jsonObject* in_item;
2113 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2118 buffer_add( sql_buf, ", " );
2121 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2122 osrfLogError( OSRF_LOG_MARK,
2123 "%s: Expected string or number within IN list; found %s",
2124 modulename, json_type( in_item->type ) );
2125 buffer_free( sql_buf );
2129 // Append the literal value -- quoted if not a number
2130 if( JSON_NUMBER == in_item->type ) {
2131 char* val = jsonNumberToDBString( field, in_item );
2132 OSRF_BUFFER_ADD( sql_buf, val );
2135 } else if( !strcmp( get_primitive( field ), "number" )) {
2136 char* val = jsonNumberToDBString( field, in_item );
2137 OSRF_BUFFER_ADD( sql_buf, val );
2141 char* key_string = jsonObjectToSimpleString( in_item );
2142 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2143 OSRF_BUFFER_ADD( sql_buf, key_string );
2146 osrfLogError( OSRF_LOG_MARK,
2147 "%s: Error quoting key string [%s]", modulename, key_string );
2149 buffer_free( sql_buf );
2155 if( in_item_first ) {
2156 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2157 buffer_free( sql_buf );
2161 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2162 modulename, json_type( node->type ));
2163 buffer_free( sql_buf );
2167 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2169 return buffer_release( sql_buf );
2172 // Receive a JSON_ARRAY representing a function call. The first
2173 // entry in the array is the function name. The rest are parameters.
2174 static char* searchValueTransform( const jsonObject* array ) {
2176 if( array->size < 1 ) {
2177 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2181 // Get the function name
2182 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2183 if( func_item->type != JSON_STRING ) {
2184 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2185 modulename, json_type( func_item->type ));
2189 growing_buffer* sql_buf = buffer_init( 32 );
2191 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2192 OSRF_BUFFER_ADD( sql_buf, "( " );
2194 // Get the parameters
2195 int func_item_index = 1; // We already grabbed the zeroth entry
2196 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2198 // Add a separator comma, if we need one
2199 if( func_item_index > 2 )
2200 buffer_add( sql_buf, ", " );
2202 // Add the current parameter
2203 if( func_item->type == JSON_NULL ) {
2204 buffer_add( sql_buf, "NULL" );
2206 char* val = jsonObjectToSimpleString( func_item );
2207 if( dbi_conn_quote_string( dbhandle, &val )) {
2208 OSRF_BUFFER_ADD( sql_buf, val );
2211 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2213 buffer_free( sql_buf );
2220 buffer_add( sql_buf, " )" );
2222 return buffer_release( sql_buf );
2225 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2226 const jsonObject* node, const char* op ) {
2228 if( ! is_good_operator( op ) ) {
2229 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2233 char* val = searchValueTransform( node );
2237 growing_buffer* sql_buf = buffer_init( 32 );
2242 osrfHashGet( field, "name" ),
2249 return buffer_release( sql_buf );
2252 // class_alias is a class name or other table alias
2253 // field is a field definition as stored in the IDL
2254 // node comes from the method parameter, and may represent an entry in the SELECT list
2255 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2256 const jsonObject* node ) {
2257 growing_buffer* sql_buf = buffer_init( 32 );
2259 const char* field_transform = jsonObjectGetString(
2260 jsonObjectGetKeyConst( node, "transform" ) );
2261 const char* transform_subcolumn = jsonObjectGetString(
2262 jsonObjectGetKeyConst( node, "result_field" ) );
2264 if( transform_subcolumn ) {
2265 if( ! is_identifier( transform_subcolumn ) ) {
2266 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2267 modulename, transform_subcolumn );
2268 buffer_free( sql_buf );
2271 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2274 if( field_transform ) {
2276 if( ! is_identifier( field_transform ) ) {
2277 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2278 modulename, field_transform );
2279 buffer_free( sql_buf );
2283 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2284 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2285 field_transform, class_alias, osrfHashGet( field, "name" ));
2287 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2288 field_transform, class_alias, osrfHashGet( field, "name" ));
2291 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2294 if( array->type != JSON_ARRAY ) {
2295 osrfLogError( OSRF_LOG_MARK,
2296 "%s: Expected JSON_ARRAY for function params; found %s",
2297 modulename, json_type( array->type ) );
2298 buffer_free( sql_buf );
2301 int func_item_index = 0;
2302 jsonObject* func_item;
2303 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2305 char* val = jsonObjectToSimpleString( func_item );
2308 buffer_add( sql_buf, ",NULL" );
2309 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2310 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2311 OSRF_BUFFER_ADD( sql_buf, val );
2313 osrfLogError( OSRF_LOG_MARK,
2314 "%s: Error quoting key string [%s]", modulename, val );
2316 buffer_free( sql_buf );
2323 buffer_add( sql_buf, " )" );
2326 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2329 if( transform_subcolumn )
2330 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2332 return buffer_release( sql_buf );
2335 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2336 const jsonObject* node, const char* op ) {
2338 if( ! is_good_operator( op ) ) {
2339 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2343 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2344 if( ! field_transform )
2347 int extra_parens = 0; // boolean
2349 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2351 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2353 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2355 free( field_transform );
2359 } else if( value_obj->type == JSON_ARRAY ) {
2360 value = searchValueTransform( value_obj );
2362 osrfLogError( OSRF_LOG_MARK,
2363 "%s: Error building value transform for field transform", modulename );
2364 free( field_transform );
2367 } else if( value_obj->type == JSON_HASH ) {
2368 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2370 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2372 free( field_transform );
2376 } else if( value_obj->type == JSON_NUMBER ) {
2377 value = jsonNumberToDBString( field, value_obj );
2378 } else if( value_obj->type == JSON_NULL ) {
2379 osrfLogError( OSRF_LOG_MARK,
2380 "%s: Error building predicate for field transform: null value", modulename );
2381 free( field_transform );
2383 } else if( value_obj->type == JSON_BOOL ) {
2384 osrfLogError( OSRF_LOG_MARK,
2385 "%s: Error building predicate for field transform: boolean value", modulename );
2386 free( field_transform );
2389 if( !strcmp( get_primitive( field ), "number") ) {
2390 value = jsonNumberToDBString( field, value_obj );
2392 value = jsonObjectToSimpleString( value_obj );
2393 if( !dbi_conn_quote_string( dbhandle, &value )) {
2394 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2395 modulename, value );
2397 free( field_transform );
2403 const char* left_parens = "";
2404 const char* right_parens = "";
2406 if( extra_parens ) {
2411 growing_buffer* sql_buf = buffer_init( 32 );
2415 "%s%s %s %s %s %s%s",
2426 free( field_transform );
2428 return buffer_release( sql_buf );
2431 static char* searchSimplePredicate( const char* op, const char* class_alias,
2432 osrfHash* field, const jsonObject* node ) {
2434 if( ! is_good_operator( op ) ) {
2435 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2441 // Get the value to which we are comparing the specified column
2442 if( node->type != JSON_NULL ) {
2443 if( node->type == JSON_NUMBER ) {
2444 val = jsonNumberToDBString( field, node );
2445 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2446 val = jsonNumberToDBString( field, node );
2448 val = jsonObjectToSimpleString( node );
2453 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2454 // Value is not numeric; enclose it in quotes
2455 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2456 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2463 // Compare to a null value
2464 val = strdup( "NULL" );
2465 if( strcmp( op, "=" ))
2471 growing_buffer* sql_buf = buffer_init( 32 );
2472 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2473 char* pred = buffer_release( sql_buf );
2480 static char* searchBETWEENPredicate( const char* class_alias,
2481 osrfHash* field, const jsonObject* node ) {
2483 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2484 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2486 if( NULL == y_node ) {
2487 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2490 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2491 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2498 if( !strcmp( get_primitive( field ), "number") ) {
2499 x_string = jsonNumberToDBString( field, x_node );
2500 y_string = jsonNumberToDBString( field, y_node );
2503 x_string = jsonObjectToSimpleString( x_node );
2504 y_string = jsonObjectToSimpleString( y_node );
2505 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2506 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2507 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2508 modulename, x_string, y_string );
2515 growing_buffer* sql_buf = buffer_init( 32 );
2516 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2517 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2521 return buffer_release( sql_buf );
2524 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2525 jsonObject* node, osrfMethodContext* ctx ) {
2528 if( node->type == JSON_ARRAY ) { // equality IN search
2529 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2530 } else if( node->type == JSON_HASH ) { // other search
2531 jsonIterator* pred_itr = jsonNewIterator( node );
2532 if( !jsonIteratorHasNext( pred_itr ) ) {
2533 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2534 modulename, osrfHashGet(field, "name" ));
2536 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2538 // Verify that there are no additional predicates
2539 if( jsonIteratorHasNext( pred_itr ) ) {
2540 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2541 modulename, osrfHashGet(field, "name" ));
2542 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2543 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2544 else if( !(strcasecmp( pred_itr->key,"in" ))
2545 || !(strcasecmp( pred_itr->key,"not in" )) )
2546 pred = searchINPredicate(
2547 class_info->alias, field, pred_node, pred_itr->key, ctx );
2548 else if( pred_node->type == JSON_ARRAY )
2549 pred = searchFunctionPredicate(
2550 class_info->alias, field, pred_node, pred_itr->key );
2551 else if( pred_node->type == JSON_HASH )
2552 pred = searchFieldTransformPredicate(
2553 class_info, field, pred_node, pred_itr->key );
2555 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2557 jsonIteratorFree( pred_itr );
2559 } else if( node->type == JSON_NULL ) { // IS NULL search
2560 growing_buffer* _p = buffer_init( 64 );
2563 "\"%s\".%s IS NULL",
2564 class_info->class_name,
2565 osrfHashGet( field, "name" )
2567 pred = buffer_release( _p );
2568 } else { // equality search
2569 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2588 field : call_number,
2604 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2606 const jsonObject* working_hash;
2607 jsonObject* freeable_hash = NULL;
2609 if( join_hash->type == JSON_HASH ) {
2610 working_hash = join_hash;
2611 } else if( join_hash->type == JSON_STRING ) {
2612 // turn it into a JSON_HASH by creating a wrapper
2613 // around a copy of the original
2614 const char* _tmp = jsonObjectGetString( join_hash );
2615 freeable_hash = jsonNewObjectType( JSON_HASH );
2616 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2617 working_hash = freeable_hash;
2621 "%s: JOIN failed; expected JSON object type not found",
2627 growing_buffer* join_buf = buffer_init( 128 );
2628 const char* leftclass = left_info->class_name;
2630 jsonObject* snode = NULL;
2631 jsonIterator* search_itr = jsonNewIterator( working_hash );
2633 while ( (snode = jsonIteratorNext( search_itr )) ) {
2634 const char* right_alias = search_itr->key;
2636 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2638 class = right_alias;
2640 const ClassInfo* right_info = add_joined_class( right_alias, class );
2644 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2648 jsonIteratorFree( search_itr );
2649 buffer_free( join_buf );
2651 jsonObjectFree( freeable_hash );
2654 osrfHash* links = right_info->links;
2655 const char* table = right_info->source_def;
2657 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2658 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2660 if( field && !fkey ) {
2661 // Look up the corresponding join column in the IDL.
2662 // The link must be defined in the child table,
2663 // and point to the right parent table.
2664 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2665 const char* reltype = NULL;
2666 const char* other_class = NULL;
2667 reltype = osrfHashGet( idl_link, "reltype" );
2668 if( reltype && strcmp( reltype, "has_many" ) )
2669 other_class = osrfHashGet( idl_link, "class" );
2670 if( other_class && !strcmp( other_class, leftclass ) )
2671 fkey = osrfHashGet( idl_link, "key" );
2675 "%s: JOIN failed. No link defined from %s.%s to %s",
2681 buffer_free( join_buf );
2683 jsonObjectFree( freeable_hash );
2684 jsonIteratorFree( search_itr );
2688 } else if( !field && fkey ) {
2689 // Look up the corresponding join column in the IDL.
2690 // The link must be defined in the child table,
2691 // and point to the right parent table.
2692 osrfHash* left_links = left_info->links;
2693 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2694 const char* reltype = NULL;
2695 const char* other_class = NULL;
2696 reltype = osrfHashGet( idl_link, "reltype" );
2697 if( reltype && strcmp( reltype, "has_many" ) )
2698 other_class = osrfHashGet( idl_link, "class" );
2699 if( other_class && !strcmp( other_class, class ) )
2700 field = osrfHashGet( idl_link, "key" );
2704 "%s: JOIN failed. No link defined from %s.%s to %s",
2710 buffer_free( join_buf );
2712 jsonObjectFree( freeable_hash );
2713 jsonIteratorFree( search_itr );
2717 } else if( !field && !fkey ) {
2718 osrfHash* left_links = left_info->links;
2720 // For each link defined for the left class:
2721 // see if the link references the joined class
2722 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2723 osrfHash* curr_link = NULL;
2724 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2725 const char* other_class = osrfHashGet( curr_link, "class" );
2726 if( other_class && !strcmp( other_class, class ) ) {
2728 // In the IDL, the parent class doesn't always know then names of the child
2729 // columns that are pointing to it, so don't use that end of the link
2730 const char* reltype = osrfHashGet( curr_link, "reltype" );
2731 if( reltype && strcmp( reltype, "has_many" ) ) {
2732 // Found a link between the classes
2733 fkey = osrfHashIteratorKey( itr );
2734 field = osrfHashGet( curr_link, "key" );
2739 osrfHashIteratorFree( itr );
2741 if( !field || !fkey ) {
2742 // Do another such search, with the classes reversed
2744 // For each link defined for the joined class:
2745 // see if the link references the left class
2746 osrfHashIterator* itr = osrfNewHashIterator( links );
2747 osrfHash* curr_link = NULL;
2748 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2749 const char* other_class = osrfHashGet( curr_link, "class" );
2750 if( other_class && !strcmp( other_class, leftclass ) ) {
2752 // In the IDL, the parent class doesn't know then names of the child
2753 // columns that are pointing to it, so don't use that end of the link
2754 const char* reltype = osrfHashGet( curr_link, "reltype" );
2755 if( reltype && strcmp( reltype, "has_many" ) ) {
2756 // Found a link between the classes
2757 field = osrfHashIteratorKey( itr );
2758 fkey = osrfHashGet( curr_link, "key" );
2763 osrfHashIteratorFree( itr );
2766 if( !field || !fkey ) {
2769 "%s: JOIN failed. No link defined between %s and %s",
2774 buffer_free( join_buf );
2776 jsonObjectFree( freeable_hash );
2777 jsonIteratorFree( search_itr );
2782 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2784 if( !strcasecmp( type,"left" )) {
2785 buffer_add( join_buf, " LEFT JOIN" );
2786 } else if( !strcasecmp( type,"right" )) {
2787 buffer_add( join_buf, " RIGHT JOIN" );
2788 } else if( !strcasecmp( type,"full" )) {
2789 buffer_add( join_buf, " FULL JOIN" );
2791 buffer_add( join_buf, " INNER JOIN" );
2794 buffer_add( join_buf, " INNER JOIN" );
2797 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2798 table, right_alias, right_alias, field, left_info->alias, fkey );
2800 // Add any other join conditions as specified by "filter"
2801 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2803 const char* filter_op = jsonObjectGetString(
2804 jsonObjectGetKeyConst( snode, "filter_op" ) );
2805 if( filter_op && !strcasecmp( "or",filter_op )) {
2806 buffer_add( join_buf, " OR " );
2808 buffer_add( join_buf, " AND " );
2811 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2813 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2814 OSRF_BUFFER_ADD( join_buf, jpred );
2819 "%s: JOIN failed. Invalid conditional expression.",
2822 jsonIteratorFree( search_itr );
2823 buffer_free( join_buf );
2825 jsonObjectFree( freeable_hash );
2830 buffer_add( join_buf, " ) " );
2832 // Recursively add a nested join, if one is present
2833 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2835 char* jpred = searchJOIN( join_filter, right_info );
2837 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2838 OSRF_BUFFER_ADD( join_buf, jpred );
2841 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
2842 jsonIteratorFree( search_itr );
2843 buffer_free( join_buf );
2845 jsonObjectFree( freeable_hash );
2852 jsonObjectFree( freeable_hash );
2853 jsonIteratorFree( search_itr );
2855 return buffer_release( join_buf );
2860 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2861 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2862 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2864 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2866 search_hash is the JSON expression of the conditions.
2867 meta is the class definition from the IDL, for the relevant table.
2868 opjoin_type indicates whether multiple conditions, if present, should be
2869 connected by AND or OR.
2870 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2871 to pass it to other functions -- and all they do with it is to use the session
2872 and request members to send error messages back to the client.
2876 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
2877 int opjoin_type, osrfMethodContext* ctx ) {
2881 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
2882 "opjoin_type = %d, ctx addr = %p",
2885 class_info->class_def,
2890 growing_buffer* sql_buf = buffer_init( 128 );
2892 jsonObject* node = NULL;
2895 if( search_hash->type == JSON_ARRAY ) {
2896 osrfLogDebug( OSRF_LOG_MARK,
2897 "%s: In WHERE clause, condition type is JSON_ARRAY", modulename );
2898 if( 0 == search_hash->size ) {
2901 "%s: Invalid predicate structure: empty JSON array",
2904 buffer_free( sql_buf );
2908 unsigned long i = 0;
2909 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
2913 if( opjoin_type == OR_OP_JOIN )
2914 buffer_add( sql_buf, " OR " );
2916 buffer_add( sql_buf, " AND " );
2919 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2921 buffer_free( sql_buf );
2925 buffer_fadd( sql_buf, "( %s )", subpred );
2929 } else if( search_hash->type == JSON_HASH ) {
2930 osrfLogDebug( OSRF_LOG_MARK,
2931 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
2932 jsonIterator* search_itr = jsonNewIterator( search_hash );
2933 if( !jsonIteratorHasNext( search_itr ) ) {
2936 "%s: Invalid predicate structure: empty JSON object",
2939 jsonIteratorFree( search_itr );
2940 buffer_free( sql_buf );
2944 while( (node = jsonIteratorNext( search_itr )) ) {
2949 if( opjoin_type == OR_OP_JOIN )
2950 buffer_add( sql_buf, " OR " );
2952 buffer_add( sql_buf, " AND " );
2955 if( '+' == search_itr->key[ 0 ] ) {
2957 // This plus sign prefixes a class name or other table alias;
2958 // make sure the table alias is in scope
2959 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2960 if( ! alias_info ) {
2963 "%s: Invalid table alias \"%s\" in WHERE clause",
2967 jsonIteratorFree( search_itr );
2968 buffer_free( sql_buf );
2972 if( node->type == JSON_STRING ) {
2973 // It's the name of a column; make sure it belongs to the class
2974 const char* fieldname = jsonObjectGetString( node );
2975 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2978 "%s: Invalid column name \"%s\" in WHERE clause "
2979 "for table alias \"%s\"",
2984 jsonIteratorFree( search_itr );
2985 buffer_free( sql_buf );
2989 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
2991 // It's something more complicated
2992 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
2994 jsonIteratorFree( search_itr );
2995 buffer_free( sql_buf );
2999 buffer_fadd( sql_buf, "( %s )", subpred );
3002 } else if( '-' == search_itr->key[ 0 ] ) {
3003 if( !strcasecmp( "-or", search_itr->key )) {
3004 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3006 jsonIteratorFree( search_itr );
3007 buffer_free( sql_buf );
3011 buffer_fadd( sql_buf, "( %s )", subpred );
3013 } else if( !strcasecmp( "-and", search_itr->key )) {
3014 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3016 jsonIteratorFree( search_itr );
3017 buffer_free( sql_buf );
3021 buffer_fadd( sql_buf, "( %s )", subpred );
3023 } else if( !strcasecmp("-not",search_itr->key) ) {
3024 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3026 jsonIteratorFree( search_itr );
3027 buffer_free( sql_buf );
3031 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3033 } else if( !strcasecmp( "-exists", search_itr->key )) {
3034 char* subpred = buildQuery( ctx, node, SUBSELECT );
3036 jsonIteratorFree( search_itr );
3037 buffer_free( sql_buf );
3041 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3043 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3044 char* subpred = buildQuery( ctx, node, SUBSELECT );
3046 jsonIteratorFree( search_itr );
3047 buffer_free( sql_buf );
3051 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3053 } else { // Invalid "minus" operator
3056 "%s: Invalid operator \"%s\" in WHERE clause",
3060 jsonIteratorFree( search_itr );
3061 buffer_free( sql_buf );
3067 const char* class = class_info->class_name;
3068 osrfHash* fields = class_info->fields;
3069 osrfHash* field = osrfHashGet( fields, search_itr->key );
3072 const char* table = class_info->source_def;
3075 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3078 table ? table : "?",
3081 jsonIteratorFree( search_itr );
3082 buffer_free( sql_buf );
3086 char* subpred = searchPredicate( class_info, field, node, ctx );
3088 buffer_free( sql_buf );
3089 jsonIteratorFree( search_itr );
3093 buffer_add( sql_buf, subpred );
3097 jsonIteratorFree( search_itr );
3100 // ERROR ... only hash and array allowed at this level
3101 char* predicate_string = jsonObjectToJSON( search_hash );
3104 "%s: Invalid predicate structure: %s",
3108 buffer_free( sql_buf );
3109 free( predicate_string );
3113 return buffer_release( sql_buf );
3116 /* Build a JSON_ARRAY of field names for a given table alias
3118 static jsonObject* defaultSelectList( const char* table_alias ) {
3123 ClassInfo* class_info = search_all_alias( table_alias );
3124 if( ! class_info ) {
3127 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3134 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3135 osrfHash* field_def = NULL;
3136 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3137 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3138 const char* field_name = osrfHashIteratorKey( field_itr );
3139 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3140 jsonObjectPush( array, jsonNewObject( field_name ) );
3143 osrfHashIteratorFree( field_itr );
3148 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3149 // The jsonObject must be a JSON_HASH with an single entry for "union",
3150 // "intersect", or "except". The data associated with this key must be an
3151 // array of hashes, each hash being a query.
3152 // Also allowed but currently ignored: entries for "order_by" and "alias".
3153 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3155 if( ! combo || combo->type != JSON_HASH )
3156 return NULL; // should be impossible; validated by caller
3158 const jsonObject* query_array = NULL; // array of subordinate queries
3159 const char* op = NULL; // name of operator, e.g. UNION
3160 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3161 int op_count = 0; // for detecting conflicting operators
3162 int excepting = 0; // boolean
3163 int all = 0; // boolean
3164 jsonObject* order_obj = NULL;
3166 // Identify the elements in the hash
3167 jsonIterator* query_itr = jsonNewIterator( combo );
3168 jsonObject* curr_obj = NULL;
3169 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3170 if( ! strcmp( "union", query_itr->key ) ) {
3173 query_array = curr_obj;
3174 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3177 query_array = curr_obj;
3178 } else if( ! strcmp( "except", query_itr->key ) ) {
3182 query_array = curr_obj;
3183 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3186 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3189 order_obj = curr_obj;
3190 } else if( ! strcmp( "alias", query_itr->key ) ) {
3191 if( curr_obj->type != JSON_STRING ) {
3192 jsonIteratorFree( query_itr );
3195 alias = jsonObjectGetString( curr_obj );
3196 } else if( ! strcmp( "all", query_itr->key ) ) {
3197 if( obj_is_true( curr_obj ) )
3201 osrfAppSessionStatus(
3203 OSRF_STATUS_INTERNALSERVERERROR,
3204 "osrfMethodException",
3206 "Malformed query; unexpected entry in query object"
3210 "%s: Unexpected entry for \"%s\" in%squery",
3215 jsonIteratorFree( query_itr );
3219 jsonIteratorFree( query_itr );
3221 // More sanity checks
3222 if( ! query_array ) {
3224 osrfAppSessionStatus(
3226 OSRF_STATUS_INTERNALSERVERERROR,
3227 "osrfMethodException",
3229 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3233 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3236 return NULL; // should be impossible...
3237 } else if( op_count > 1 ) {
3239 osrfAppSessionStatus(
3241 OSRF_STATUS_INTERNALSERVERERROR,
3242 "osrfMethodException",
3244 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3248 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3252 } if( query_array->type != JSON_ARRAY ) {
3254 osrfAppSessionStatus(
3256 OSRF_STATUS_INTERNALSERVERERROR,
3257 "osrfMethodException",
3259 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3263 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3266 json_type( query_array->type )
3269 } if( query_array->size < 2 ) {
3271 osrfAppSessionStatus(
3273 OSRF_STATUS_INTERNALSERVERERROR,
3274 "osrfMethodException",
3276 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3280 "%s:%srequires multiple queries as operands",
3285 } else if( excepting && query_array->size > 2 ) {
3287 osrfAppSessionStatus(
3289 OSRF_STATUS_INTERNALSERVERERROR,
3290 "osrfMethodException",
3292 "EXCEPT operator has too many queries as operands"
3296 "%s:EXCEPT operator has too many queries as operands",
3300 } else if( order_obj && ! alias ) {
3302 osrfAppSessionStatus(
3304 OSRF_STATUS_INTERNALSERVERERROR,
3305 "osrfMethodException",
3307 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3311 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3317 // So far so good. Now build the SQL.
3318 growing_buffer* sql = buffer_init( 256 );
3320 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3321 // Add a layer of parentheses
3322 if( flags & SUBCOMBO )
3323 OSRF_BUFFER_ADD( sql, "( " );
3325 // Traverse the query array. Each entry should be a hash.
3326 int first = 1; // boolean
3328 jsonObject* query = NULL;
3329 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3330 if( query->type != JSON_HASH ) {
3332 osrfAppSessionStatus(
3334 OSRF_STATUS_INTERNALSERVERERROR,
3335 "osrfMethodException",
3337 "Malformed query under UNION, INTERSECT or EXCEPT"
3341 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3344 json_type( query->type )
3353 OSRF_BUFFER_ADD( sql, op );
3355 OSRF_BUFFER_ADD( sql, "ALL " );
3358 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3362 "%s: Error building query under%s",
3370 OSRF_BUFFER_ADD( sql, query_str );
3373 if( flags & SUBCOMBO )
3374 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3376 if( !(flags & SUBSELECT) )
3377 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3379 return buffer_release( sql );
3382 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3383 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3384 // or "except" to indicate the type of query.
3385 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3389 osrfAppSessionStatus(
3391 OSRF_STATUS_INTERNALSERVERERROR,
3392 "osrfMethodException",
3394 "Malformed query; no query object"
3396 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3398 } else if( query->type != JSON_HASH ) {
3400 osrfAppSessionStatus(
3402 OSRF_STATUS_INTERNALSERVERERROR,
3403 "osrfMethodException",
3405 "Malformed query object"
3409 "%s: Query object is %s instead of JSON_HASH",
3411 json_type( query->type )
3416 // Determine what kind of query it purports to be, and dispatch accordingly.
3417 if( jsonObjectGetKey( query, "union" ) ||
3418 jsonObjectGetKey( query, "intersect" ) ||
3419 jsonObjectGetKey( query, "except" ) ) {
3420 return doCombo( ctx, query, flags );
3422 // It is presumably a SELECT query
3424 // Push a node onto the stack for the current query. Every level of
3425 // subquery gets its own QueryFrame on the Stack.
3428 // Build an SQL SELECT statement
3431 jsonObjectGetKey( query, "select" ),
3432 jsonObjectGetKey( query, "from" ),
3433 jsonObjectGetKey( query, "where" ),
3434 jsonObjectGetKey( query, "having" ),
3435 jsonObjectGetKey( query, "order_by" ),
3436 jsonObjectGetKey( query, "limit" ),
3437 jsonObjectGetKey( query, "offset" ),
3446 /* method context */ osrfMethodContext* ctx,
3448 /* SELECT */ jsonObject* selhash,
3449 /* FROM */ jsonObject* join_hash,
3450 /* WHERE */ jsonObject* search_hash,
3451 /* HAVING */ jsonObject* having_hash,
3452 /* ORDER BY */ jsonObject* order_hash,
3453 /* LIMIT */ jsonObject* limit,
3454 /* OFFSET */ jsonObject* offset,
3455 /* flags */ int flags
3457 const char* locale = osrf_message_get_last_locale();
3459 // general tmp objects
3460 const jsonObject* tmp_const;
3461 jsonObject* selclass = NULL;
3462 jsonObject* snode = NULL;
3463 jsonObject* onode = NULL;
3465 char* string = NULL;
3466 int from_function = 0;
3471 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3473 // punt if there's no FROM clause
3474 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3477 "%s: FROM clause is missing or empty",
3481 osrfAppSessionStatus(
3483 OSRF_STATUS_INTERNALSERVERERROR,
3484 "osrfMethodException",
3486 "FROM clause is missing or empty in JSON query"
3491 // the core search class
3492 const char* core_class = NULL;
3494 // get the core class -- the only key of the top level FROM clause, or a string
3495 if( join_hash->type == JSON_HASH ) {
3496 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3497 snode = jsonIteratorNext( tmp_itr );
3499 // Populate the current QueryFrame with information
3500 // about the core class
3501 if( add_query_core( NULL, tmp_itr->key ) ) {
3503 osrfAppSessionStatus(
3505 OSRF_STATUS_INTERNALSERVERERROR,
3506 "osrfMethodException",
3508 "Unable to look up core class"
3512 core_class = curr_query->core.class_name;
3515 jsonObject* extra = jsonIteratorNext( tmp_itr );
3517 jsonIteratorFree( tmp_itr );
3520 // There shouldn't be more than one entry in join_hash
3524 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3528 osrfAppSessionStatus(
3530 OSRF_STATUS_INTERNALSERVERERROR,
3531 "osrfMethodException",
3533 "Malformed FROM clause in JSON query"
3535 return NULL; // Malformed join_hash; extra entry
3537 } else if( join_hash->type == JSON_ARRAY ) {
3538 // We're selecting from a function, not from a table
3540 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3543 } else if( join_hash->type == JSON_STRING ) {
3544 // Populate the current QueryFrame with information
3545 // about the core class
3546 core_class = jsonObjectGetString( join_hash );
3548 if( add_query_core( NULL, core_class ) ) {
3550 osrfAppSessionStatus(
3552 OSRF_STATUS_INTERNALSERVERERROR,
3553 "osrfMethodException",
3555 "Unable to look up core class"
3563 "%s: FROM clause is unexpected JSON type: %s",
3565 json_type( join_hash->type )
3568 osrfAppSessionStatus(
3570 OSRF_STATUS_INTERNALSERVERERROR,
3571 "osrfMethodException",
3573 "Ill-formed FROM clause in JSON query"
3578 // Build the join clause, if any, while filling out the list
3579 // of joined classes in the current QueryFrame.
3580 char* join_clause = NULL;
3581 if( join_hash && ! from_function ) {
3583 join_clause = searchJOIN( join_hash, &curr_query->core );
3584 if( ! join_clause ) {
3586 osrfAppSessionStatus(
3588 OSRF_STATUS_INTERNALSERVERERROR,
3589 "osrfMethodException",
3591 "Unable to construct JOIN clause(s)"
3597 // For in case we don't get a select list
3598 jsonObject* defaultselhash = NULL;
3600 // if there is no select list, build a default select list ...
3601 if( !selhash && !from_function ) {
3602 jsonObject* default_list = defaultSelectList( core_class );
3603 if( ! default_list ) {
3605 osrfAppSessionStatus(
3607 OSRF_STATUS_INTERNALSERVERERROR,
3608 "osrfMethodException",
3610 "Unable to build default SELECT clause in JSON query"
3612 free( join_clause );
3617 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3618 jsonObjectSetKey( selhash, core_class, default_list );
3621 // The SELECT clause can be encoded only by a hash
3622 if( !from_function && selhash->type != JSON_HASH ) {
3625 "%s: Expected JSON_HASH for SELECT clause; found %s",
3627 json_type( selhash->type )
3631 osrfAppSessionStatus(
3633 OSRF_STATUS_INTERNALSERVERERROR,
3634 "osrfMethodException",
3636 "Malformed SELECT clause in JSON query"
3638 free( join_clause );
3642 // If you see a null or wild card specifier for the core class, or an
3643 // empty array, replace it with a default SELECT list
3644 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3646 int default_needed = 0; // boolean
3647 if( JSON_STRING == tmp_const->type
3648 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3650 else if( JSON_NULL == tmp_const->type )
3653 if( default_needed ) {
3654 // Build a default SELECT list
3655 jsonObject* default_list = defaultSelectList( core_class );
3656 if( ! default_list ) {
3658 osrfAppSessionStatus(
3660 OSRF_STATUS_INTERNALSERVERERROR,
3661 "osrfMethodException",
3663 "Can't build default SELECT clause in JSON query"
3665 free( join_clause );
3670 jsonObjectSetKey( selhash, core_class, default_list );
3674 // temp buffers for the SELECT list and GROUP BY clause
3675 growing_buffer* select_buf = buffer_init( 128 );
3676 growing_buffer* group_buf = buffer_init( 128 );
3678 int aggregate_found = 0; // boolean
3680 // Build a select list
3681 if( from_function ) // From a function we select everything
3682 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3685 // Build the SELECT list as SQL
3689 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3690 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3692 const char* cname = selclass_itr->key;
3694 // Make sure the target relation is in the FROM clause.
3696 // At this point join_hash is a step down from the join_hash we
3697 // received as a parameter. If the original was a JSON_STRING,
3698 // then json_hash is now NULL. If the original was a JSON_HASH,
3699 // then json_hash is now the first (and only) entry in it,
3700 // denoting the core class. We've already excluded the
3701 // possibility that the original was a JSON_ARRAY, because in
3702 // that case from_function would be non-NULL, and we wouldn't
3705 // If the current table alias isn't in scope, bail out
3706 ClassInfo* class_info = search_alias( cname );
3707 if( ! class_info ) {
3710 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3715 osrfAppSessionStatus(
3717 OSRF_STATUS_INTERNALSERVERERROR,
3718 "osrfMethodException",
3720 "Selected class not in FROM clause in JSON query"
3722 jsonIteratorFree( selclass_itr );
3723 buffer_free( select_buf );
3724 buffer_free( group_buf );
3725 if( defaultselhash )
3726 jsonObjectFree( defaultselhash );
3727 free( join_clause );
3731 if( selclass->type != JSON_ARRAY ) {
3734 "%s: Malformed SELECT list for class \"%s\"; not an array",
3739 osrfAppSessionStatus(
3741 OSRF_STATUS_INTERNALSERVERERROR,
3742 "osrfMethodException",
3744 "Selected class not in FROM clause 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 );
3756 // Look up some attributes of the current class
3757 osrfHash* idlClass = class_info->class_def;
3758 osrfHash* class_field_set = class_info->fields;
3759 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3760 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3762 if( 0 == selclass->size ) {
3765 "%s: No columns selected from \"%s\"",
3771 // stitch together the column list for the current table alias...
3772 unsigned long field_idx = 0;
3773 jsonObject* selfield = NULL;
3774 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3776 // If we need a separator comma, add one
3780 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3783 // if the field specification is a string, add it to the list
3784 if( selfield->type == JSON_STRING ) {
3786 // Look up the field in the IDL
3787 const char* col_name = jsonObjectGetString( selfield );
3788 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3790 // No such field in current class
3793 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3799 osrfAppSessionStatus(
3801 OSRF_STATUS_INTERNALSERVERERROR,
3802 "osrfMethodException",
3804 "Selected column not defined in JSON query"
3806 jsonIteratorFree( selclass_itr );
3807 buffer_free( select_buf );
3808 buffer_free( group_buf );
3809 if( defaultselhash )
3810 jsonObjectFree( defaultselhash );
3811 free( join_clause );
3813 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3814 // Virtual field not allowed
3817 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3823 osrfAppSessionStatus(
3825 OSRF_STATUS_INTERNALSERVERERROR,
3826 "osrfMethodException",
3828 "Selected column may not be virtual in JSON query"
3830 jsonIteratorFree( selclass_itr );
3831 buffer_free( select_buf );
3832 buffer_free( group_buf );
3833 if( defaultselhash )
3834 jsonObjectFree( defaultselhash );
3835 free( join_clause );
3841 if( flags & DISABLE_I18N )
3844 i18n = osrfHashGet( field_def, "i18n" );
3846 if( str_is_true( i18n ) ) {
3847 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
3848 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3849 class_tname, cname, col_name, class_pkey,
3850 cname, class_pkey, locale, col_name );
3852 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3853 cname, col_name, col_name );
3856 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3857 cname, col_name, col_name );
3860 // ... but it could be an object, in which case we check for a Field Transform
3861 } else if( selfield->type == JSON_HASH ) {
3863 const char* col_name = jsonObjectGetString(
3864 jsonObjectGetKeyConst( selfield, "column" ) );
3866 // Get the field definition from the IDL
3867 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3869 // No such field in current class
3872 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3878 osrfAppSessionStatus(
3880 OSRF_STATUS_INTERNALSERVERERROR,
3881 "osrfMethodException",
3883 "Selected column is not defined in JSON query"
3885 jsonIteratorFree( selclass_itr );
3886 buffer_free( select_buf );
3887 buffer_free( group_buf );
3888 if( defaultselhash )
3889 jsonObjectFree( defaultselhash );
3890 free( join_clause );
3892 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
3893 // No such field in current class
3896 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3902 osrfAppSessionStatus(
3904 OSRF_STATUS_INTERNALSERVERERROR,
3905 "osrfMethodException",
3907 "Selected column is virtual in JSON query"
3909 jsonIteratorFree( selclass_itr );
3910 buffer_free( select_buf );
3911 buffer_free( group_buf );
3912 if( defaultselhash )
3913 jsonObjectFree( defaultselhash );
3914 free( join_clause );
3918 // Decide what to use as a column alias
3920 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3921 _alias = jsonObjectGetString( tmp_const );
3922 } else { // Use field name as the alias
3926 if( jsonObjectGetKeyConst( selfield, "transform" )) {
3927 char* transform_str = searchFieldTransform(
3928 class_info->alias, field_def, selfield );
3929 if( transform_str ) {
3930 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
3931 free( transform_str );
3934 osrfAppSessionStatus(
3936 OSRF_STATUS_INTERNALSERVERERROR,
3937 "osrfMethodException",
3939 "Unable to generate transform function in JSON query"
3941 jsonIteratorFree( selclass_itr );
3942 buffer_free( select_buf );
3943 buffer_free( group_buf );
3944 if( defaultselhash )
3945 jsonObjectFree( defaultselhash );
3946 free( join_clause );
3953 if( flags & DISABLE_I18N )
3956 i18n = osrfHashGet( field_def, "i18n" );
3958 if( str_is_true( i18n ) ) {
3959 buffer_fadd( select_buf,
3960 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
3961 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
3962 class_tname, cname, col_name, class_pkey, cname,
3963 class_pkey, locale, _alias );
3965 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3966 cname, col_name, _alias );
3969 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3970 cname, col_name, _alias );
3977 "%s: Selected item is unexpected JSON type: %s",
3979 json_type( selfield->type )
3982 osrfAppSessionStatus(
3984 OSRF_STATUS_INTERNALSERVERERROR,
3985 "osrfMethodException",
3987 "Ill-formed SELECT item in JSON query"
3989 jsonIteratorFree( selclass_itr );
3990 buffer_free( select_buf );
3991 buffer_free( group_buf );
3992 if( defaultselhash )
3993 jsonObjectFree( defaultselhash );
3994 free( join_clause );
3998 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3999 if( obj_is_true( agg_obj ) )
4000 aggregate_found = 1;
4002 // Append a comma (except for the first one)
4003 // and add the column to a GROUP BY clause
4007 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4009 buffer_fadd( group_buf, " %d", sel_pos );
4013 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4015 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4016 if ( ! obj_is_true( aggregate_obj ) ) {
4020 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4023 buffer_fadd(group_buf, " %d", sel_pos);
4026 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4030 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4033 _column = searchFieldTransform(class_info->alias, field, selfield);
4034 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4035 OSRF_BUFFER_ADD(group_buf, _column);
4036 _column = searchFieldTransform(class_info->alias, field, selfield);
4043 } // end while -- iterating across SELECT columns
4045 } // end while -- iterating across classes
4047 jsonIteratorFree( selclass_itr );
4051 char* col_list = buffer_release( select_buf );
4053 // Make sure the SELECT list isn't empty. This can happen, for example,
4054 // if we try to build a default SELECT clause from a non-core table.
4057 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4059 osrfAppSessionStatus(
4061 OSRF_STATUS_INTERNALSERVERERROR,
4062 "osrfMethodException",
4064 "SELECT list is empty"
4067 buffer_free( group_buf );
4068 if( defaultselhash )
4069 jsonObjectFree( defaultselhash );
4070 free( join_clause );
4076 table = searchValueTransform( join_hash );
4078 table = strdup( curr_query->core.source_def );
4082 osrfAppSessionStatus(
4084 OSRF_STATUS_INTERNALSERVERERROR,
4085 "osrfMethodException",
4087 "Unable to identify table for core class"
4090 buffer_free( group_buf );
4091 if( defaultselhash )
4092 jsonObjectFree( defaultselhash );
4093 free( join_clause );
4097 // Put it all together
4098 growing_buffer* sql_buf = buffer_init( 128 );
4099 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4103 // Append the join clause, if any
4105 buffer_add(sql_buf, join_clause );
4106 free( join_clause );
4109 char* order_by_list = NULL;
4110 char* having_buf = NULL;
4112 if( !from_function ) {
4114 // Build a WHERE clause, if there is one
4116 buffer_add( sql_buf, " WHERE " );
4118 // and it's on the WHERE clause
4119 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4122 osrfAppSessionStatus(
4124 OSRF_STATUS_INTERNALSERVERERROR,
4125 "osrfMethodException",
4127 "Severe query error in WHERE predicate -- see error log for more details"
4130 buffer_free( group_buf );
4131 buffer_free( sql_buf );
4132 if( defaultselhash )
4133 jsonObjectFree( defaultselhash );
4137 buffer_add( sql_buf, pred );
4141 // Build a HAVING clause, if there is one
4144 // and it's on the the WHERE clause
4145 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4147 if( ! having_buf ) {
4149 osrfAppSessionStatus(
4151 OSRF_STATUS_INTERNALSERVERERROR,
4152 "osrfMethodException",
4154 "Severe query error in HAVING predicate -- see error log for more details"
4157 buffer_free( group_buf );
4158 buffer_free( sql_buf );
4159 if( defaultselhash )
4160 jsonObjectFree( defaultselhash );
4165 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4167 // Build an ORDER BY clause, if there is one
4168 if( NULL == order_hash )
4169 ; // No ORDER BY? do nothing
4170 else if( JSON_ARRAY == order_hash->type ) {
4171 // Array of field specifications, each specification being a
4172 // hash to define the class, field, and other details
4174 jsonObject* order_spec;
4175 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4177 if( JSON_HASH != order_spec->type ) {
4178 osrfLogError( OSRF_LOG_MARK,
4179 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4180 modulename, json_type( order_spec->type ) );
4182 osrfAppSessionStatus(
4184 OSRF_STATUS_INTERNALSERVERERROR,
4185 "osrfMethodException",
4187 "Malformed ORDER BY clause -- see error log for more details"
4189 buffer_free( order_buf );
4191 buffer_free( group_buf );
4192 buffer_free( sql_buf );
4193 if( defaultselhash )
4194 jsonObjectFree( defaultselhash );
4198 const char* class_alias =
4199 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4201 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4204 OSRF_BUFFER_ADD( order_buf, ", " );
4206 order_buf = buffer_init( 128 );
4208 if( !field || !class_alias ) {
4209 osrfLogError( OSRF_LOG_MARK,
4210 "%s: Missing class or field name in field specification "
4211 "of ORDER BY clause",
4214 osrfAppSessionStatus(
4216 OSRF_STATUS_INTERNALSERVERERROR,
4217 "osrfMethodException",
4219 "Malformed ORDER BY clause -- see error log for more details"
4221 buffer_free( order_buf );
4223 buffer_free( group_buf );
4224 buffer_free( sql_buf );
4225 if( defaultselhash )
4226 jsonObjectFree( defaultselhash );
4230 ClassInfo* order_class_info = search_alias( class_alias );
4231 if( ! order_class_info ) {
4232 osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4233 "not in FROM clause", modulename, class_alias );
4235 osrfAppSessionStatus(
4237 OSRF_STATUS_INTERNALSERVERERROR,
4238 "osrfMethodException",
4240 "Invalid class referenced in ORDER BY clause -- "
4241 "see error log for more details"
4244 buffer_free( group_buf );
4245 buffer_free( sql_buf );
4246 if( defaultselhash )
4247 jsonObjectFree( defaultselhash );
4251 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4253 osrfLogError( OSRF_LOG_MARK,
4254 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4255 modulename, class_alias, field );
4257 osrfAppSessionStatus(
4259 OSRF_STATUS_INTERNALSERVERERROR,
4260 "osrfMethodException",
4262 "Invalid field referenced in ORDER BY clause -- "
4263 "see error log for more details"
4266 buffer_free( group_buf );
4267 buffer_free( sql_buf );
4268 if( defaultselhash )
4269 jsonObjectFree( defaultselhash );
4271 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4272 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4273 modulename, field );
4275 osrfAppSessionStatus(
4277 OSRF_STATUS_INTERNALSERVERERROR,
4278 "osrfMethodException",
4280 "Virtual field in ORDER BY clause -- see error log for more details"
4282 buffer_free( order_buf );
4284 buffer_free( group_buf );
4285 buffer_free( sql_buf );
4286 if( defaultselhash )
4287 jsonObjectFree( defaultselhash );
4291 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4292 char* transform_str = searchFieldTransform(
4293 class_alias, field_def, order_spec );
4294 if( ! transform_str ) {
4296 osrfAppSessionStatus(
4298 OSRF_STATUS_INTERNALSERVERERROR,
4299 "osrfMethodException",
4301 "Severe query error in ORDER BY clause -- "
4302 "see error log for more details"
4304 buffer_free( order_buf );
4306 buffer_free( group_buf );
4307 buffer_free( sql_buf );
4308 if( defaultselhash )
4309 jsonObjectFree( defaultselhash );
4313 OSRF_BUFFER_ADD( order_buf, transform_str );
4314 free( transform_str );
4317 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4319 const char* direction =
4320 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4322 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4323 OSRF_BUFFER_ADD( order_buf, " DESC" );
4325 OSRF_BUFFER_ADD( order_buf, " ASC" );
4328 } else if( JSON_HASH == order_hash->type ) {
4329 // This hash is keyed on class alias. Each class has either
4330 // an array of field names or a hash keyed on field name.
4331 jsonIterator* class_itr = jsonNewIterator( order_hash );
4332 while( (snode = jsonIteratorNext( class_itr )) ) {
4334 ClassInfo* order_class_info = search_alias( class_itr->key );
4335 if( ! order_class_info ) {
4336 osrfLogError( OSRF_LOG_MARK,
4337 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4338 modulename, class_itr->key );
4340 osrfAppSessionStatus(
4342 OSRF_STATUS_INTERNALSERVERERROR,
4343 "osrfMethodException",
4345 "Invalid class referenced in ORDER BY clause -- "
4346 "see error log for more details"
4348 jsonIteratorFree( class_itr );
4349 buffer_free( order_buf );
4351 buffer_free( group_buf );
4352 buffer_free( sql_buf );
4353 if( defaultselhash )
4354 jsonObjectFree( defaultselhash );
4358 osrfHash* field_list_def = order_class_info->fields;
4360 if( snode->type == JSON_HASH ) {
4362 // Hash is keyed on field names from the current class. For each field
4363 // there is another layer of hash to define the sorting details, if any,
4364 // or a string to indicate direction of sorting.
4365 jsonIterator* order_itr = jsonNewIterator( snode );
4366 while( (onode = jsonIteratorNext( order_itr )) ) {
4368 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4370 osrfLogError( OSRF_LOG_MARK,
4371 "%s: Invalid field \"%s\" in ORDER BY clause",
4372 modulename, order_itr->key );
4374 osrfAppSessionStatus(
4376 OSRF_STATUS_INTERNALSERVERERROR,
4377 "osrfMethodException",
4379 "Invalid field in ORDER BY clause -- "
4380 "see error log for more details"
4382 jsonIteratorFree( order_itr );
4383 jsonIteratorFree( class_itr );
4384 buffer_free( order_buf );
4386 buffer_free( group_buf );
4387 buffer_free( sql_buf );
4388 if( defaultselhash )
4389 jsonObjectFree( defaultselhash );
4391 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4392 osrfLogError( OSRF_LOG_MARK,
4393 "%s: Virtual field \"%s\" in ORDER BY clause",
4394 modulename, order_itr->key );
4396 osrfAppSessionStatus(
4398 OSRF_STATUS_INTERNALSERVERERROR,
4399 "osrfMethodException",
4401 "Virtual field in ORDER BY clause -- "
4402 "see error log for more details"
4404 jsonIteratorFree( order_itr );
4405 jsonIteratorFree( class_itr );
4406 buffer_free( order_buf );
4408 buffer_free( group_buf );
4409 buffer_free( sql_buf );
4410 if( defaultselhash )
4411 jsonObjectFree( defaultselhash );
4415 const char* direction = NULL;
4416 if( onode->type == JSON_HASH ) {
4417 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4418 string = searchFieldTransform(
4420 osrfHashGet( field_list_def, order_itr->key ),
4424 if( ctx ) osrfAppSessionStatus(
4426 OSRF_STATUS_INTERNALSERVERERROR,
4427 "osrfMethodException",
4429 "Severe query error in ORDER BY clause -- "
4430 "see error log for more details"
4432 jsonIteratorFree( order_itr );
4433 jsonIteratorFree( class_itr );
4435 buffer_free( group_buf );
4436 buffer_free( order_buf);
4437 buffer_free( sql_buf );
4438 if( defaultselhash )
4439 jsonObjectFree( defaultselhash );
4443 growing_buffer* field_buf = buffer_init( 16 );
4444 buffer_fadd( field_buf, "\"%s\".%s",
4445 class_itr->key, order_itr->key );
4446 string = buffer_release( field_buf );
4449 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4450 const char* dir = jsonObjectGetString( tmp_const );
4451 if(!strncasecmp( dir, "d", 1 )) {
4452 direction = " DESC";
4458 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4459 osrfLogError( OSRF_LOG_MARK,
4460 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4461 modulename, json_type( onode->type ) );
4463 osrfAppSessionStatus(
4465 OSRF_STATUS_INTERNALSERVERERROR,
4466 "osrfMethodException",
4468 "Malformed ORDER BY clause -- see error log for more details"
4470 jsonIteratorFree( order_itr );
4471 jsonIteratorFree( class_itr );
4473 buffer_free( group_buf );
4474 buffer_free( order_buf );
4475 buffer_free( sql_buf );
4476 if( defaultselhash )
4477 jsonObjectFree( defaultselhash );
4481 string = strdup( order_itr->key );
4482 const char* dir = jsonObjectGetString( onode );
4483 if( !strncasecmp( dir, "d", 1 )) {
4484 direction = " DESC";
4491 OSRF_BUFFER_ADD( order_buf, ", " );
4493 order_buf = buffer_init( 128 );
4495 OSRF_BUFFER_ADD( order_buf, string );
4499 OSRF_BUFFER_ADD( order_buf, direction );
4503 jsonIteratorFree( order_itr );
4505 } else if( snode->type == JSON_ARRAY ) {
4507 // Array is a list of fields from the current class
4508 unsigned long order_idx = 0;
4509 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4511 const char* _f = jsonObjectGetString( onode );
4513 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4515 osrfLogError( OSRF_LOG_MARK,
4516 "%s: Invalid field \"%s\" in ORDER BY clause",
4519 osrfAppSessionStatus(
4521 OSRF_STATUS_INTERNALSERVERERROR,
4522 "osrfMethodException",
4524 "Invalid field in ORDER BY clause -- "
4525 "see error log for more details"
4527 jsonIteratorFree( class_itr );
4528 buffer_free( order_buf );
4530 buffer_free( group_buf );
4531 buffer_free( sql_buf );
4532 if( defaultselhash )
4533 jsonObjectFree( defaultselhash );
4535 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4536 osrfLogError( OSRF_LOG_MARK,
4537 "%s: Virtual field \"%s\" in ORDER BY clause",
4540 osrfAppSessionStatus(
4542 OSRF_STATUS_INTERNALSERVERERROR,
4543 "osrfMethodException",
4545 "Virtual field in ORDER BY clause -- "
4546 "see error log for more details"
4548 jsonIteratorFree( class_itr );
4549 buffer_free( order_buf );
4551 buffer_free( group_buf );
4552 buffer_free( sql_buf );
4553 if( defaultselhash )
4554 jsonObjectFree( defaultselhash );
4559 OSRF_BUFFER_ADD( order_buf, ", " );
4561 order_buf = buffer_init( 128 );
4563 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4567 // IT'S THE OOOOOOOOOOOLD STYLE!
4569 osrfLogError( OSRF_LOG_MARK,
4570 "%s: Possible SQL injection attempt; direct order by is not allowed",
4573 osrfAppSessionStatus(
4575 OSRF_STATUS_INTERNALSERVERERROR,
4576 "osrfMethodException",
4578 "Severe query error -- see error log for more details"
4583 buffer_free( group_buf );
4584 buffer_free( order_buf );
4585 buffer_free( sql_buf );
4586 if( defaultselhash )
4587 jsonObjectFree( defaultselhash );
4588 jsonIteratorFree( class_itr );
4592 jsonIteratorFree( class_itr );
4594 osrfLogError( OSRF_LOG_MARK,
4595 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4596 modulename, json_type( order_hash->type ) );
4598 osrfAppSessionStatus(
4600 OSRF_STATUS_INTERNALSERVERERROR,
4601 "osrfMethodException",
4603 "Malformed ORDER BY clause -- see error log for more details"
4605 buffer_free( order_buf );
4607 buffer_free( group_buf );
4608 buffer_free( sql_buf );
4609 if( defaultselhash )
4610 jsonObjectFree( defaultselhash );
4615 order_by_list = buffer_release( order_buf );
4619 string = buffer_release( group_buf );
4621 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4622 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4623 OSRF_BUFFER_ADD( sql_buf, string );
4628 if( having_buf && *having_buf ) {
4629 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4630 OSRF_BUFFER_ADD( sql_buf, having_buf );
4634 if( order_by_list ) {
4636 if( *order_by_list ) {
4637 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4638 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4641 free( order_by_list );
4645 const char* str = jsonObjectGetString( limit );
4646 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4650 const char* str = jsonObjectGetString( offset );
4651 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4654 if( !(flags & SUBSELECT) )
4655 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4657 if( defaultselhash )
4658 jsonObjectFree( defaultselhash );
4660 return buffer_release( sql_buf );
4662 } // end of SELECT()
4664 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4666 const char* locale = osrf_message_get_last_locale();
4668 osrfHash* fields = osrfHashGet( meta, "fields" );
4669 char* core_class = osrfHashGet( meta, "classname" );
4671 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4673 jsonObject* node = NULL;
4674 jsonObject* snode = NULL;
4675 jsonObject* onode = NULL;
4676 const jsonObject* _tmp = NULL;
4677 jsonObject* selhash = NULL;
4678 jsonObject* defaultselhash = NULL;
4680 growing_buffer* sql_buf = buffer_init( 128 );
4681 growing_buffer* select_buf = buffer_init( 128 );
4683 if( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4684 defaultselhash = jsonNewObjectType( JSON_HASH );
4685 selhash = defaultselhash;
4688 // If there's no SELECT list for the core class, build one
4689 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4690 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4692 // Add every non-virtual field to the field list
4693 osrfHash* field_def = NULL;
4694 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4695 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4696 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4697 const char* field = osrfHashIteratorKey( field_itr );
4698 jsonObjectPush( field_list, jsonNewObject( field ) );
4701 osrfHashIteratorFree( field_itr );
4702 jsonObjectSetKey( selhash, core_class, field_list );
4706 jsonIterator* class_itr = jsonNewIterator( selhash );
4707 while( (snode = jsonIteratorNext( class_itr )) ) {
4709 const char* cname = class_itr->key;
4710 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4714 if( strcmp(core_class,class_itr->key )) {
4718 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4719 if( !found->size ) {
4720 jsonObjectFree( found );
4724 jsonObjectFree( found );
4727 jsonIterator* select_itr = jsonNewIterator( snode );
4728 while( (node = jsonIteratorNext( select_itr )) ) {
4729 const char* item_str = jsonObjectGetString( node );
4730 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4731 char* fname = osrfHashGet( field, "name" );
4739 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4744 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4745 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4748 i18n = osrfHashGet( field, "i18n" );
4750 if( str_is_true( i18n ) ) {
4751 char* pkey = osrfHashGet( idlClass, "primarykey" );
4752 char* tname = osrfHashGet( idlClass, "tablename" );
4754 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4755 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4756 tname, cname, fname, pkey, cname, pkey, locale, fname );
4758 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4761 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4765 jsonIteratorFree( select_itr );
4768 jsonIteratorFree( class_itr );
4770 char* col_list = buffer_release( select_buf );
4771 char* table = getRelation( meta );
4773 table = strdup( "(null)" );
4775 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4779 // Clear the query stack (as a fail-safe precaution against possible
4780 // leftover garbage); then push the first query frame onto the stack.
4781 clear_query_stack();
4783 if( add_query_core( NULL, core_class ) ) {
4785 osrfAppSessionStatus(
4787 OSRF_STATUS_INTERNALSERVERERROR,
4788 "osrfMethodException",
4790 "Unable to build query frame for core class"
4796 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4797 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
4798 OSRF_BUFFER_ADD( sql_buf, join_clause );
4799 free( join_clause );
4802 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4803 modulename, OSRF_BUFFER_C_STR( sql_buf ));
4805 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
4807 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4809 osrfAppSessionStatus(
4811 OSRF_STATUS_INTERNALSERVERERROR,
4812 "osrfMethodException",
4814 "Severe query error -- see error log for more details"
4816 buffer_free( sql_buf );
4817 if( defaultselhash )
4818 jsonObjectFree( defaultselhash );
4819 clear_query_stack();
4822 buffer_add( sql_buf, pred );
4827 char* string = NULL;
4828 if( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4830 growing_buffer* order_buf = buffer_init( 128 );
4833 jsonIterator* class_itr = jsonNewIterator( _tmp );
4834 while( (snode = jsonIteratorNext( class_itr )) ) {
4836 if( !jsonObjectGetKeyConst( selhash,class_itr->key ))
4839 if( snode->type == JSON_HASH ) {
4841 jsonIterator* order_itr = jsonNewIterator( snode );
4842 while( (onode = jsonIteratorNext( order_itr )) ) {
4844 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4845 class_itr->key, order_itr->key );
4849 char* direction = NULL;
4850 if( onode->type == JSON_HASH ) {
4851 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4852 string = searchFieldTransform( class_itr->key, field_def, onode );
4854 osrfAppSessionStatus(
4856 OSRF_STATUS_INTERNALSERVERERROR,
4857 "osrfMethodException",
4859 "Severe query error in ORDER BY clause -- "
4860 "see error log for more details"
4862 jsonIteratorFree( order_itr );
4863 jsonIteratorFree( class_itr );
4864 buffer_free( order_buf );
4865 buffer_free( sql_buf );
4866 if( defaultselhash )
4867 jsonObjectFree( defaultselhash );
4868 clear_query_stack();
4872 growing_buffer* field_buf = buffer_init( 16 );
4873 buffer_fadd( field_buf, "\"%s\".%s",
4874 class_itr->key, order_itr->key );
4875 string = buffer_release( field_buf );
4878 if( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4879 const char* dir = jsonObjectGetString( _tmp );
4880 if(!strncasecmp( dir, "d", 1 )) {
4881 direction = " DESC";
4885 string = strdup( order_itr->key );
4886 const char* dir = jsonObjectGetString( onode );
4887 if( !strncasecmp( dir, "d", 1 )) {
4888 direction = " DESC";
4897 buffer_add( order_buf, ", " );
4900 buffer_add( order_buf, string );
4904 buffer_add( order_buf, direction );
4908 jsonIteratorFree( order_itr );
4911 const char* str = jsonObjectGetString( snode );
4912 buffer_add( order_buf, str );
4918 jsonIteratorFree( class_itr );
4920 string = buffer_release( order_buf );
4923 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4924 OSRF_BUFFER_ADD( sql_buf, string );
4930 if( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ) {
4931 const char* str = jsonObjectGetString( _tmp );
4939 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4941 const char* str = jsonObjectGetString( _tmp );
4950 if( defaultselhash )
4951 jsonObjectFree( defaultselhash );
4952 clear_query_stack();
4954 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4955 return buffer_release( sql_buf );
4958 int doJSONSearch ( osrfMethodContext* ctx ) {
4959 if(osrfMethodVerifyContext( ctx )) {
4960 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4964 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
4969 dbhandle = writehandle;
4971 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
4975 if( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4976 flags |= SELECT_DISTINCT;
4978 if( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4979 flags |= DISABLE_I18N;
4981 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
4982 clear_query_stack(); // a possibly needless precaution
4983 char* sql = buildQuery( ctx, hash, flags );
4984 clear_query_stack();
4991 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
4992 dbi_result result = dbi_conn_query( dbhandle, sql );
4995 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
4997 if( dbi_result_first_row( result )) {
4998 /* JSONify the result */
4999 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5002 jsonObject* return_val = oilsMakeJSONFromResult( result );
5003 osrfAppRespond( ctx, return_val );
5004 jsonObjectFree( return_val );
5005 } while( dbi_result_next_row( result ));
5008 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5011 osrfAppRespondComplete( ctx, NULL );
5013 /* clean up the query */
5014 dbi_result_free( result );
5018 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]", modulename, sql );
5019 osrfAppSessionStatus(
5021 OSRF_STATUS_INTERNALSERVERERROR,
5022 "osrfMethodException",
5024 "Severe query error -- see error log for more details"
5032 // The last parameter, err, is used to report an error condition by updating an int owned by
5033 // the calling code.
5035 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5036 // It is the responsibility of the calling code to initialize *err before the
5037 // call, so that it will be able to make sense of the result.
5039 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5040 // redundant anyway.
5041 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5042 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5045 dbhandle = writehandle;
5047 char* core_class = osrfHashGet( class_meta, "classname" );
5048 char* pkey = osrfHashGet( class_meta, "primarykey" );
5050 const jsonObject* _tmp;
5052 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5054 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5059 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5061 dbi_result result = dbi_conn_query( dbhandle, sql );
5062 if( NULL == result ) {
5063 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
5064 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql );
5065 osrfAppSessionStatus(
5067 OSRF_STATUS_INTERNALSERVERERROR,
5068 "osrfMethodException",
5070 "Severe query error -- see error log for more details"
5077 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5080 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5081 jsonObject* row_obj = NULL;
5083 if( dbi_result_first_row( result )) {
5085 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5086 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5087 // eliminate the duplicates.
5088 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5089 osrfHash* dedup = osrfNewHash();
5091 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5092 char* pkey_val = oilsFMGetString( row_obj, pkey );
5093 if( osrfHashGet( dedup, pkey_val ) ) {
5094 jsonObjectFree( row_obj );
5097 osrfHashSet( dedup, pkey_val, pkey_val );
5098 jsonObjectPush( res_list, row_obj );
5100 } while( dbi_result_next_row( result ));
5101 osrfHashFree( dedup );
5104 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5108 /* clean up the query */
5109 dbi_result_free( result );
5112 // If we're asked to flesh, and there's anything to flesh, then flesh.
5113 if( res_list->size && query_hash ) {
5114 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5116 // Get the flesh depth
5117 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5118 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5119 flesh_depth = max_flesh_depth;
5121 // We need a non-zero flesh depth, and a list of fields to flesh
5122 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5123 if( temp_blob && flesh_depth > 0 ) {
5125 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5126 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5128 osrfStringArray* link_fields = NULL;
5129 osrfHash* links = osrfHashGet( class_meta, "links" );
5131 // Make an osrfStringArray of the names of fields to be fleshed
5132 if( flesh_fields ) {
5133 if( flesh_fields->size == 1 ) {
5134 const char* _t = jsonObjectGetString(
5135 jsonObjectGetIndex( flesh_fields, 0 ) );
5136 if( !strcmp( _t, "*" ))
5137 link_fields = osrfHashKeys( links );
5140 if( !link_fields ) {
5142 link_fields = osrfNewStringArray( 1 );
5143 jsonIterator* _i = jsonNewIterator( flesh_fields );
5144 while ((_f = jsonIteratorNext( _i ))) {
5145 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5147 jsonIteratorFree( _i );
5151 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5153 // Iterate over the JSON_ARRAY of rows
5155 unsigned long res_idx = 0;
5156 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5159 const char* link_field;
5161 // Iterate over the list of fleshable fields
5162 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5164 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5166 osrfHash* kid_link = osrfHashGet( links, link_field );
5168 continue; // Not a link field; skip it
5170 osrfHash* field = osrfHashGet( fields, link_field );
5172 continue; // Not a field at all; skip it (IDL is ill-formed)
5174 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5175 osrfHashGet( kid_link, "class" ));
5177 continue; // The class it links to doesn't exist; skip it
5179 const char* reltype = osrfHashGet( kid_link, "reltype" );
5181 continue; // No reltype; skip it (IDL is ill-formed)
5183 osrfHash* value_field = field;
5185 if( !strcmp( reltype, "has_many" )
5186 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5187 value_field = osrfHashGet(
5188 fields, osrfHashGet( class_meta, "primarykey" ) );
5191 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5193 if( link_map->size > 0 ) {
5194 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5197 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5202 osrfHashGet( kid_link, "class" ),
5209 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5210 osrfHashGet( kid_link, "field" ),
5211 osrfHashGet( kid_link, "class" ),
5212 osrfHashGet( kid_link, "key" ),
5213 osrfHashGet( kid_link, "reltype" )
5216 const char* search_key = jsonObjectGetString(
5217 jsonObjectGetIndex( cur,
5218 atoi( osrfHashGet( value_field, "array_position" ) )
5223 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5227 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5229 // construct WHERE clause
5230 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5233 osrfHashGet( kid_link, "key" ),
5234 jsonNewObject( search_key )
5237 // construct the rest of the query, mostly
5238 // by copying pieces of the previous level of query
5239 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5240 jsonObjectSetKey( rest_of_query, "flesh",
5241 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5245 jsonObjectSetKey( rest_of_query, "flesh_fields",
5246 jsonObjectClone( flesh_blob ));
5248 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5249 jsonObjectSetKey( rest_of_query, "order_by",
5250 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5254 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5255 jsonObjectSetKey( rest_of_query, "select",
5256 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5260 // do the query, recursively, to expand the fleshable field
5261 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5262 where_clause, rest_of_query, err );
5264 jsonObjectFree( where_clause );
5265 jsonObjectFree( rest_of_query );
5268 osrfStringArrayFree( link_fields );
5269 jsonObjectFree( res_list );
5270 jsonObjectFree( flesh_blob );
5274 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5275 osrfHashGet( kid_link, "class" ), kids->size );
5277 // Traverse the result set
5278 jsonObject* X = NULL;
5279 if( link_map->size > 0 && kids->size > 0 ) {
5281 kids = jsonNewObjectType( JSON_ARRAY );
5283 jsonObject* _k_node;
5284 unsigned long res_idx = 0;
5285 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5291 (unsigned long) atoi(
5297 osrfHashGet( kid_link, "class" )
5301 osrfStringArrayGetString( link_map, 0 )
5309 } // end while loop traversing X
5312 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5313 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5314 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5315 osrfHashGet( kid_link, "field" ));
5318 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5319 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5323 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5325 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5326 osrfHashGet( kid_link, "field" ) );
5329 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5330 jsonObjectClone( kids )
5335 jsonObjectFree( kids );
5339 jsonObjectFree( kids );
5341 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5342 osrfHashGet( kid_link, "field" ) );
5343 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5345 } // end while loop traversing list of fleshable fields
5346 } // end while loop traversing res_list
5347 jsonObjectFree( flesh_blob );
5348 osrfStringArrayFree( link_fields );
5357 int doUpdate( osrfMethodContext* ctx ) {
5358 if( osrfMethodVerifyContext( ctx )) {
5359 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5364 timeout_needs_resetting = 1;
5366 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5368 jsonObject* target = NULL;
5370 target = jsonObjectGetIndex( ctx->params, 1 );
5372 target = jsonObjectGetIndex( ctx->params, 0 );
5374 if(!verifyObjectClass( ctx, target )) {
5375 osrfAppRespondComplete( ctx, NULL );
5379 if( getXactId( ctx ) == NULL ) {
5380 osrfAppSessionStatus(
5382 OSRF_STATUS_BADREQUEST,
5383 "osrfMethodException",
5385 "No active transaction -- required for UPDATE"
5387 osrfAppRespondComplete( ctx, NULL );
5391 // The following test is harmless but redundant. If a class is
5392 // readonly, we don't register an update method for it.
5393 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5394 osrfAppSessionStatus(
5396 OSRF_STATUS_BADREQUEST,
5397 "osrfMethodException",
5399 "Cannot UPDATE readonly class"
5401 osrfAppRespondComplete( ctx, NULL );
5405 dbhandle = writehandle;
5406 const char* trans_id = getXactId( ctx );
5408 // Set the last_xact_id
5409 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5411 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5412 trans_id, target->classname, index );
5413 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5416 char* pkey = osrfHashGet( meta, "primarykey" );
5417 osrfHash* fields = osrfHashGet( meta, "fields" );
5419 char* id = oilsFMGetString( target, pkey );
5423 "%s updating %s object with %s = %s",
5425 osrfHashGet( meta, "fieldmapper" ),
5430 growing_buffer* sql = buffer_init( 128 );
5431 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5434 osrfHash* field_def = NULL;
5435 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5436 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5438 // Skip virtual fields, and the primary key
5439 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5442 const char* field_name = osrfHashIteratorKey( field_itr );
5443 if( ! strcmp( field_name, pkey ) )
5446 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5448 int value_is_numeric = 0; // boolean
5450 if( field_object && field_object->classname ) {
5451 value = oilsFMGetString(
5453 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5455 } else if( field_object && JSON_BOOL == field_object->type ) {
5456 if( jsonBoolIsTrue( field_object ) )
5457 value = strdup( "t" );
5459 value = strdup( "f" );
5461 value = jsonObjectToSimpleString( field_object );
5462 if( field_object && JSON_NUMBER == field_object->type )
5463 value_is_numeric = 1;
5466 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5467 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5469 if( !field_object || field_object->type == JSON_NULL ) {
5470 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5471 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5475 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5476 buffer_fadd( sql, " %s = NULL", field_name );
5479 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5483 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5485 const char* numtype = get_datatype( field_def );
5486 if( !strncmp( numtype, "INT", 3 ) ) {
5487 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5488 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5489 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5491 // Must really be intended as a string, so quote it
5492 if( dbi_conn_quote_string( dbhandle, &value )) {
5493 buffer_fadd( sql, " %s = %s", field_name, value );
5495 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5496 modulename, value );
5497 osrfAppSessionStatus(
5499 OSRF_STATUS_INTERNALSERVERERROR,
5500 "osrfMethodException",
5502 "Error quoting string -- please see the error log for more details"
5506 osrfHashIteratorFree( field_itr );
5508 osrfAppRespondComplete( ctx, NULL );
5513 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5516 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5520 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5521 buffer_fadd( sql, " %s = %s", field_name, value );
5523 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5524 osrfAppSessionStatus(
5526 OSRF_STATUS_INTERNALSERVERERROR,
5527 "osrfMethodException",
5529 "Error quoting string -- please see the error log for more details"
5533 osrfHashIteratorFree( field_itr );
5535 osrfAppRespondComplete( ctx, NULL );
5544 osrfHashIteratorFree( field_itr );
5546 jsonObject* obj = jsonNewObject( id );
5548 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5549 dbi_conn_quote_string( dbhandle, &id );
5551 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5553 char* query = buffer_release( sql );
5554 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5556 dbi_result result = dbi_conn_query( dbhandle, query );
5560 jsonObjectFree( obj );
5561 obj = jsonNewObject( NULL );
5564 "%s ERROR updating %s object with %s = %s",
5566 osrfHashGet( meta, "fieldmapper" ),
5573 osrfAppRespondComplete( ctx, obj );
5574 jsonObjectFree( obj );
5578 int doDelete( osrfMethodContext* ctx ) {
5579 if( osrfMethodVerifyContext( ctx )) {
5580 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5585 timeout_needs_resetting = 1;
5587 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5589 if( getXactId( ctx ) == NULL ) {
5590 osrfAppSessionStatus(
5592 OSRF_STATUS_BADREQUEST,
5593 "osrfMethodException",
5595 "No active transaction -- required for DELETE"
5597 osrfAppRespondComplete( ctx, NULL );
5601 // The following test is harmless but redundant. If a class is
5602 // readonly, we don't register a delete method for it.
5603 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5604 osrfAppSessionStatus(
5606 OSRF_STATUS_BADREQUEST,
5607 "osrfMethodException",
5609 "Cannot DELETE readonly class"
5611 osrfAppRespondComplete( ctx, NULL );
5615 dbhandle = writehandle;
5617 char* pkey = osrfHashGet( meta, "primarykey" );
5624 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5625 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5626 osrfAppRespondComplete( ctx, NULL );
5630 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5632 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5633 osrfAppRespondComplete( ctx, NULL );
5636 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5641 "%s deleting %s object with %s = %s",
5643 osrfHashGet( meta, "fieldmapper" ),
5648 jsonObject* obj = jsonNewObject( id );
5650 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5651 dbi_conn_quote_string( writehandle, &id );
5653 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
5654 osrfHashGet( meta, "tablename" ), pkey, id );
5657 jsonObjectFree( obj );
5658 obj = jsonNewObject( NULL );
5661 "%s ERROR deleting %s object with %s = %s",
5663 osrfHashGet( meta, "fieldmapper" ),
5671 osrfAppRespondComplete( ctx, obj );
5672 jsonObjectFree( obj );
5677 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5678 @param result An iterator for a result set; we only look at the current row.
5679 @param @meta Pointer to the class metadata for the core class.
5680 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5682 If a column is not defined in the IDL, or if it has no array_position defined for it in
5683 the IDL, or if it is defined as virtual, ignore it.
5685 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5686 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5687 array_position in the IDL.
5689 A field defined in the IDL but not represented in the returned row will leave a hole
5690 in the JSON_ARRAY. In effect it will be treated as a null value.
5692 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5693 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5694 classname corresponding to the @a meta argument.
5696 The calling code is responsible for freeing the the resulting jsonObject by calling
5699 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5700 if( !( result && meta )) return NULL;
5702 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5703 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
5704 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
5706 osrfHash* fields = osrfHashGet( meta, "fields" );
5708 int columnIndex = 1;
5709 const char* columnName;
5711 /* cycle through the columns in the row returned from the database */
5712 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
5714 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5716 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5718 /* determine the field type and storage attributes */
5719 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
5720 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5722 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5723 // or if it has no sequence number there, or if it's virtual, skip it.
5724 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
5727 if( str_is_true( osrfHashGet( _f, "virtual" )))
5728 continue; // skip this column: IDL says it's virtual
5730 const char* pos = (char*) osrfHashGet( _f, "array_position" );
5731 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
5732 continue; // since we assign sequence numbers dynamically as we load the IDL.
5734 fmIndex = atoi( pos );
5735 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
5737 continue; // This field is not defined in the IDL
5740 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
5741 // sequence number from the IDL (which is likely to be different from the sequence
5742 // of columns in the SELECT clause).
5743 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5744 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
5749 case DBI_TYPE_INTEGER :
5751 if( attr & DBI_INTEGER_SIZE8 )
5752 jsonObjectSetIndex( object, fmIndex,
5753 jsonNewNumberObject(
5754 dbi_result_get_longlong_idx( result, columnIndex )));
5756 jsonObjectSetIndex( object, fmIndex,
5757 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
5761 case DBI_TYPE_DECIMAL :
5762 jsonObjectSetIndex( object, fmIndex,
5763 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
5766 case DBI_TYPE_STRING :
5771 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
5776 case DBI_TYPE_DATETIME : {
5778 char dt_string[ 256 ] = "";
5781 // Fetch the date column as a time_t
5782 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
5784 // Translate the time_t to a human-readable string
5785 if( !( attr & DBI_DATETIME_DATE )) {
5786 gmtime_r( &_tmp_dt, &gmdt );
5787 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
5788 } else if( !( attr & DBI_DATETIME_TIME )) {
5789 localtime_r( &_tmp_dt, &gmdt );
5790 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
5792 localtime_r( &_tmp_dt, &gmdt );
5793 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
5796 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
5800 case DBI_TYPE_BINARY :
5801 osrfLogError( OSRF_LOG_MARK,
5802 "Can't do binary at column %s : index %d", columnName, columnIndex );
5811 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5812 if( !result ) return NULL;
5814 jsonObject* object = jsonNewObject( NULL );
5817 char dt_string[ 256 ];
5821 int columnIndex = 1;
5823 unsigned short type;
5824 const char* columnName;
5826 /* cycle through the column list */
5827 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
5829 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5831 fmIndex = -1; // reset the position
5833 /* determine the field type and storage attributes */
5834 type = dbi_result_get_field_type_idx( result, columnIndex );
5835 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5837 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5838 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
5843 case DBI_TYPE_INTEGER :
5845 if( attr & DBI_INTEGER_SIZE8 )
5846 jsonObjectSetKey( object, columnName,
5847 jsonNewNumberObject( dbi_result_get_longlong_idx(
5848 result, columnIndex )) );
5850 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
5851 dbi_result_get_int_idx( result, columnIndex )) );
5854 case DBI_TYPE_DECIMAL :
5855 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
5856 dbi_result_get_double_idx( result, columnIndex )) );
5859 case DBI_TYPE_STRING :
5860 jsonObjectSetKey( object, columnName,
5861 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
5864 case DBI_TYPE_DATETIME :
5866 memset( dt_string, '\0', sizeof( dt_string ));
5867 memset( &gmdt, '\0', sizeof( gmdt ));
5869 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
5871 if( !( attr & DBI_DATETIME_DATE )) {
5872 gmtime_r( &_tmp_dt, &gmdt );
5873 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
5874 } else if( !( attr & DBI_DATETIME_TIME )) {
5875 localtime_r( &_tmp_dt, &gmdt );
5876 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
5878 localtime_r( &_tmp_dt, &gmdt );
5879 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
5882 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
5885 case DBI_TYPE_BINARY :
5886 osrfLogError( OSRF_LOG_MARK,
5887 "Can't do binary at column %s : index %d", columnName, columnIndex );
5891 } // end while loop traversing result
5896 // Interpret a string as true or false
5897 int str_is_true( const char* str ) {
5898 if( NULL == str || strcasecmp( str, "true" ) )
5904 // Interpret a jsonObject as true or false
5905 static int obj_is_true( const jsonObject* obj ) {
5908 else switch( obj->type )
5916 if( strcasecmp( obj->value.s, "true" ) )
5920 case JSON_NUMBER : // Support 1/0 for perl's sake
5921 if( jsonObjectGetNumber( obj ) == 1.0 )
5930 // Translate a numeric code into a text string identifying a type of
5931 // jsonObject. To be used for building error messages.
5932 static const char* json_type( int code ) {
5938 return "JSON_ARRAY";
5940 return "JSON_STRING";
5942 return "JSON_NUMBER";
5948 return "(unrecognized)";
5952 // Extract the "primitive" attribute from an IDL field definition.
5953 // If we haven't initialized the app, then we must be running in
5954 // some kind of testbed. In that case, default to "string".
5955 static const char* get_primitive( osrfHash* field ) {
5956 const char* s = osrfHashGet( field, "primitive" );
5958 if( child_initialized )
5961 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5963 osrfHashGet( field, "name" )
5971 // Extract the "datatype" attribute from an IDL field definition.
5972 // If we haven't initialized the app, then we must be running in
5973 // some kind of testbed. In that case, default to to NUMERIC,
5974 // since we look at the datatype only for numbers.
5975 static const char* get_datatype( osrfHash* field ) {
5976 const char* s = osrfHashGet( field, "datatype" );
5978 if( child_initialized )
5981 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5983 osrfHashGet( field, "name" )
5992 @brief Determine whether a string is potentially a valid SQL identifier.
5993 @param s The identifier to be tested.
5994 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
5996 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
5997 need to follow all the rules exactly, such as requiring that the first character not
6000 We allow leading and trailing white space. In between, we do not allow punctuation
6001 (except for underscores and dollar signs), control characters, or embedded white space.
6003 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6004 for the foreseeable future such quoted identifiers are not likely to be an issue.
6006 static int is_identifier( const char* s) {
6010 // Skip leading white space
6011 while( isspace( (unsigned char) *s ) )
6015 return 0; // Nothing but white space? Not okay.
6017 // Check each character until we reach white space or
6018 // end-of-string. Letters, digits, underscores, and
6019 // dollar signs are okay. With the exception of periods
6020 // (as in schema.identifier), control characters and other
6021 // punctuation characters are not okay. Anything else
6022 // is okay -- it could for example be part of a multibyte
6023 // UTF8 character such as a letter with diacritical marks,
6024 // and those are allowed.
6026 if( isalnum( (unsigned char) *s )
6030 ; // Fine; keep going
6031 else if( ispunct( (unsigned char) *s )
6032 || iscntrl( (unsigned char) *s ) )
6035 } while( *s && ! isspace( (unsigned char) *s ) );
6037 // If we found any white space in the above loop,
6038 // the rest had better be all white space.
6040 while( isspace( (unsigned char) *s ) )
6044 return 0; // White space was embedded within non-white space
6050 @brief Determine whether to accept a character string as a comparison operator.
6051 @param op The candidate comparison operator.
6052 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6054 We don't validate the operator for real. We just make sure that it doesn't contain
6055 any semicolons or white space (with special exceptions for a few specific operators).
6056 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6057 space but it's still not a valid operator, then the database will complain.
6059 Another approach would be to compare the string against a short list of approved operators.
6060 We don't do that because we want to allow custom operators like ">100*", which at this
6061 writing would be difficult or impossible to express otherwise in a JSON query.
6063 static int is_good_operator( const char* op ) {
6064 if( !op ) return 0; // Sanity check
6068 if( isspace( (unsigned char) *s ) ) {
6069 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6070 // and IS NOT DISTINCT FROM.
6071 if( !strcasecmp( op, "similar to" ) )
6073 else if( !strcasecmp( op, "is distinct from" ) )
6075 else if( !strcasecmp( op, "is not distinct from" ) )
6080 else if( ';' == *s )
6088 @name Query Frame Management
6090 The following machinery supports a stack of query frames for use by SELECT().
6092 A query frame caches information about one level of a SELECT query. When we enter
6093 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6095 The query frame stores information about the core class, and about any joined classes
6098 The main purpose is to map table aliases to classes and tables, so that a query can
6099 join to the same table more than once. A secondary goal is to reduce the number of
6100 lookups in the IDL by caching the results.
6104 #define STATIC_CLASS_INFO_COUNT 3
6106 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6109 @brief Allocate a ClassInfo as raw memory.
6110 @return Pointer to the newly allocated ClassInfo.
6112 Except for the in_use flag, which is used only by the allocation and deallocation
6113 logic, we don't initialize the ClassInfo here.
6115 static ClassInfo* allocate_class_info( void ) {
6116 // In order to reduce the number of mallocs and frees, we return a static
6117 // instance of ClassInfo, if we can find one that we're not already using.
6118 // We rely on the fact that the compiler will implicitly initialize the
6119 // static instances so that in_use == 0.
6122 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6123 if( ! static_class_info[ i ].in_use ) {
6124 static_class_info[ i ].in_use = 1;
6125 return static_class_info + i;
6129 // The static ones are all in use. Malloc one.
6131 return safe_malloc( sizeof( ClassInfo ) );
6135 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6136 @param info Pointer to the ClassInfo to be cleared.
6138 static void clear_class_info( ClassInfo* info ) {
6143 // Free any malloc'd strings
6145 if( info->alias != info->alias_store )
6146 free( info->alias );
6148 if( info->class_name != info->class_name_store )
6149 free( info->class_name );
6151 free( info->source_def );
6153 info->alias = info->class_name = info->source_def = NULL;
6158 @brief Free a ClassInfo and everything it owns.
6159 @param info Pointer to the ClassInfo to be freed.
6161 static void free_class_info( ClassInfo* info ) {
6166 clear_class_info( info );
6168 // If it's one of the static instances, just mark it as not in use
6171 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6172 if( info == static_class_info + i ) {
6173 static_class_info[ i ].in_use = 0;
6178 // Otherwise it must have been malloc'd, so free it
6184 @brief Populate an already-allocated ClassInfo.
6185 @param info Pointer to the ClassInfo to be populated.
6186 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6188 @param class Name of the class.
6189 @return Zero if successful, or 1 if not.
6191 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6192 the relevant portions of the IDL for the specified class.
6194 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6197 osrfLogError( OSRF_LOG_MARK,
6198 "%s ERROR: No ClassInfo available to populate", modulename );
6199 info->alias = info->class_name = info->source_def = NULL;
6200 info->class_def = info->fields = info->links = NULL;
6205 osrfLogError( OSRF_LOG_MARK,
6206 "%s ERROR: No class name provided for lookup", modulename );
6207 info->alias = info->class_name = info->source_def = NULL;
6208 info->class_def = info->fields = info->links = NULL;
6212 // Alias defaults to class name if not supplied
6213 if( ! alias || ! alias[ 0 ] )
6216 // Look up class info in the IDL
6217 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6219 osrfLogError( OSRF_LOG_MARK,
6220 "%s ERROR: Class %s not defined in IDL", modulename, class );
6221 info->alias = info->class_name = info->source_def = NULL;
6222 info->class_def = info->fields = info->links = NULL;
6224 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6225 osrfLogError( OSRF_LOG_MARK,
6226 "%s ERROR: Class %s is defined as virtual", modulename, class );
6227 info->alias = info->class_name = info->source_def = NULL;
6228 info->class_def = info->fields = info->links = NULL;
6232 osrfHash* links = osrfHashGet( class_def, "links" );
6234 osrfLogError( OSRF_LOG_MARK,
6235 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6236 info->alias = info->class_name = info->source_def = NULL;
6237 info->class_def = info->fields = info->links = NULL;
6241 osrfHash* fields = osrfHashGet( class_def, "fields" );
6243 osrfLogError( OSRF_LOG_MARK,
6244 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6245 info->alias = info->class_name = info->source_def = NULL;
6246 info->class_def = info->fields = info->links = NULL;
6250 char* source_def = getRelation( class_def );
6254 // We got everything we need, so populate the ClassInfo
6255 if( strlen( alias ) > ALIAS_STORE_SIZE )
6256 info->alias = strdup( alias );
6258 strcpy( info->alias_store, alias );
6259 info->alias = info->alias_store;
6262 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6263 info->class_name = strdup( class );
6265 strcpy( info->class_name_store, class );
6266 info->class_name = info->class_name_store;
6269 info->source_def = source_def;
6271 info->class_def = class_def;
6272 info->links = links;
6273 info->fields = fields;
6278 #define STATIC_FRAME_COUNT 3
6280 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6283 @brief Allocate a QueryFrame as raw memory.
6284 @return Pointer to the newly allocated QueryFrame.
6286 Except for the in_use flag, which is used only by the allocation and deallocation
6287 logic, we don't initialize the QueryFrame here.
6289 static QueryFrame* allocate_frame( void ) {
6290 // In order to reduce the number of mallocs and frees, we return a static
6291 // instance of QueryFrame, if we can find one that we're not already using.
6292 // We rely on the fact that the compiler will implicitly initialize the
6293 // static instances so that in_use == 0.
6296 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6297 if( ! static_frame[ i ].in_use ) {
6298 static_frame[ i ].in_use = 1;
6299 return static_frame + i;
6303 // The static ones are all in use. Malloc one.
6305 return safe_malloc( sizeof( QueryFrame ) );
6309 @brief Free a QueryFrame, and all the memory it owns.
6310 @param frame Pointer to the QueryFrame to be freed.
6312 static void free_query_frame( QueryFrame* frame ) {
6317 clear_class_info( &frame->core );
6319 // Free the join list
6321 ClassInfo* info = frame->join_list;
6324 free_class_info( info );
6328 frame->join_list = NULL;
6331 // If the frame is a static instance, just mark it as unused
6333 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6334 if( frame == static_frame + i ) {
6335 static_frame[ i ].in_use = 0;
6340 // Otherwise it must have been malloc'd, so free it
6346 @brief Search a given QueryFrame for a specified alias.
6347 @param frame Pointer to the QueryFrame to be searched.
6348 @param target The alias for which to search.
6349 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6351 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6352 if( ! frame || ! target ) {
6356 ClassInfo* found_class = NULL;
6358 if( !strcmp( target, frame->core.alias ) )
6359 return &(frame->core);
6361 ClassInfo* curr_class = frame->join_list;
6362 while( curr_class ) {
6363 if( strcmp( target, curr_class->alias ) )
6364 curr_class = curr_class->next;
6366 found_class = curr_class;
6376 @brief Push a new (blank) QueryFrame onto the stack.
6378 static void push_query_frame( void ) {
6379 QueryFrame* frame = allocate_frame();
6380 frame->join_list = NULL;
6381 frame->next = curr_query;
6383 // Initialize the ClassInfo for the core class
6384 ClassInfo* core = &frame->core;
6385 core->alias = core->class_name = core->source_def = NULL;
6386 core->class_def = core->fields = core->links = NULL;
6392 @brief Pop a QueryFrame off the stack and destroy it.
6394 static void pop_query_frame( void ) {
6399 QueryFrame* popped = curr_query;
6400 curr_query = popped->next;
6402 free_query_frame( popped );
6406 @brief Populate the ClassInfo for the core class.
6407 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6408 class name as an alias.
6409 @param class_name Name of the core class.
6410 @return Zero if successful, or 1 if not.
6412 Populate the ClassInfo of the core class with copies of the alias and class name, and
6413 with pointers to the relevant portions of the IDL for the core class.
6415 static int add_query_core( const char* alias, const char* class_name ) {
6418 if( ! curr_query ) {
6419 osrfLogError( OSRF_LOG_MARK,
6420 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6422 } else if( curr_query->core.alias ) {
6423 osrfLogError( OSRF_LOG_MARK,
6424 "%s ERROR: Core class %s already populated as %s",
6425 modulename, curr_query->core.class_name, curr_query->core.alias );
6429 build_class_info( &curr_query->core, alias, class_name );
6430 if( curr_query->core.alias )
6433 osrfLogError( OSRF_LOG_MARK,
6434 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6440 @brief Search the current QueryFrame for a specified alias.
6441 @param target The alias for which to search.
6442 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6444 static inline ClassInfo* search_alias( const char* target ) {
6445 return search_alias_in_frame( curr_query, target );
6449 @brief Search all levels of query for a specified alias, starting with the current query.
6450 @param target The alias for which to search.
6451 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6453 static ClassInfo* search_all_alias( const char* target ) {
6454 ClassInfo* found_class = NULL;
6455 QueryFrame* curr_frame = curr_query;
6457 while( curr_frame ) {
6458 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6461 curr_frame = curr_frame->next;
6468 @brief Add a class to the list of classes joined to the current query.
6469 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6470 the class name as an alias.
6471 @param classname The name of the class to be added.
6472 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6474 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6476 if( ! classname || ! *classname ) { // sanity check
6477 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6484 const ClassInfo* conflict = search_alias( alias );
6486 osrfLogError( OSRF_LOG_MARK,
6487 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6488 modulename, alias, conflict->class_name );
6492 ClassInfo* info = allocate_class_info();
6494 if( build_class_info( info, alias, classname ) ) {
6495 free_class_info( info );
6499 // Add the new ClassInfo to the join list of the current QueryFrame
6500 info->next = curr_query->join_list;
6501 curr_query->join_list = info;
6507 @brief Destroy all nodes on the query stack.
6509 static void clear_query_stack( void ) {