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 int obj_is_true( const jsonObject* obj );
102 static const char* json_type( int code );
103 static const char* get_primitive( osrfHash* field );
104 static const char* get_datatype( osrfHash* field );
105 static int is_identifier( const char* s );
106 static int is_good_operator( const char* op );
107 static void pop_query_frame( void );
108 static void push_query_frame( void );
109 static int add_query_core( const char* alias, const char* class_name );
110 static inline ClassInfo* search_alias( const char* target );
111 static ClassInfo* search_all_alias( const char* target );
112 static ClassInfo* add_joined_class( const char* alias, const char* classname );
113 static void clear_query_stack( void );
115 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
116 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
117 static char* org_tree_root( osrfMethodContext* ctx );
118 static jsonObject* single_hash( const char* key, const char* value );
120 static int child_initialized = 0; /* boolean */
122 static dbi_conn writehandle; /* our MASTER db connection */
123 static dbi_conn dbhandle; /* our CURRENT db connection */
124 //static osrfHash * readHandles;
126 // The following points to the top of a stack of QueryFrames. It's a little
127 // confusing because the top level of the query is at the bottom of the stack.
128 static QueryFrame* curr_query = NULL;
130 static dbi_conn writehandle; /* our MASTER db connection */
131 static dbi_conn dbhandle; /* our CURRENT db connection */
132 //static osrfHash * readHandles;
134 static int max_flesh_depth = 100;
136 static int enforce_pcrud = 0; // Boolean
137 static char* modulename = NULL;
140 @brief Connect to the database.
141 @return A database connection if successful, or NULL if not.
143 dbi_conn oilsConnectDB( const char* mod_name ) {
145 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
146 if( dbi_initialize( NULL ) == -1 ) {
147 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
150 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
152 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
153 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
154 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
155 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
156 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
157 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
159 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
160 dbi_conn handle = dbi_conn_new( driver );
163 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
166 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
168 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
169 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
171 if( host ) dbi_conn_set_option( handle, "host", host );
172 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
173 if( user ) dbi_conn_set_option( handle, "username", user );
174 if( pw ) dbi_conn_set_option( handle, "password", pw );
175 if( db ) dbi_conn_set_option( handle, "dbname", db );
183 if( dbi_conn_connect( handle ) < 0 ) {
185 if( dbi_conn_connect( handle ) < 0 ) {
187 dbi_conn_error( handle, &errmsg );
188 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", errmsg );
193 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
199 @brief Select some options.
200 @param module_name: Name of the server.
201 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
203 This source file is used (at this writing) to implement three different servers:
204 - open-ils.reporter-store
208 These servers behave mostly the same, but they implement different combinations of
209 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
211 Here we use the server name in messages to identify which kind of server issued them.
212 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
214 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
216 module_name = "open-ils.cstore"; // bulletproofing with a default
221 modulename = strdup( module_name );
222 enforce_pcrud = do_pcrud;
223 max_flesh_depth = flesh_depth;
227 @brief Install a database connection.
228 @param conn Pointer to a database connection.
230 In some contexts, @a conn may merely provide a driver so that we can process strings
231 properly, without providing an open database connection.
233 void oilsSetDBConnection( dbi_conn conn ) {
234 dbhandle = writehandle = conn;
238 @brief Get a table name, view name, or subquery for use in a FROM clause.
239 @param class Pointer to the IDL class entry.
240 @return A table name, a view name, or a subquery in parentheses.
242 In some cases the IDL defines a class, not with a table name or a view name, but with
243 a SELECT statement, which may be used as a subquery.
245 char* oilsGetRelation( osrfHash* classdef ) {
247 char* source_def = NULL;
248 const char* tabledef = osrfHashGet( classdef, "tablename" );
251 source_def = strdup( tabledef ); // Return the name of a table or view
253 tabledef = osrfHashGet( classdef, "source_definition" );
255 // Return a subquery, enclosed in parentheses
256 source_def = safe_malloc( strlen( tabledef ) + 3 );
257 source_def[ 0 ] = '(';
258 strcpy( source_def + 1, tabledef );
259 strcat( source_def, ")" );
261 // Not found: return an error
262 const char* classname = osrfHashGet( classdef, "classname" );
267 "%s ERROR No tablename or source_definition for class \"%s\"",
278 @brief Add datatypes from the database to the fields in the IDL.
279 @return Zero if successful, or 1 upon error.
281 For each relevant class in the IDL: ask the database for the datatype of every field.
282 In particular, determine which fields are text fields and which fields are numeric
283 fields, so that we know whether to enclose their values in quotes.
285 At this writing this function does not detect any errors, so it always returns zero.
287 int oilsExtendIDL( void ) {
288 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
289 osrfHash* class = NULL;
290 growing_buffer* query_buf = buffer_init( 64 );
292 // For each class in the IDL...
293 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
294 const char* classname = osrfHashIteratorKey( class_itr );
295 osrfHash* fields = osrfHashGet( class, "fields" );
297 // If the class is virtual, ignore it
298 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
299 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
303 char* tabledef = oilsGetRelation( class );
305 continue; // No such relation -- a query of it would be doomed to failure
307 buffer_reset( query_buf );
308 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
312 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
313 modulename, OSRF_BUFFER_C_STR( query_buf ) );
315 dbi_result result = dbi_conn_query( writehandle, OSRF_BUFFER_C_STR( query_buf ) );
319 const char* columnName;
320 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
322 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
325 /* fetch the fieldmapper index */
326 osrfHash* _f = osrfHashGet(fields, columnName);
329 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
331 /* determine the field type and storage attributes */
333 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
335 case DBI_TYPE_INTEGER : {
337 if( !osrfHashGet(_f, "primitive") )
338 osrfHashSet(_f, "number", "primitive");
340 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
341 if( attr & DBI_INTEGER_SIZE8 )
342 osrfHashSet( _f, "INT8", "datatype" );
344 osrfHashSet( _f, "INT", "datatype" );
347 case DBI_TYPE_DECIMAL :
348 if( !osrfHashGet( _f, "primitive" ))
349 osrfHashSet( _f, "number", "primitive" );
351 osrfHashSet( _f, "NUMERIC", "datatype" );
354 case DBI_TYPE_STRING :
355 if( !osrfHashGet( _f, "primitive" ))
356 osrfHashSet( _f, "string", "primitive" );
358 osrfHashSet( _f,"TEXT", "datatype" );
361 case DBI_TYPE_DATETIME :
362 if( !osrfHashGet( _f, "primitive" ))
363 osrfHashSet( _f, "string", "primitive" );
365 osrfHashSet( _f, "TIMESTAMP", "datatype" );
368 case DBI_TYPE_BINARY :
369 if( !osrfHashGet( _f, "primitive" ))
370 osrfHashSet( _f, "string", "primitive" );
372 osrfHashSet( _f, "BYTEA", "datatype" );
377 "Setting [%s] to primitive [%s] and datatype [%s]...",
379 osrfHashGet( _f, "primitive" ),
380 osrfHashGet( _f, "datatype" )
384 } // end while loop for traversing columns of result
385 dbi_result_free( result );
387 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]...", classname );
389 } // end for each class in IDL
391 buffer_free( query_buf );
392 osrfHashIteratorFree( class_itr );
393 child_initialized = 1;
398 @brief Free an osrfHash that stores a transaction ID.
399 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
401 This function is a callback, to be called by the application session when it ends.
402 The application session stores the osrfHash via an opaque pointer.
404 If the osrfHash contains an entry for the key "xact_id", it means that an
405 uncommitted transaction is pending. Roll it back.
407 void userDataFree( void* blob ) {
408 osrfHash* hash = (osrfHash*) blob;
409 if( osrfHashGet( hash, "xact_id" ) && writehandle )
410 dbi_conn_query( writehandle, "ROLLBACK;" );
412 osrfHashFree( hash );
416 @name Managing session data
417 @brief Maintain data stored via the userData pointer of the application session.
419 Currently, session-level data is stored in an osrfHash. Other arrangements are
420 possible, and some would be more efficient. The application session calls a
421 callback function to free userData before terminating.
423 Currently, the only data we store at the session level is the transaction id. By this
424 means we can ensure that any pending transactions are rolled back before the application
430 @brief Free an item in the application session's userData.
431 @param key The name of a key for an osrfHash.
432 @param item An opaque pointer to the item associated with the key.
434 We store an osrfHash as userData with the application session, and arrange (by
435 installing userDataFree() as a different callback) for the session to free that
436 osrfHash before terminating.
438 This function is a callback for freeing items in the osrfHash. Currently we store
440 - Transaction id of a pending transaction; a character string. Key: "xact_id".
441 - Authkey; a character string. Key: "authkey".
442 - User object from the authentication server; a jsonObject. Key: "user_login".
444 If we ever store anything else in userData, we will need to revisit this function so
445 that it will free whatever else needs freeing.
447 static void sessionDataFree( char* key, void* item ) {
448 if( !strcmp( key, "xact_id" )
449 || !strcmp( key, "authkey" ) ) {
451 } else if( !strcmp( key, "user_login" ) )
452 jsonObjectFree( (jsonObject*) item );
456 @brief Save a transaction id.
457 @param ctx Pointer to the method context.
459 Save the session_id of the current application session as a transaction id.
461 static void setXactId( osrfMethodContext* ctx ) {
462 if( ctx && ctx->session ) {
463 osrfAppSession* session = ctx->session;
465 osrfHash* cache = session->userData;
467 // If the session doesn't already have a hash, create one. Make sure
468 // that the application session frees the hash when it terminates.
469 if( NULL == cache ) {
470 session->userData = cache = osrfNewHash();
471 osrfHashSetCallback( cache, &sessionDataFree );
472 ctx->session->userDataFree = &userDataFree;
475 // Save the transaction id in the hash, with the key "xact_id"
476 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
481 @brief Get the transaction ID for the current transaction, if any.
482 @param ctx Pointer to the method context.
483 @return Pointer to the transaction ID.
485 The return value points to an internal buffer, and will become invalid upon issuing
486 a commit or rollback.
488 static inline const char* getXactId( osrfMethodContext* ctx ) {
489 if( ctx && ctx->session && ctx->session->userData )
490 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
496 @brief Clear the current transaction id.
497 @param ctx Pointer to the method context.
499 static inline void clearXactId( osrfMethodContext* ctx ) {
500 if( ctx && ctx->session && ctx->session->userData )
501 osrfHashRemove( ctx->session->userData, "xact_id" );
506 @brief Save the user's login in the userData for the current application session.
507 @param ctx Pointer to the method context.
508 @param user_login Pointer to the user login object to be cached (we cache the original,
511 If @a user_login is NULL, remove the user login if one is already cached.
513 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
514 if( ctx && ctx->session ) {
515 osrfAppSession* session = ctx->session;
517 osrfHash* cache = session->userData;
519 // If the session doesn't already have a hash, create one. Make sure
520 // that the application session frees the hash when it terminates.
521 if( NULL == cache ) {
522 session->userData = cache = osrfNewHash();
523 osrfHashSetCallback( cache, &sessionDataFree );
524 ctx->session->userDataFree = &userDataFree;
528 osrfHashSet( cache, user_login, "user_login" );
530 osrfHashRemove( cache, "user_login" );
535 @brief Get the user login object for the current application session, if any.
536 @param ctx Pointer to the method context.
537 @return Pointer to the user login object if found; otherwise NULL.
539 The user login object was returned from the authentication server, and then cached so
540 we don't have to call the authentication server again for the same user.
542 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
543 if( ctx && ctx->session && ctx->session->userData )
544 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
550 @brief Save a copy of an authkey in the userData of the current application session.
551 @param ctx Pointer to the method context.
552 @param authkey The authkey to be saved.
554 If @a authkey is NULL, remove the authkey if one is already cached.
556 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
557 if( ctx && ctx->session && authkey ) {
558 osrfAppSession* session = ctx->session;
559 osrfHash* cache = session->userData;
561 // If the session doesn't already have a hash, create one. Make sure
562 // that the application session frees the hash when it terminates.
563 if( NULL == cache ) {
564 session->userData = cache = osrfNewHash();
565 osrfHashSetCallback( cache, &sessionDataFree );
566 ctx->session->userDataFree = &userDataFree;
569 // Save the transaction id in the hash, with the key "xact_id"
570 if( authkey && *authkey )
571 osrfHashSet( cache, strdup( authkey ), "authkey" );
573 osrfHashRemove( cache, "authkey" );
578 @brief Reset the login timeout.
579 @param authkey The authentication key for the current login session.
580 @param now The current time.
581 @return Zero if successful, or 1 if not.
583 Tell the authentication server to reset the timeout so that the login session won't
584 expire for a while longer.
586 We could dispense with the @a now parameter by calling time(). But we just called
587 time() in order to decide whether to reset the timeout, so we might as well reuse
588 the result instead of calling time() again.
590 static int reset_timeout( const char* authkey, time_t now ) {
591 jsonObject* auth_object = jsonNewObject( authkey );
593 // Ask the authentication server to reset the timeout. It returns an event
594 // indicating success or failure.
595 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
596 "open-ils.auth.session.reset_timeout", auth_object );
597 jsonObjectFree( auth_object );
599 if( !result || result->type != JSON_HASH ) {
600 osrfLogError( OSRF_LOG_MARK,
601 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
602 jsonObjectFree( result );
603 return 1; // Not the right sort of object returned
606 const jsonObject* ilsevent = jsonObjectGetKey( result, "ilsevent" );
607 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
608 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
609 jsonObjectFree( result );
610 return 1; // Return code from method not available
613 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
614 const char* desc = jsonObjectGetString( jsonObjectGetKey( result, "desc" ) );
616 desc = "(No reason available)"; // failsafe; shouldn't happen
617 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
618 jsonObjectFree( result );
622 // Revise our local proxy for the timeout deadline
623 // by a smallish fraction of the timeout interval
624 const char* timeout = jsonObjectGetString( jsonObjectGetKey( result, "payload" ) );
626 timeout = "1"; // failsafe; shouldn't happen
627 time_next_reset = now + atoi( timeout ) / 15;
629 jsonObjectFree( result );
630 return 0; // Successfully reset timeout
634 @brief Get the authkey string for the current application session, if any.
635 @param ctx Pointer to the method context.
636 @return Pointer to the cached authkey if found; otherwise NULL.
638 If present, the authkey string was cached from a previous method call.
640 static const char* getAuthkey( osrfMethodContext* ctx ) {
641 if( ctx && ctx->session && ctx->session->userData ) {
642 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
644 // Possibly reset the authentication timeout to keep the login alive. We do so
645 // no more than once per method call, and not at all if it has been only a short
646 // time since the last reset.
648 // Here we reset explicitly, if at all. We also implicitly reset the timeout
649 // whenever we call the "open-ils.auth.session.retrieve" method.
650 if( timeout_needs_resetting ) {
651 time_t now = time( NULL );
652 if( now >= time_next_reset && reset_timeout( authkey, now ) )
653 authkey = NULL; // timeout has apparently expired already
656 timeout_needs_resetting = 0;
664 @brief Implement the transaction.begin method.
665 @param ctx Pointer to the method context.
666 @return Zero if successful, or -1 upon error.
668 Start a transaction. Save a transaction ID for future reference.
671 - authkey (PCRUD only)
673 Return to client: Transaction ID
675 int beginTransaction( osrfMethodContext* ctx ) {
676 if(osrfMethodVerifyContext( ctx )) {
677 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
681 if( enforce_pcrud ) {
682 timeout_needs_resetting = 1;
683 const jsonObject* user = verifyUserPCRUD( ctx );
688 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
690 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction", modulename );
691 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
692 "osrfMethodException", ctx->request, "Error starting transaction" );
696 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
697 osrfAppRespondComplete( ctx, ret );
698 jsonObjectFree( ret );
704 @brief Implement the savepoint.set method.
705 @param ctx Pointer to the method context.
706 @return Zero if successful, or -1 if not.
708 Issue a SAVEPOINT to the database server.
711 - authkey (PCRUD only)
714 Return to client: Savepoint name
716 int setSavepoint( osrfMethodContext* ctx ) {
717 if(osrfMethodVerifyContext( ctx )) {
718 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
723 if( enforce_pcrud ) {
725 timeout_needs_resetting = 1;
726 const jsonObject* user = verifyUserPCRUD( ctx );
731 // Verify that a transaction is pending
732 const char* trans_id = getXactId( ctx );
733 if( NULL == trans_id ) {
734 osrfAppSessionStatus(
736 OSRF_STATUS_INTERNALSERVERERROR,
737 "osrfMethodException",
739 "No active transaction -- required for savepoints"
744 // Get the savepoint name from the method params
745 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
747 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
751 "%s: Error creating savepoint %s in transaction %s",
756 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
757 "osrfMethodException", ctx->request, "Error creating savepoint" );
760 jsonObject* ret = jsonNewObject( spName );
761 osrfAppRespondComplete( ctx, ret );
762 jsonObjectFree( ret );
768 @brief Implement the savepoint.release method.
769 @param ctx Pointer to the method context.
770 @return Zero if successful, or -1 if not.
772 Issue a RELEASE SAVEPOINT to the database server.
775 - authkey (PCRUD only)
778 Return to client: Savepoint name
780 int releaseSavepoint( osrfMethodContext* ctx ) {
781 if(osrfMethodVerifyContext( ctx )) {
782 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
787 if( enforce_pcrud ) {
789 timeout_needs_resetting = 1;
790 const jsonObject* user = verifyUserPCRUD( ctx );
795 // Verify that a transaction is pending
796 const char* trans_id = getXactId( ctx );
797 if( NULL == trans_id ) {
798 osrfAppSessionStatus(
800 OSRF_STATUS_INTERNALSERVERERROR,
801 "osrfMethodException",
803 "No active transaction -- required for savepoints"
808 // Get the savepoint name from the method params
809 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
811 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
815 "%s: Error releasing savepoint %s in transaction %s",
820 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
821 "osrfMethodException", ctx->request, "Error releasing savepoint" );
824 jsonObject* ret = jsonNewObject( spName );
825 osrfAppRespondComplete( ctx, ret );
826 jsonObjectFree( ret );
832 @brief Implement the savepoint.rollback method.
833 @param ctx Pointer to the method context.
834 @return Zero if successful, or -1 if not.
836 Issue a ROLLBACK TO SAVEPOINT to the database server.
839 - authkey (PCRUD only)
842 Return to client: Savepoint name
844 int rollbackSavepoint( osrfMethodContext* ctx ) {
845 if(osrfMethodVerifyContext( ctx )) {
846 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
851 if( enforce_pcrud ) {
853 timeout_needs_resetting = 1;
854 const jsonObject* user = verifyUserPCRUD( ctx );
859 // Verify that a transaction is pending
860 const char* trans_id = getXactId( ctx );
861 if( NULL == trans_id ) {
862 osrfAppSessionStatus(
864 OSRF_STATUS_INTERNALSERVERERROR,
865 "osrfMethodException",
867 "No active transaction -- required for savepoints"
872 // Get the savepoint name from the method params
873 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
875 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
879 "%s: Error rolling back savepoint %s in transaction %s",
884 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
885 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
888 jsonObject* ret = jsonNewObject( spName );
889 osrfAppRespondComplete( ctx, ret );
890 jsonObjectFree( ret );
896 @brief Implement the transaction.commit method.
897 @param ctx Pointer to the method context.
898 @return Zero if successful, or -1 if not.
900 Issue a COMMIT to the database server.
903 - authkey (PCRUD only)
905 Return to client: Transaction ID.
907 int commitTransaction( osrfMethodContext* ctx ) {
908 if(osrfMethodVerifyContext( ctx )) {
909 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
913 if( enforce_pcrud ) {
914 timeout_needs_resetting = 1;
915 const jsonObject* user = verifyUserPCRUD( ctx );
920 // Verify that a transaction is pending
921 const char* trans_id = getXactId( ctx );
922 if( NULL == trans_id ) {
923 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
924 "osrfMethodException", ctx->request, "No active transaction to commit" );
928 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
930 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction", modulename );
931 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
932 "osrfMethodException", ctx->request, "Error committing transaction" );
935 jsonObject* ret = jsonNewObject( trans_id );
936 osrfAppRespondComplete( ctx, ret );
937 jsonObjectFree( ret );
944 @brief Implement the transaction.rollback method.
945 @param ctx Pointer to the method context.
946 @return Zero if successful, or -1 if not.
948 Issue a ROLLBACK to the database server.
951 - authkey (PCRUD only)
953 Return to client: Transaction ID
955 int rollbackTransaction( osrfMethodContext* ctx ) {
956 if( osrfMethodVerifyContext( ctx )) {
957 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
961 if( enforce_pcrud ) {
962 timeout_needs_resetting = 1;
963 const jsonObject* user = verifyUserPCRUD( ctx );
968 // Verify that a transaction is pending
969 const char* trans_id = getXactId( ctx );
970 if( NULL == trans_id ) {
971 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
972 "osrfMethodException", ctx->request, "No active transaction to roll back" );
976 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
978 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction", modulename );
979 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
980 "osrfMethodException", ctx->request, "Error rolling back transaction" );
983 jsonObject* ret = jsonNewObject( trans_id );
984 osrfAppRespondComplete( ctx, ret );
985 jsonObjectFree( ret );
992 @brief Implement the "search" method.
993 @param ctx Pointer to the method context.
994 @return Zero if successful, or -1 if not.
997 - authkey (PCRUD only)
998 - WHERE clause, as jsonObject
999 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1001 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1002 Optionally flesh linked fields.
1004 int doSearch( osrfMethodContext* ctx ) {
1005 if( osrfMethodVerifyContext( ctx )) {
1006 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1011 timeout_needs_resetting = 1;
1013 jsonObject* where_clause;
1014 jsonObject* rest_of_query;
1016 if( enforce_pcrud ) {
1017 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1018 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1020 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1021 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1024 // Get the class metadata
1025 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1026 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1030 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1032 osrfAppRespondComplete( ctx, NULL );
1036 // Return each row to the client (except that some may be suppressed by PCRUD)
1037 jsonObject* cur = 0;
1038 unsigned long res_idx = 0;
1039 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1040 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1042 osrfAppRespond( ctx, cur );
1044 jsonObjectFree( obj );
1046 osrfAppRespondComplete( ctx, NULL );
1051 @brief Implement the "id_list" method.
1052 @param ctx Pointer to the method context.
1053 @param err Pointer through which to return an error code.
1054 @return Zero if successful, or -1 if not.
1057 - authkey (PCRUD only)
1058 - WHERE clause, as jsonObject
1059 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1061 Return to client: The primary key values for all rows of the relevant class that
1062 satisfy a specified WHERE clause.
1064 This method relies on the assumption that every class has a primary key consisting of
1067 int doIdList( osrfMethodContext* ctx ) {
1068 if( osrfMethodVerifyContext( ctx )) {
1069 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1074 timeout_needs_resetting = 1;
1076 jsonObject* where_clause;
1077 jsonObject* rest_of_query;
1079 // We use the where clause without change. But we need to massage the rest of the
1080 // query, so we work with a copy of it instead of modifying the original.
1082 if( enforce_pcrud ) {
1083 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1084 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1086 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1087 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1090 // Eliminate certain SQL clauses, if present.
1091 if( rest_of_query ) {
1092 jsonObjectRemoveKey( rest_of_query, "select" );
1093 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1094 jsonObjectRemoveKey( rest_of_query, "flesh" );
1095 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
1097 rest_of_query = jsonNewObjectType( JSON_HASH );
1100 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1102 // Get the class metadata
1103 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1104 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1106 // Build a SELECT list containing just the primary key,
1107 // i.e. like { "classname":["keyname"] }
1108 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1110 // Load array with name of primary key
1111 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1112 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1113 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1115 jsonObjectSetKey( rest_of_query, "select", select_clause );
1120 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1122 jsonObjectFree( rest_of_query );
1124 osrfAppRespondComplete( ctx, NULL );
1128 // Return each primary key value to the client
1130 unsigned long res_idx = 0;
1131 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1132 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1133 continue; // Suppress due to lack of permission
1135 osrfAppRespond( ctx,
1136 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1139 jsonObjectFree( obj );
1140 osrfAppRespondComplete( ctx, NULL );
1145 @brief Verify that we have a valid class reference.
1146 @param ctx Pointer to the method context.
1147 @param param Pointer to the method parameters.
1148 @return 1 if the class reference is valid, or zero if it isn't.
1150 The class of the method params must match the class to which the method id devoted.
1151 For PCRUD there are additional restrictions.
1153 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1155 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1156 osrfHash* class = osrfHashGet( method_meta, "class" );
1158 // Compare the method's class to the parameters' class
1159 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1161 // Oops -- they don't match. Complain.
1162 growing_buffer* msg = buffer_init( 128 );
1165 "%s: %s method for type %s was passed a %s",
1167 osrfHashGet( method_meta, "methodtype" ),
1168 osrfHashGet( class, "classname" ),
1169 param->classname ? param->classname : "(null)"
1172 char* m = buffer_release( msg );
1173 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1181 return verifyObjectPCRUD( ctx, param );
1187 @brief (PCRUD only) Verify that the user is properly logged in.
1188 @param ctx Pointer to the method context.
1189 @return If the user is logged in, a pointer to the user object from the authentication
1190 server; otherwise NULL.
1192 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1194 // Get the authkey (the first method parameter)
1195 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1197 // See if we have the same authkey, and a user object,
1198 // locally cached from a previous call
1199 const char* cached_authkey = getAuthkey( ctx );
1200 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1201 const jsonObject* cached_user = getUserLogin( ctx );
1206 // We have no matching authentication data in the cache. Authenticate from scratch.
1207 jsonObject* auth_object = jsonNewObject( auth );
1209 // Fetch the user object from the authentication server
1210 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1212 jsonObjectFree( auth_object );
1214 if( !user->classname || strcmp(user->classname, "au" )) {
1216 growing_buffer* msg = buffer_init( 128 );
1219 "%s: permacrud received a bad auth token: %s",
1224 char* m = buffer_release( msg );
1225 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1229 jsonObjectFree( user );
1233 setUserLogin( ctx, user );
1234 setAuthkey( ctx, auth );
1236 // Allow ourselves up to a second before we have to reset the login timeout.
1237 // It would be nice to use some fraction of the timeout interval enforced by the
1238 // authentication server, but that value is not readily available at this point.
1239 // Instead, we use a conservative default interval.
1240 time_next_reset = time( NULL ) + 1;
1245 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1247 dbhandle = writehandle;
1249 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1250 osrfHash* class = osrfHashGet( method_metadata, "class" );
1251 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1254 if( *method_type == 's' || *method_type == 'i' ) {
1255 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1256 } else if( *method_type == 'u' || *method_type == 'd' ) {
1257 fetch = 1; // MUST go to the db for the object for update and delete
1260 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1263 // No permacrud for this method type on this class
1265 growing_buffer* msg = buffer_init( 128 );
1268 "%s: %s on class %s has no permacrud IDL entry",
1270 osrfHashGet( method_metadata, "methodtype" ),
1271 osrfHashGet( class, "classname" )
1274 char* m = buffer_release( msg );
1275 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1276 "osrfMethodException", ctx->request, m );
1283 const jsonObject* user = verifyUserPCRUD( ctx );
1287 int userid = atoi( oilsFMGetString( user, "id" ) );
1289 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1290 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1291 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1293 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1296 char* pkey_value = NULL;
1297 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1298 osrfLogDebug( OSRF_LOG_MARK,
1299 "global-level permissions required, fetching top of the org tree" );
1301 // check for perm at top of org tree
1302 char* org_tree_root_id = org_tree_root( ctx );
1303 if( org_tree_root_id ) {
1304 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1305 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1307 osrfStringArrayFree( context_org_array );
1312 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1313 "fetching context org ids" );
1314 const char* pkey = osrfHashGet( class, "primarykey" );
1315 jsonObject *param = NULL;
1317 if( obj->classname ) {
1318 pkey_value = oilsFMGetString( obj, pkey );
1320 param = jsonObjectClone( obj );
1321 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1324 pkey_value = jsonObjectToSimpleString( obj );
1326 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1327 "of %s and retrieving from the database", pkey_value );
1331 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1332 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1333 jsonObjectFree( _tmp_params );
1335 param = jsonObjectExtractIndex( _list, 0 );
1336 jsonObjectFree( _list );
1340 osrfLogDebug( OSRF_LOG_MARK,
1341 "Object not found in the database with primary key %s of %s",
1344 growing_buffer* msg = buffer_init( 128 );
1347 "%s: no object found with primary key %s of %s",
1353 char* m = buffer_release( msg );
1354 osrfAppSessionStatus(
1356 OSRF_STATUS_INTERNALSERVERERROR,
1357 "osrfMethodException",
1369 if( local_context->size > 0 ) {
1370 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1371 local_context->size );
1373 const char* lcontext = NULL;
1374 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1375 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1378 "adding class-local field %s (value: %s) to the context org list",
1380 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1385 if( foreign_context ) {
1386 unsigned long class_count = osrfHashGetCount( foreign_context );
1387 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1389 if( class_count > 0 ) {
1391 osrfHash* fcontext = NULL;
1392 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1393 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1394 const char* class_name = osrfHashIteratorKey( class_itr );
1395 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1399 "%d foreign context fields(s) specified for class %s",
1400 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1404 char* foreign_pkey = osrfHashGet( fcontext, "field" );
1405 char* foreign_pkey_value =
1406 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1408 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1410 jsonObject* _list = doFieldmapperSearch(
1411 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1413 jsonObject* _fparam = jsonObjectClone( jsonObjectGetIndex( _list, 0 ));
1414 jsonObjectFree( _tmp_params );
1415 jsonObjectFree( _list );
1417 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1419 if( _fparam && jump_list ) {
1420 const char* flink = NULL;
1422 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1423 free( foreign_pkey_value );
1425 osrfHash* foreign_link_hash =
1426 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1428 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1429 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1431 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1433 _list = doFieldmapperSearch(
1435 osrfHashGet( oilsIDL(),
1436 osrfHashGet( foreign_link_hash, "class" ) ),
1442 _fparam = jsonObjectClone( jsonObjectGetIndex( _list, 0 ));
1443 jsonObjectFree( _tmp_params );
1444 jsonObjectFree( _list );
1450 growing_buffer* msg = buffer_init( 128 );
1453 "%s: no object found with primary key %s of %s",
1459 char* m = buffer_release( msg );
1460 osrfAppSessionStatus(
1462 OSRF_STATUS_INTERNALSERVERERROR,
1463 "osrfMethodException",
1469 osrfHashIteratorFree( class_itr );
1470 free( foreign_pkey_value );
1471 jsonObjectFree( param );
1476 free( foreign_pkey_value );
1479 const char* foreign_field = NULL;
1480 while ( (foreign_field = osrfStringArrayGetString(
1481 osrfHashGet(fcontext,"context" ), j++ )) ) {
1482 osrfStringArrayAdd( context_org_array,
1483 oilsFMGetString( _fparam, foreign_field ) );
1486 "adding foreign class %s field %s (value: %s) to the context org list",
1489 osrfStringArrayGetString(
1490 context_org_array, context_org_array->size - 1 )
1494 jsonObjectFree( _fparam );
1497 osrfHashIteratorFree( class_itr );
1501 jsonObjectFree( param );
1504 const char* context_org = NULL;
1505 const char* perm = NULL;
1508 if( permission->size == 0 ) {
1509 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1514 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1516 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1522 "Checking object permission [%s] for user %d "
1523 "on object %s (class %s) at org %d",
1527 osrfHashGet( class, "classname" ),
1531 result = dbi_conn_queryf(
1533 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1536 osrfHashGet( class, "classname" ),
1544 "Received a result for object permission [%s] "
1545 "for user %d on object %s (class %s) at org %d",
1549 osrfHashGet( class, "classname" ),
1553 if( dbi_result_first_row( result )) {
1554 jsonObject* return_val = oilsMakeJSONFromResult( result );
1555 const char* has_perm = jsonObjectGetString(
1556 jsonObjectGetKeyConst( return_val, "has_perm" ));
1560 "Status of object permission [%s] for user %d "
1561 "on object %s (class %s) at org %d is %s",
1565 osrfHashGet(class, "classname"),
1570 if( *has_perm == 't' )
1572 jsonObjectFree( return_val );
1575 dbi_result_free( result );
1581 osrfLogDebug( OSRF_LOG_MARK,
1582 "Checking non-object permission [%s] for user %d at org %d",
1583 perm, userid, atoi(context_org) );
1584 result = dbi_conn_queryf(
1586 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1593 osrfLogDebug( OSRF_LOG_MARK,
1594 "Received a result for permission [%s] for user %d at org %d",
1595 perm, userid, atoi( context_org ));
1596 if( dbi_result_first_row( result )) {
1597 jsonObject* return_val = oilsMakeJSONFromResult( result );
1598 const char* has_perm = jsonObjectGetString(
1599 jsonObjectGetKeyConst( return_val, "has_perm" ));
1600 osrfLogDebug( OSRF_LOG_MARK,
1601 "Status of permission [%s] for user %d at org %d is [%s]",
1602 perm, userid, atoi( context_org ), has_perm );
1603 if( *has_perm == 't' )
1605 jsonObjectFree( return_val );
1608 dbi_result_free( result );
1620 osrfStringArrayFree( context_org_array );
1626 @brief Look up the root of the org_unit tree.
1627 @param ctx Pointer to the method context.
1628 @return The id of the root org unit, as a character string.
1630 Query actor.org_unit where parent_ou is null, and return the id as a string.
1632 This function assumes that there is only one root org unit, i.e. that we
1633 have a single tree, not a forest.
1635 The calling code is responsible for freeing the returned string.
1637 static char* org_tree_root( osrfMethodContext* ctx ) {
1639 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1640 static time_t last_lookup_time = 0;
1641 time_t current_time = time( NULL );
1643 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1644 // We successfully looked this up less than an hour ago.
1645 // It's not likely to have changed since then.
1646 return strdup( cached_root_id );
1648 last_lookup_time = current_time;
1651 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1652 jsonObject* result = doFieldmapperSearch(
1653 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1654 jsonObjectFree( where_clause );
1656 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1659 jsonObjectFree( result );
1661 growing_buffer* msg = buffer_init( 128 );
1662 OSRF_BUFFER_ADD( msg, modulename );
1663 OSRF_BUFFER_ADD( msg,
1664 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1666 char* m = buffer_release( msg );
1667 osrfAppSessionStatus( ctx->session,
1668 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1671 cached_root_id[ 0 ] = '\0';
1675 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1676 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1678 jsonObjectFree( result );
1680 strcpy( cached_root_id, root_org_unit_id );
1681 return root_org_unit_id;
1685 @brief Create a JSON_HASH with a single key/value pair.
1686 @param key The key of the key/value pair.
1687 @param value the value of the key/value pair.
1688 @return Pointer to a newly created jsonObject of type JSON_HASH.
1690 The value of the key/value is either a string or (if @a value is NULL) a null.
1692 static jsonObject* single_hash( const char* key, const char* value ) {
1694 if( ! key ) key = "";
1696 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1697 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1702 int doCreate( osrfMethodContext* ctx ) {
1703 if(osrfMethodVerifyContext( ctx )) {
1704 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1709 timeout_needs_resetting = 1;
1711 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1712 jsonObject* target = NULL;
1713 jsonObject* options = NULL;
1715 if( enforce_pcrud ) {
1716 target = jsonObjectGetIndex( ctx->params, 1 );
1717 options = jsonObjectGetIndex( ctx->params, 2 );
1719 target = jsonObjectGetIndex( ctx->params, 0 );
1720 options = jsonObjectGetIndex( ctx->params, 1 );
1723 if( !verifyObjectClass( ctx, target )) {
1724 osrfAppRespondComplete( ctx, NULL );
1728 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1730 const char* trans_id = getXactId( ctx );
1732 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1734 osrfAppSessionStatus(
1736 OSRF_STATUS_BADREQUEST,
1737 "osrfMethodException",
1739 "No active transaction -- required for CREATE"
1741 osrfAppRespondComplete( ctx, NULL );
1745 // The following test is harmless but redundant. If a class is
1746 // readonly, we don't register a create method for it.
1747 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1748 osrfAppSessionStatus(
1750 OSRF_STATUS_BADREQUEST,
1751 "osrfMethodException",
1753 "Cannot INSERT readonly class"
1755 osrfAppRespondComplete( ctx, NULL );
1759 // Set the last_xact_id
1760 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1762 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1763 trans_id, target->classname, index);
1764 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1767 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1769 dbhandle = writehandle;
1771 osrfHash* fields = osrfHashGet( meta, "fields" );
1772 char* pkey = osrfHashGet( meta, "primarykey" );
1773 char* seq = osrfHashGet( meta, "sequence" );
1775 growing_buffer* table_buf = buffer_init( 128 );
1776 growing_buffer* col_buf = buffer_init( 128 );
1777 growing_buffer* val_buf = buffer_init( 128 );
1779 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1780 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1781 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1782 buffer_add( val_buf,"VALUES (" );
1786 osrfHash* field = NULL;
1787 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1788 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1790 const char* field_name = osrfHashIteratorKey( field_itr );
1792 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1795 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1798 if( field_object && field_object->classname ) {
1799 value = oilsFMGetString(
1801 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
1803 } else if( field_object && JSON_BOOL == field_object->type ) {
1804 if( jsonBoolIsTrue( field_object ) )
1805 value = strdup( "t" );
1807 value = strdup( "f" );
1809 value = jsonObjectToSimpleString( field_object );
1815 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1816 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1819 buffer_add( col_buf, field_name );
1821 if( !field_object || field_object->type == JSON_NULL ) {
1822 buffer_add( val_buf, "DEFAULT" );
1824 } else if( !strcmp( get_primitive( field ), "number" )) {
1825 const char* numtype = get_datatype( field );
1826 if( !strcmp( numtype, "INT8" )) {
1827 buffer_fadd( val_buf, "%lld", atoll( value ));
1829 } else if( !strcmp( numtype, "INT" )) {
1830 buffer_fadd( val_buf, "%d", atoi( value ));
1832 } else if( !strcmp( numtype, "NUMERIC" )) {
1833 buffer_fadd( val_buf, "%f", atof( value ));
1836 if( dbi_conn_quote_string( writehandle, &value )) {
1837 OSRF_BUFFER_ADD( val_buf, value );
1840 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
1841 osrfAppSessionStatus(
1843 OSRF_STATUS_INTERNALSERVERERROR,
1844 "osrfMethodException",
1846 "Error quoting string -- please see the error log for more details"
1849 buffer_free( table_buf );
1850 buffer_free( col_buf );
1851 buffer_free( val_buf );
1852 osrfAppRespondComplete( ctx, NULL );
1860 osrfHashIteratorFree( field_itr );
1862 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1863 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1865 char* table_str = buffer_release( table_buf );
1866 char* col_str = buffer_release( col_buf );
1867 char* val_str = buffer_release( val_buf );
1868 growing_buffer* sql = buffer_init( 128 );
1869 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1874 char* query = buffer_release( sql );
1876 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
1878 jsonObject* obj = NULL;
1881 dbi_result result = dbi_conn_query( writehandle, query );
1883 obj = jsonNewObject( NULL );
1886 "%s ERROR inserting %s object using query [%s]",
1888 osrfHashGet(meta, "fieldmapper"),
1891 osrfAppSessionStatus(
1893 OSRF_STATUS_INTERNALSERVERERROR,
1894 "osrfMethodException",
1896 "INSERT error -- please see the error log for more details"
1901 char* id = oilsFMGetString( target, pkey );
1903 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
1904 growing_buffer* _id = buffer_init( 10 );
1905 buffer_fadd( _id, "%lld", new_id );
1906 id = buffer_release( _id );
1909 // Find quietness specification, if present
1910 const char* quiet_str = NULL;
1912 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1914 quiet_str = jsonObjectGetString( quiet_obj );
1917 if( str_is_true( quiet_str )) { // if quietness is specified
1918 obj = jsonNewObject( id );
1922 // Fetch the row that we just inserted, so that we can return it to the client
1923 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1924 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
1927 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
1931 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
1933 jsonObjectFree( list );
1934 jsonObjectFree( where_clause );
1941 osrfAppRespondComplete( ctx, obj );
1942 jsonObjectFree( obj );
1947 @brief Implement the retrieve method.
1948 @param ctx Pointer to the method context.
1949 @param err Pointer through which to return an error code.
1950 @return If successful, a pointer to the result to be returned to the client;
1953 From the method's class, fetch a row with a specified value in the primary key. This
1954 method relies on the database design convention that a primary key consists of a single
1958 - authkey (PCRUD only)
1959 - value of the primary key for the desired row, for building the WHERE clause
1960 - a JSON_HASH containing any other SQL clauses: select, join, etc.
1962 Return to client: One row from the query.
1964 int doRetrieve( osrfMethodContext* ctx ) {
1965 if(osrfMethodVerifyContext( ctx )) {
1966 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1971 timeout_needs_resetting = 1;
1976 if( enforce_pcrud ) {
1981 // Get the class metadata
1982 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1984 // Get the value of the primary key, from a method parameter
1985 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
1989 "%s retrieving %s object with primary key value of %s",
1991 osrfHashGet( class_def, "fieldmapper" ),
1992 jsonObjectGetString( id_obj )
1995 // Build a WHERE clause based on the key value
1996 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1999 osrfHashGet( class_def, "primarykey" ), // name of key column
2000 jsonObjectClone( id_obj ) // value of key column
2003 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2007 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2009 jsonObjectFree( where_clause );
2011 osrfAppRespondComplete( ctx, NULL );
2015 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2016 jsonObjectFree( list );
2018 if( enforce_pcrud ) {
2019 if(!verifyObjectPCRUD( ctx, obj )) {
2020 jsonObjectFree( obj );
2022 growing_buffer* msg = buffer_init( 128 );
2023 OSRF_BUFFER_ADD( msg, modulename );
2024 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2026 char* m = buffer_release( msg );
2027 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2031 osrfAppRespondComplete( ctx, NULL );
2036 osrfAppRespondComplete( ctx, obj );
2037 jsonObjectFree( obj );
2042 @brief Translate a numeric value to a string representation for the database.
2043 @param field Pointer to the IDL field definition.
2044 @param value Pointer to a jsonObject holding the value of a field.
2045 @return Pointer to a newly allocated string.
2047 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2048 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2049 or (what is worse) valid SQL that is wrong.
2051 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2053 The calling code is responsible for freeing the resulting string by calling free().
2055 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2056 growing_buffer* val_buf = buffer_init( 32 );
2057 const char* numtype = get_datatype( field );
2059 // For historical reasons the following contains cruft that could be cleaned up.
2060 if( !strncmp( numtype, "INT", 3 ) ) {
2061 if( value->type == JSON_NUMBER )
2062 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2063 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2065 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2068 } else if( !strcmp( numtype, "NUMERIC" )) {
2069 if( value->type == JSON_NUMBER )
2070 buffer_fadd( val_buf, jsonObjectGetString( value ));
2072 buffer_fadd( val_buf, jsonObjectGetString( value ));
2076 // Presumably this was really intended to be a string, so quote it
2077 char* str = jsonObjectToSimpleString( value );
2078 if( dbi_conn_quote_string( dbhandle, &str )) {
2079 OSRF_BUFFER_ADD( val_buf, str );
2082 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2084 buffer_free( val_buf );
2089 return buffer_release( val_buf );
2092 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2093 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2094 growing_buffer* sql_buf = buffer_init( 32 );
2100 osrfHashGet( field, "name" )
2104 buffer_add( sql_buf, "IN (" );
2105 } else if( !strcasecmp( op,"not in" )) {
2106 buffer_add( sql_buf, "NOT IN (" );
2108 buffer_add( sql_buf, "IN (" );
2111 if( node->type == JSON_HASH ) {
2112 // subquery predicate
2113 char* subpred = buildQuery( ctx, node, SUBSELECT );
2115 buffer_free( sql_buf );
2119 buffer_add( sql_buf, subpred );
2122 } else if( node->type == JSON_ARRAY ) {
2123 // literal value list
2124 int in_item_index = 0;
2125 int in_item_first = 1;
2126 const jsonObject* in_item;
2127 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2132 buffer_add( sql_buf, ", " );
2135 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2136 osrfLogError( OSRF_LOG_MARK,
2137 "%s: Expected string or number within IN list; found %s",
2138 modulename, json_type( in_item->type ) );
2139 buffer_free( sql_buf );
2143 // Append the literal value -- quoted if not a number
2144 if( JSON_NUMBER == in_item->type ) {
2145 char* val = jsonNumberToDBString( field, in_item );
2146 OSRF_BUFFER_ADD( sql_buf, val );
2149 } else if( !strcmp( get_primitive( field ), "number" )) {
2150 char* val = jsonNumberToDBString( field, in_item );
2151 OSRF_BUFFER_ADD( sql_buf, val );
2155 char* key_string = jsonObjectToSimpleString( in_item );
2156 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2157 OSRF_BUFFER_ADD( sql_buf, key_string );
2160 osrfLogError( OSRF_LOG_MARK,
2161 "%s: Error quoting key string [%s]", modulename, key_string );
2163 buffer_free( sql_buf );
2169 if( in_item_first ) {
2170 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2171 buffer_free( sql_buf );
2175 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2176 modulename, json_type( node->type ));
2177 buffer_free( sql_buf );
2181 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2183 return buffer_release( sql_buf );
2186 // Receive a JSON_ARRAY representing a function call. The first
2187 // entry in the array is the function name. The rest are parameters.
2188 static char* searchValueTransform( const jsonObject* array ) {
2190 if( array->size < 1 ) {
2191 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2195 // Get the function name
2196 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2197 if( func_item->type != JSON_STRING ) {
2198 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2199 modulename, json_type( func_item->type ));
2203 growing_buffer* sql_buf = buffer_init( 32 );
2205 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2206 OSRF_BUFFER_ADD( sql_buf, "( " );
2208 // Get the parameters
2209 int func_item_index = 1; // We already grabbed the zeroth entry
2210 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2212 // Add a separator comma, if we need one
2213 if( func_item_index > 2 )
2214 buffer_add( sql_buf, ", " );
2216 // Add the current parameter
2217 if( func_item->type == JSON_NULL ) {
2218 buffer_add( sql_buf, "NULL" );
2220 char* val = jsonObjectToSimpleString( func_item );
2221 if( dbi_conn_quote_string( dbhandle, &val )) {
2222 OSRF_BUFFER_ADD( sql_buf, val );
2225 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2227 buffer_free( sql_buf );
2234 buffer_add( sql_buf, " )" );
2236 return buffer_release( sql_buf );
2239 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2240 const jsonObject* node, const char* op ) {
2242 if( ! is_good_operator( op ) ) {
2243 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2247 char* val = searchValueTransform( node );
2251 growing_buffer* sql_buf = buffer_init( 32 );
2256 osrfHashGet( field, "name" ),
2263 return buffer_release( sql_buf );
2266 // class_alias is a class name or other table alias
2267 // field is a field definition as stored in the IDL
2268 // node comes from the method parameter, and may represent an entry in the SELECT list
2269 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2270 const jsonObject* node ) {
2271 growing_buffer* sql_buf = buffer_init( 32 );
2273 const char* field_transform = jsonObjectGetString(
2274 jsonObjectGetKeyConst( node, "transform" ) );
2275 const char* transform_subcolumn = jsonObjectGetString(
2276 jsonObjectGetKeyConst( node, "result_field" ) );
2278 if( transform_subcolumn ) {
2279 if( ! is_identifier( transform_subcolumn ) ) {
2280 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2281 modulename, transform_subcolumn );
2282 buffer_free( sql_buf );
2285 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2288 if( field_transform ) {
2290 if( ! is_identifier( field_transform ) ) {
2291 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2292 modulename, field_transform );
2293 buffer_free( sql_buf );
2297 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2298 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2299 field_transform, class_alias, osrfHashGet( field, "name" ));
2301 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2302 field_transform, class_alias, osrfHashGet( field, "name" ));
2305 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2308 if( array->type != JSON_ARRAY ) {
2309 osrfLogError( OSRF_LOG_MARK,
2310 "%s: Expected JSON_ARRAY for function params; found %s",
2311 modulename, json_type( array->type ) );
2312 buffer_free( sql_buf );
2315 int func_item_index = 0;
2316 jsonObject* func_item;
2317 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2319 char* val = jsonObjectToSimpleString( func_item );
2322 buffer_add( sql_buf, ",NULL" );
2323 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2324 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2325 OSRF_BUFFER_ADD( sql_buf, val );
2327 osrfLogError( OSRF_LOG_MARK,
2328 "%s: Error quoting key string [%s]", modulename, val );
2330 buffer_free( sql_buf );
2337 buffer_add( sql_buf, " )" );
2340 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2343 if( transform_subcolumn )
2344 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2346 return buffer_release( sql_buf );
2349 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2350 const jsonObject* node, const char* op ) {
2352 if( ! is_good_operator( op ) ) {
2353 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2357 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2358 if( ! field_transform )
2361 int extra_parens = 0; // boolean
2363 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2365 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2367 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2369 free( field_transform );
2373 } else if( value_obj->type == JSON_ARRAY ) {
2374 value = searchValueTransform( value_obj );
2376 osrfLogError( OSRF_LOG_MARK,
2377 "%s: Error building value transform for field transform", modulename );
2378 free( field_transform );
2381 } else if( value_obj->type == JSON_HASH ) {
2382 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2384 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2386 free( field_transform );
2390 } else if( value_obj->type == JSON_NUMBER ) {
2391 value = jsonNumberToDBString( field, value_obj );
2392 } else if( value_obj->type == JSON_NULL ) {
2393 osrfLogError( OSRF_LOG_MARK,
2394 "%s: Error building predicate for field transform: null value", modulename );
2395 free( field_transform );
2397 } else if( value_obj->type == JSON_BOOL ) {
2398 osrfLogError( OSRF_LOG_MARK,
2399 "%s: Error building predicate for field transform: boolean value", modulename );
2400 free( field_transform );
2403 if( !strcmp( get_primitive( field ), "number") ) {
2404 value = jsonNumberToDBString( field, value_obj );
2406 value = jsonObjectToSimpleString( value_obj );
2407 if( !dbi_conn_quote_string( dbhandle, &value )) {
2408 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2409 modulename, value );
2411 free( field_transform );
2417 const char* left_parens = "";
2418 const char* right_parens = "";
2420 if( extra_parens ) {
2425 growing_buffer* sql_buf = buffer_init( 32 );
2429 "%s%s %s %s %s %s%s",
2440 free( field_transform );
2442 return buffer_release( sql_buf );
2445 static char* searchSimplePredicate( const char* op, const char* class_alias,
2446 osrfHash* field, const jsonObject* node ) {
2448 if( ! is_good_operator( op ) ) {
2449 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2455 // Get the value to which we are comparing the specified column
2456 if( node->type != JSON_NULL ) {
2457 if( node->type == JSON_NUMBER ) {
2458 val = jsonNumberToDBString( field, node );
2459 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2460 val = jsonNumberToDBString( field, node );
2462 val = jsonObjectToSimpleString( node );
2467 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2468 // Value is not numeric; enclose it in quotes
2469 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2470 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2477 // Compare to a null value
2478 val = strdup( "NULL" );
2479 if( strcmp( op, "=" ))
2485 growing_buffer* sql_buf = buffer_init( 32 );
2486 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2487 char* pred = buffer_release( sql_buf );
2494 static char* searchBETWEENPredicate( const char* class_alias,
2495 osrfHash* field, const jsonObject* node ) {
2497 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2498 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2500 if( NULL == y_node ) {
2501 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2504 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2505 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2512 if( !strcmp( get_primitive( field ), "number") ) {
2513 x_string = jsonNumberToDBString( field, x_node );
2514 y_string = jsonNumberToDBString( field, y_node );
2517 x_string = jsonObjectToSimpleString( x_node );
2518 y_string = jsonObjectToSimpleString( y_node );
2519 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2520 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2521 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2522 modulename, x_string, y_string );
2529 growing_buffer* sql_buf = buffer_init( 32 );
2530 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2531 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2535 return buffer_release( sql_buf );
2538 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2539 jsonObject* node, osrfMethodContext* ctx ) {
2542 if( node->type == JSON_ARRAY ) { // equality IN search
2543 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2544 } else if( node->type == JSON_HASH ) { // other search
2545 jsonIterator* pred_itr = jsonNewIterator( node );
2546 if( !jsonIteratorHasNext( pred_itr ) ) {
2547 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2548 modulename, osrfHashGet(field, "name" ));
2550 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2552 // Verify that there are no additional predicates
2553 if( jsonIteratorHasNext( pred_itr ) ) {
2554 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2555 modulename, osrfHashGet(field, "name" ));
2556 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2557 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2558 else if( !(strcasecmp( pred_itr->key,"in" ))
2559 || !(strcasecmp( pred_itr->key,"not in" )) )
2560 pred = searchINPredicate(
2561 class_info->alias, field, pred_node, pred_itr->key, ctx );
2562 else if( pred_node->type == JSON_ARRAY )
2563 pred = searchFunctionPredicate(
2564 class_info->alias, field, pred_node, pred_itr->key );
2565 else if( pred_node->type == JSON_HASH )
2566 pred = searchFieldTransformPredicate(
2567 class_info, field, pred_node, pred_itr->key );
2569 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2571 jsonIteratorFree( pred_itr );
2573 } else if( node->type == JSON_NULL ) { // IS NULL search
2574 growing_buffer* _p = buffer_init( 64 );
2577 "\"%s\".%s IS NULL",
2578 class_info->class_name,
2579 osrfHashGet( field, "name" )
2581 pred = buffer_release( _p );
2582 } else { // equality search
2583 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2602 field : call_number,
2618 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2620 const jsonObject* working_hash;
2621 jsonObject* freeable_hash = NULL;
2623 if( join_hash->type == JSON_HASH ) {
2624 working_hash = join_hash;
2625 } else if( join_hash->type == JSON_STRING ) {
2626 // turn it into a JSON_HASH by creating a wrapper
2627 // around a copy of the original
2628 const char* _tmp = jsonObjectGetString( join_hash );
2629 freeable_hash = jsonNewObjectType( JSON_HASH );
2630 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2631 working_hash = freeable_hash;
2635 "%s: JOIN failed; expected JSON object type not found",
2641 growing_buffer* join_buf = buffer_init( 128 );
2642 const char* leftclass = left_info->class_name;
2644 jsonObject* snode = NULL;
2645 jsonIterator* search_itr = jsonNewIterator( working_hash );
2647 while ( (snode = jsonIteratorNext( search_itr )) ) {
2648 const char* right_alias = search_itr->key;
2650 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2652 class = right_alias;
2654 const ClassInfo* right_info = add_joined_class( right_alias, class );
2658 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2662 jsonIteratorFree( search_itr );
2663 buffer_free( join_buf );
2665 jsonObjectFree( freeable_hash );
2668 osrfHash* links = right_info->links;
2669 const char* table = right_info->source_def;
2671 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2672 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2674 if( field && !fkey ) {
2675 // Look up the corresponding join column in the IDL.
2676 // The link must be defined in the child table,
2677 // and point to the right parent table.
2678 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2679 const char* reltype = NULL;
2680 const char* other_class = NULL;
2681 reltype = osrfHashGet( idl_link, "reltype" );
2682 if( reltype && strcmp( reltype, "has_many" ) )
2683 other_class = osrfHashGet( idl_link, "class" );
2684 if( other_class && !strcmp( other_class, leftclass ) )
2685 fkey = osrfHashGet( idl_link, "key" );
2689 "%s: JOIN failed. No link defined from %s.%s to %s",
2695 buffer_free( join_buf );
2697 jsonObjectFree( freeable_hash );
2698 jsonIteratorFree( search_itr );
2702 } else if( !field && fkey ) {
2703 // Look up the corresponding join column in the IDL.
2704 // The link must be defined in the child table,
2705 // and point to the right parent table.
2706 osrfHash* left_links = left_info->links;
2707 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2708 const char* reltype = NULL;
2709 const char* other_class = NULL;
2710 reltype = osrfHashGet( idl_link, "reltype" );
2711 if( reltype && strcmp( reltype, "has_many" ) )
2712 other_class = osrfHashGet( idl_link, "class" );
2713 if( other_class && !strcmp( other_class, class ) )
2714 field = osrfHashGet( idl_link, "key" );
2718 "%s: JOIN failed. No link defined from %s.%s to %s",
2724 buffer_free( join_buf );
2726 jsonObjectFree( freeable_hash );
2727 jsonIteratorFree( search_itr );
2731 } else if( !field && !fkey ) {
2732 osrfHash* left_links = left_info->links;
2734 // For each link defined for the left class:
2735 // see if the link references the joined class
2736 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2737 osrfHash* curr_link = NULL;
2738 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2739 const char* other_class = osrfHashGet( curr_link, "class" );
2740 if( other_class && !strcmp( other_class, class ) ) {
2742 // In the IDL, the parent class doesn't always know then names of the child
2743 // columns that are pointing to it, so don't use that end of the link
2744 const char* reltype = osrfHashGet( curr_link, "reltype" );
2745 if( reltype && strcmp( reltype, "has_many" ) ) {
2746 // Found a link between the classes
2747 fkey = osrfHashIteratorKey( itr );
2748 field = osrfHashGet( curr_link, "key" );
2753 osrfHashIteratorFree( itr );
2755 if( !field || !fkey ) {
2756 // Do another such search, with the classes reversed
2758 // For each link defined for the joined class:
2759 // see if the link references the left class
2760 osrfHashIterator* itr = osrfNewHashIterator( links );
2761 osrfHash* curr_link = NULL;
2762 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2763 const char* other_class = osrfHashGet( curr_link, "class" );
2764 if( other_class && !strcmp( other_class, leftclass ) ) {
2766 // In the IDL, the parent class doesn't know then names of the child
2767 // columns that are pointing to it, so don't use that end of the link
2768 const char* reltype = osrfHashGet( curr_link, "reltype" );
2769 if( reltype && strcmp( reltype, "has_many" ) ) {
2770 // Found a link between the classes
2771 field = osrfHashIteratorKey( itr );
2772 fkey = osrfHashGet( curr_link, "key" );
2777 osrfHashIteratorFree( itr );
2780 if( !field || !fkey ) {
2783 "%s: JOIN failed. No link defined between %s and %s",
2788 buffer_free( join_buf );
2790 jsonObjectFree( freeable_hash );
2791 jsonIteratorFree( search_itr );
2796 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2798 if( !strcasecmp( type,"left" )) {
2799 buffer_add( join_buf, " LEFT JOIN" );
2800 } else if( !strcasecmp( type,"right" )) {
2801 buffer_add( join_buf, " RIGHT JOIN" );
2802 } else if( !strcasecmp( type,"full" )) {
2803 buffer_add( join_buf, " FULL JOIN" );
2805 buffer_add( join_buf, " INNER JOIN" );
2808 buffer_add( join_buf, " INNER JOIN" );
2811 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2812 table, right_alias, right_alias, field, left_info->alias, fkey );
2814 // Add any other join conditions as specified by "filter"
2815 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2817 const char* filter_op = jsonObjectGetString(
2818 jsonObjectGetKeyConst( snode, "filter_op" ) );
2819 if( filter_op && !strcasecmp( "or",filter_op )) {
2820 buffer_add( join_buf, " OR " );
2822 buffer_add( join_buf, " AND " );
2825 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2827 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2828 OSRF_BUFFER_ADD( join_buf, jpred );
2833 "%s: JOIN failed. Invalid conditional expression.",
2836 jsonIteratorFree( search_itr );
2837 buffer_free( join_buf );
2839 jsonObjectFree( freeable_hash );
2844 buffer_add( join_buf, " ) " );
2846 // Recursively add a nested join, if one is present
2847 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2849 char* jpred = searchJOIN( join_filter, right_info );
2851 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2852 OSRF_BUFFER_ADD( join_buf, jpred );
2855 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
2856 jsonIteratorFree( search_itr );
2857 buffer_free( join_buf );
2859 jsonObjectFree( freeable_hash );
2866 jsonObjectFree( freeable_hash );
2867 jsonIteratorFree( search_itr );
2869 return buffer_release( join_buf );
2874 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2875 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2876 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2878 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2880 search_hash is the JSON expression of the conditions.
2881 meta is the class definition from the IDL, for the relevant table.
2882 opjoin_type indicates whether multiple conditions, if present, should be
2883 connected by AND or OR.
2884 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2885 to pass it to other functions -- and all they do with it is to use the session
2886 and request members to send error messages back to the client.
2890 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
2891 int opjoin_type, osrfMethodContext* ctx ) {
2895 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
2896 "opjoin_type = %d, ctx addr = %p",
2899 class_info->class_def,
2904 growing_buffer* sql_buf = buffer_init( 128 );
2906 jsonObject* node = NULL;
2909 if( search_hash->type == JSON_ARRAY ) {
2910 osrfLogDebug( OSRF_LOG_MARK,
2911 "%s: In WHERE clause, condition type is JSON_ARRAY", modulename );
2912 if( 0 == search_hash->size ) {
2915 "%s: Invalid predicate structure: empty JSON array",
2918 buffer_free( sql_buf );
2922 unsigned long i = 0;
2923 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
2927 if( opjoin_type == OR_OP_JOIN )
2928 buffer_add( sql_buf, " OR " );
2930 buffer_add( sql_buf, " AND " );
2933 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2935 buffer_free( sql_buf );
2939 buffer_fadd( sql_buf, "( %s )", subpred );
2943 } else if( search_hash->type == JSON_HASH ) {
2944 osrfLogDebug( OSRF_LOG_MARK,
2945 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
2946 jsonIterator* search_itr = jsonNewIterator( search_hash );
2947 if( !jsonIteratorHasNext( search_itr ) ) {
2950 "%s: Invalid predicate structure: empty JSON object",
2953 jsonIteratorFree( search_itr );
2954 buffer_free( sql_buf );
2958 while( (node = jsonIteratorNext( search_itr )) ) {
2963 if( opjoin_type == OR_OP_JOIN )
2964 buffer_add( sql_buf, " OR " );
2966 buffer_add( sql_buf, " AND " );
2969 if( '+' == search_itr->key[ 0 ] ) {
2971 // This plus sign prefixes a class name or other table alias;
2972 // make sure the table alias is in scope
2973 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2974 if( ! alias_info ) {
2977 "%s: Invalid table alias \"%s\" in WHERE clause",
2981 jsonIteratorFree( search_itr );
2982 buffer_free( sql_buf );
2986 if( node->type == JSON_STRING ) {
2987 // It's the name of a column; make sure it belongs to the class
2988 const char* fieldname = jsonObjectGetString( node );
2989 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2992 "%s: Invalid column name \"%s\" in WHERE clause "
2993 "for table alias \"%s\"",
2998 jsonIteratorFree( search_itr );
2999 buffer_free( sql_buf );
3003 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3005 // It's something more complicated
3006 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3008 jsonIteratorFree( search_itr );
3009 buffer_free( sql_buf );
3013 buffer_fadd( sql_buf, "( %s )", subpred );
3016 } else if( '-' == search_itr->key[ 0 ] ) {
3017 if( !strcasecmp( "-or", search_itr->key )) {
3018 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3020 jsonIteratorFree( search_itr );
3021 buffer_free( sql_buf );
3025 buffer_fadd( sql_buf, "( %s )", subpred );
3027 } else if( !strcasecmp( "-and", search_itr->key )) {
3028 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3030 jsonIteratorFree( search_itr );
3031 buffer_free( sql_buf );
3035 buffer_fadd( sql_buf, "( %s )", subpred );
3037 } else if( !strcasecmp("-not",search_itr->key) ) {
3038 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3040 jsonIteratorFree( search_itr );
3041 buffer_free( sql_buf );
3045 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3047 } else if( !strcasecmp( "-exists", search_itr->key )) {
3048 char* subpred = buildQuery( ctx, node, SUBSELECT );
3050 jsonIteratorFree( search_itr );
3051 buffer_free( sql_buf );
3055 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3057 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3058 char* subpred = buildQuery( ctx, node, SUBSELECT );
3060 jsonIteratorFree( search_itr );
3061 buffer_free( sql_buf );
3065 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3067 } else { // Invalid "minus" operator
3070 "%s: Invalid operator \"%s\" in WHERE clause",
3074 jsonIteratorFree( search_itr );
3075 buffer_free( sql_buf );
3081 const char* class = class_info->class_name;
3082 osrfHash* fields = class_info->fields;
3083 osrfHash* field = osrfHashGet( fields, search_itr->key );
3086 const char* table = class_info->source_def;
3089 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3092 table ? table : "?",
3095 jsonIteratorFree( search_itr );
3096 buffer_free( sql_buf );
3100 char* subpred = searchPredicate( class_info, field, node, ctx );
3102 buffer_free( sql_buf );
3103 jsonIteratorFree( search_itr );
3107 buffer_add( sql_buf, subpred );
3111 jsonIteratorFree( search_itr );
3114 // ERROR ... only hash and array allowed at this level
3115 char* predicate_string = jsonObjectToJSON( search_hash );
3118 "%s: Invalid predicate structure: %s",
3122 buffer_free( sql_buf );
3123 free( predicate_string );
3127 return buffer_release( sql_buf );
3130 /* Build a JSON_ARRAY of field names for a given table alias
3132 static jsonObject* defaultSelectList( const char* table_alias ) {
3137 ClassInfo* class_info = search_all_alias( table_alias );
3138 if( ! class_info ) {
3141 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3148 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3149 osrfHash* field_def = NULL;
3150 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3151 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3152 const char* field_name = osrfHashIteratorKey( field_itr );
3153 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3154 jsonObjectPush( array, jsonNewObject( field_name ) );
3157 osrfHashIteratorFree( field_itr );
3162 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3163 // The jsonObject must be a JSON_HASH with an single entry for "union",
3164 // "intersect", or "except". The data associated with this key must be an
3165 // array of hashes, each hash being a query.
3166 // Also allowed but currently ignored: entries for "order_by" and "alias".
3167 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3169 if( ! combo || combo->type != JSON_HASH )
3170 return NULL; // should be impossible; validated by caller
3172 const jsonObject* query_array = NULL; // array of subordinate queries
3173 const char* op = NULL; // name of operator, e.g. UNION
3174 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3175 int op_count = 0; // for detecting conflicting operators
3176 int excepting = 0; // boolean
3177 int all = 0; // boolean
3178 jsonObject* order_obj = NULL;
3180 // Identify the elements in the hash
3181 jsonIterator* query_itr = jsonNewIterator( combo );
3182 jsonObject* curr_obj = NULL;
3183 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3184 if( ! strcmp( "union", query_itr->key ) ) {
3187 query_array = curr_obj;
3188 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3191 query_array = curr_obj;
3192 } else if( ! strcmp( "except", query_itr->key ) ) {
3196 query_array = curr_obj;
3197 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3200 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3203 order_obj = curr_obj;
3204 } else if( ! strcmp( "alias", query_itr->key ) ) {
3205 if( curr_obj->type != JSON_STRING ) {
3206 jsonIteratorFree( query_itr );
3209 alias = jsonObjectGetString( curr_obj );
3210 } else if( ! strcmp( "all", query_itr->key ) ) {
3211 if( obj_is_true( curr_obj ) )
3215 osrfAppSessionStatus(
3217 OSRF_STATUS_INTERNALSERVERERROR,
3218 "osrfMethodException",
3220 "Malformed query; unexpected entry in query object"
3224 "%s: Unexpected entry for \"%s\" in%squery",
3229 jsonIteratorFree( query_itr );
3233 jsonIteratorFree( query_itr );
3235 // More sanity checks
3236 if( ! query_array ) {
3238 osrfAppSessionStatus(
3240 OSRF_STATUS_INTERNALSERVERERROR,
3241 "osrfMethodException",
3243 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3247 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3250 return NULL; // should be impossible...
3251 } else if( op_count > 1 ) {
3253 osrfAppSessionStatus(
3255 OSRF_STATUS_INTERNALSERVERERROR,
3256 "osrfMethodException",
3258 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3262 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3266 } if( query_array->type != JSON_ARRAY ) {
3268 osrfAppSessionStatus(
3270 OSRF_STATUS_INTERNALSERVERERROR,
3271 "osrfMethodException",
3273 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3277 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3280 json_type( query_array->type )
3283 } if( query_array->size < 2 ) {
3285 osrfAppSessionStatus(
3287 OSRF_STATUS_INTERNALSERVERERROR,
3288 "osrfMethodException",
3290 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3294 "%s:%srequires multiple queries as operands",
3299 } else if( excepting && query_array->size > 2 ) {
3301 osrfAppSessionStatus(
3303 OSRF_STATUS_INTERNALSERVERERROR,
3304 "osrfMethodException",
3306 "EXCEPT operator has too many queries as operands"
3310 "%s:EXCEPT operator has too many queries as operands",
3314 } else if( order_obj && ! alias ) {
3316 osrfAppSessionStatus(
3318 OSRF_STATUS_INTERNALSERVERERROR,
3319 "osrfMethodException",
3321 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3325 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3331 // So far so good. Now build the SQL.
3332 growing_buffer* sql = buffer_init( 256 );
3334 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3335 // Add a layer of parentheses
3336 if( flags & SUBCOMBO )
3337 OSRF_BUFFER_ADD( sql, "( " );
3339 // Traverse the query array. Each entry should be a hash.
3340 int first = 1; // boolean
3342 jsonObject* query = NULL;
3343 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3344 if( query->type != JSON_HASH ) {
3346 osrfAppSessionStatus(
3348 OSRF_STATUS_INTERNALSERVERERROR,
3349 "osrfMethodException",
3351 "Malformed query under UNION, INTERSECT or EXCEPT"
3355 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3358 json_type( query->type )
3367 OSRF_BUFFER_ADD( sql, op );
3369 OSRF_BUFFER_ADD( sql, "ALL " );
3372 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3376 "%s: Error building query under%s",
3384 OSRF_BUFFER_ADD( sql, query_str );
3387 if( flags & SUBCOMBO )
3388 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3390 if( !(flags & SUBSELECT) )
3391 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3393 return buffer_release( sql );
3396 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3397 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3398 // or "except" to indicate the type of query.
3399 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3403 osrfAppSessionStatus(
3405 OSRF_STATUS_INTERNALSERVERERROR,
3406 "osrfMethodException",
3408 "Malformed query; no query object"
3410 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3412 } else if( query->type != JSON_HASH ) {
3414 osrfAppSessionStatus(
3416 OSRF_STATUS_INTERNALSERVERERROR,
3417 "osrfMethodException",
3419 "Malformed query object"
3423 "%s: Query object is %s instead of JSON_HASH",
3425 json_type( query->type )
3430 // Determine what kind of query it purports to be, and dispatch accordingly.
3431 if( jsonObjectGetKey( query, "union" ) ||
3432 jsonObjectGetKey( query, "intersect" ) ||
3433 jsonObjectGetKey( query, "except" ) ) {
3434 return doCombo( ctx, query, flags );
3436 // It is presumably a SELECT query
3438 // Push a node onto the stack for the current query. Every level of
3439 // subquery gets its own QueryFrame on the Stack.
3442 // Build an SQL SELECT statement
3445 jsonObjectGetKey( query, "select" ),
3446 jsonObjectGetKey( query, "from" ),
3447 jsonObjectGetKey( query, "where" ),
3448 jsonObjectGetKey( query, "having" ),
3449 jsonObjectGetKey( query, "order_by" ),
3450 jsonObjectGetKey( query, "limit" ),
3451 jsonObjectGetKey( query, "offset" ),
3460 /* method context */ osrfMethodContext* ctx,
3462 /* SELECT */ jsonObject* selhash,
3463 /* FROM */ jsonObject* join_hash,
3464 /* WHERE */ jsonObject* search_hash,
3465 /* HAVING */ jsonObject* having_hash,
3466 /* ORDER BY */ jsonObject* order_hash,
3467 /* LIMIT */ jsonObject* limit,
3468 /* OFFSET */ jsonObject* offset,
3469 /* flags */ int flags
3471 const char* locale = osrf_message_get_last_locale();
3473 // general tmp objects
3474 const jsonObject* tmp_const;
3475 jsonObject* selclass = NULL;
3476 jsonObject* snode = NULL;
3477 jsonObject* onode = NULL;
3479 char* string = NULL;
3480 int from_function = 0;
3485 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3487 // punt if there's no FROM clause
3488 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3491 "%s: FROM clause is missing or empty",
3495 osrfAppSessionStatus(
3497 OSRF_STATUS_INTERNALSERVERERROR,
3498 "osrfMethodException",
3500 "FROM clause is missing or empty in JSON query"
3505 // the core search class
3506 const char* core_class = NULL;
3508 // get the core class -- the only key of the top level FROM clause, or a string
3509 if( join_hash->type == JSON_HASH ) {
3510 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3511 snode = jsonIteratorNext( tmp_itr );
3513 // Populate the current QueryFrame with information
3514 // about the core class
3515 if( add_query_core( NULL, tmp_itr->key ) ) {
3517 osrfAppSessionStatus(
3519 OSRF_STATUS_INTERNALSERVERERROR,
3520 "osrfMethodException",
3522 "Unable to look up core class"
3526 core_class = curr_query->core.class_name;
3529 jsonObject* extra = jsonIteratorNext( tmp_itr );
3531 jsonIteratorFree( tmp_itr );
3534 // There shouldn't be more than one entry in join_hash
3538 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3542 osrfAppSessionStatus(
3544 OSRF_STATUS_INTERNALSERVERERROR,
3545 "osrfMethodException",
3547 "Malformed FROM clause in JSON query"
3549 return NULL; // Malformed join_hash; extra entry
3551 } else if( join_hash->type == JSON_ARRAY ) {
3552 // We're selecting from a function, not from a table
3554 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3557 } else if( join_hash->type == JSON_STRING ) {
3558 // Populate the current QueryFrame with information
3559 // about the core class
3560 core_class = jsonObjectGetString( join_hash );
3562 if( add_query_core( NULL, core_class ) ) {
3564 osrfAppSessionStatus(
3566 OSRF_STATUS_INTERNALSERVERERROR,
3567 "osrfMethodException",
3569 "Unable to look up core class"
3577 "%s: FROM clause is unexpected JSON type: %s",
3579 json_type( join_hash->type )
3582 osrfAppSessionStatus(
3584 OSRF_STATUS_INTERNALSERVERERROR,
3585 "osrfMethodException",
3587 "Ill-formed FROM clause in JSON query"
3592 // Build the join clause, if any, while filling out the list
3593 // of joined classes in the current QueryFrame.
3594 char* join_clause = NULL;
3595 if( join_hash && ! from_function ) {
3597 join_clause = searchJOIN( join_hash, &curr_query->core );
3598 if( ! join_clause ) {
3600 osrfAppSessionStatus(
3602 OSRF_STATUS_INTERNALSERVERERROR,
3603 "osrfMethodException",
3605 "Unable to construct JOIN clause(s)"
3611 // For in case we don't get a select list
3612 jsonObject* defaultselhash = NULL;
3614 // if there is no select list, build a default select list ...
3615 if( !selhash && !from_function ) {
3616 jsonObject* default_list = defaultSelectList( core_class );
3617 if( ! default_list ) {
3619 osrfAppSessionStatus(
3621 OSRF_STATUS_INTERNALSERVERERROR,
3622 "osrfMethodException",
3624 "Unable to build default SELECT clause in JSON query"
3626 free( join_clause );
3631 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3632 jsonObjectSetKey( selhash, core_class, default_list );
3635 // The SELECT clause can be encoded only by a hash
3636 if( !from_function && selhash->type != JSON_HASH ) {
3639 "%s: Expected JSON_HASH for SELECT clause; found %s",
3641 json_type( selhash->type )
3645 osrfAppSessionStatus(
3647 OSRF_STATUS_INTERNALSERVERERROR,
3648 "osrfMethodException",
3650 "Malformed SELECT clause in JSON query"
3652 free( join_clause );
3656 // If you see a null or wild card specifier for the core class, or an
3657 // empty array, replace it with a default SELECT list
3658 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3660 int default_needed = 0; // boolean
3661 if( JSON_STRING == tmp_const->type
3662 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3664 else if( JSON_NULL == tmp_const->type )
3667 if( default_needed ) {
3668 // Build a default SELECT list
3669 jsonObject* default_list = defaultSelectList( core_class );
3670 if( ! default_list ) {
3672 osrfAppSessionStatus(
3674 OSRF_STATUS_INTERNALSERVERERROR,
3675 "osrfMethodException",
3677 "Can't build default SELECT clause in JSON query"
3679 free( join_clause );
3684 jsonObjectSetKey( selhash, core_class, default_list );
3688 // temp buffers for the SELECT list and GROUP BY clause
3689 growing_buffer* select_buf = buffer_init( 128 );
3690 growing_buffer* group_buf = buffer_init( 128 );
3692 int aggregate_found = 0; // boolean
3694 // Build a select list
3695 if( from_function ) // From a function we select everything
3696 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3699 // Build the SELECT list as SQL
3703 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3704 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3706 const char* cname = selclass_itr->key;
3708 // Make sure the target relation is in the FROM clause.
3710 // At this point join_hash is a step down from the join_hash we
3711 // received as a parameter. If the original was a JSON_STRING,
3712 // then json_hash is now NULL. If the original was a JSON_HASH,
3713 // then json_hash is now the first (and only) entry in it,
3714 // denoting the core class. We've already excluded the
3715 // possibility that the original was a JSON_ARRAY, because in
3716 // that case from_function would be non-NULL, and we wouldn't
3719 // If the current table alias isn't in scope, bail out
3720 ClassInfo* class_info = search_alias( cname );
3721 if( ! class_info ) {
3724 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3729 osrfAppSessionStatus(
3731 OSRF_STATUS_INTERNALSERVERERROR,
3732 "osrfMethodException",
3734 "Selected class not in FROM clause in JSON query"
3736 jsonIteratorFree( selclass_itr );
3737 buffer_free( select_buf );
3738 buffer_free( group_buf );
3739 if( defaultselhash )
3740 jsonObjectFree( defaultselhash );
3741 free( join_clause );
3745 if( selclass->type != JSON_ARRAY ) {
3748 "%s: Malformed SELECT list for class \"%s\"; not an array",
3753 osrfAppSessionStatus(
3755 OSRF_STATUS_INTERNALSERVERERROR,
3756 "osrfMethodException",
3758 "Selected class not in FROM clause in JSON query"
3761 jsonIteratorFree( selclass_itr );
3762 buffer_free( select_buf );
3763 buffer_free( group_buf );
3764 if( defaultselhash )
3765 jsonObjectFree( defaultselhash );
3766 free( join_clause );
3770 // Look up some attributes of the current class
3771 osrfHash* idlClass = class_info->class_def;
3772 osrfHash* class_field_set = class_info->fields;
3773 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3774 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3776 if( 0 == selclass->size ) {
3779 "%s: No columns selected from \"%s\"",
3785 // stitch together the column list for the current table alias...
3786 unsigned long field_idx = 0;
3787 jsonObject* selfield = NULL;
3788 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3790 // If we need a separator comma, add one
3794 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3797 // if the field specification is a string, add it to the list
3798 if( selfield->type == JSON_STRING ) {
3800 // Look up the field in the IDL
3801 const char* col_name = jsonObjectGetString( selfield );
3802 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3804 // No such field in current class
3807 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3813 osrfAppSessionStatus(
3815 OSRF_STATUS_INTERNALSERVERERROR,
3816 "osrfMethodException",
3818 "Selected column not defined in JSON query"
3820 jsonIteratorFree( selclass_itr );
3821 buffer_free( select_buf );
3822 buffer_free( group_buf );
3823 if( defaultselhash )
3824 jsonObjectFree( defaultselhash );
3825 free( join_clause );
3827 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3828 // Virtual field not allowed
3831 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3837 osrfAppSessionStatus(
3839 OSRF_STATUS_INTERNALSERVERERROR,
3840 "osrfMethodException",
3842 "Selected column may not be virtual in JSON query"
3844 jsonIteratorFree( selclass_itr );
3845 buffer_free( select_buf );
3846 buffer_free( group_buf );
3847 if( defaultselhash )
3848 jsonObjectFree( defaultselhash );
3849 free( join_clause );
3855 if( flags & DISABLE_I18N )
3858 i18n = osrfHashGet( field_def, "i18n" );
3860 if( str_is_true( i18n ) ) {
3861 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
3862 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3863 class_tname, cname, col_name, class_pkey,
3864 cname, class_pkey, locale, col_name );
3866 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3867 cname, col_name, col_name );
3870 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3871 cname, col_name, col_name );
3874 // ... but it could be an object, in which case we check for a Field Transform
3875 } else if( selfield->type == JSON_HASH ) {
3877 const char* col_name = jsonObjectGetString(
3878 jsonObjectGetKeyConst( selfield, "column" ) );
3880 // Get the field definition from the IDL
3881 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3883 // No such field in current class
3886 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3892 osrfAppSessionStatus(
3894 OSRF_STATUS_INTERNALSERVERERROR,
3895 "osrfMethodException",
3897 "Selected column is not defined in JSON query"
3899 jsonIteratorFree( selclass_itr );
3900 buffer_free( select_buf );
3901 buffer_free( group_buf );
3902 if( defaultselhash )
3903 jsonObjectFree( defaultselhash );
3904 free( join_clause );
3906 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
3907 // No such field in current class
3910 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3916 osrfAppSessionStatus(
3918 OSRF_STATUS_INTERNALSERVERERROR,
3919 "osrfMethodException",
3921 "Selected column is virtual in JSON query"
3923 jsonIteratorFree( selclass_itr );
3924 buffer_free( select_buf );
3925 buffer_free( group_buf );
3926 if( defaultselhash )
3927 jsonObjectFree( defaultselhash );
3928 free( join_clause );
3932 // Decide what to use as a column alias
3934 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3935 _alias = jsonObjectGetString( tmp_const );
3936 } else { // Use field name as the alias
3940 if( jsonObjectGetKeyConst( selfield, "transform" )) {
3941 char* transform_str = searchFieldTransform(
3942 class_info->alias, field_def, selfield );
3943 if( transform_str ) {
3944 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
3945 free( transform_str );
3948 osrfAppSessionStatus(
3950 OSRF_STATUS_INTERNALSERVERERROR,
3951 "osrfMethodException",
3953 "Unable to generate transform function in JSON query"
3955 jsonIteratorFree( selclass_itr );
3956 buffer_free( select_buf );
3957 buffer_free( group_buf );
3958 if( defaultselhash )
3959 jsonObjectFree( defaultselhash );
3960 free( join_clause );
3967 if( flags & DISABLE_I18N )
3970 i18n = osrfHashGet( field_def, "i18n" );
3972 if( str_is_true( i18n ) ) {
3973 buffer_fadd( select_buf,
3974 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
3975 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
3976 class_tname, cname, col_name, class_pkey, cname,
3977 class_pkey, locale, _alias );
3979 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3980 cname, col_name, _alias );
3983 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3984 cname, col_name, _alias );
3991 "%s: Selected item is unexpected JSON type: %s",
3993 json_type( selfield->type )
3996 osrfAppSessionStatus(
3998 OSRF_STATUS_INTERNALSERVERERROR,
3999 "osrfMethodException",
4001 "Ill-formed SELECT item in JSON query"
4003 jsonIteratorFree( selclass_itr );
4004 buffer_free( select_buf );
4005 buffer_free( group_buf );
4006 if( defaultselhash )
4007 jsonObjectFree( defaultselhash );
4008 free( join_clause );
4012 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
4013 if( obj_is_true( agg_obj ) )
4014 aggregate_found = 1;
4016 // Append a comma (except for the first one)
4017 // and add the column to a GROUP BY clause
4021 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4023 buffer_fadd( group_buf, " %d", sel_pos );
4027 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4029 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4030 if ( ! obj_is_true( aggregate_obj ) ) {
4034 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4037 buffer_fadd(group_buf, " %d", sel_pos);
4040 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4044 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4047 _column = searchFieldTransform(class_info->alias, field, selfield);
4048 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4049 OSRF_BUFFER_ADD(group_buf, _column);
4050 _column = searchFieldTransform(class_info->alias, field, selfield);
4057 } // end while -- iterating across SELECT columns
4059 } // end while -- iterating across classes
4061 jsonIteratorFree( selclass_itr );
4065 char* col_list = buffer_release( select_buf );
4067 // Make sure the SELECT list isn't empty. This can happen, for example,
4068 // if we try to build a default SELECT clause from a non-core table.
4071 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4073 osrfAppSessionStatus(
4075 OSRF_STATUS_INTERNALSERVERERROR,
4076 "osrfMethodException",
4078 "SELECT list is empty"
4081 buffer_free( group_buf );
4082 if( defaultselhash )
4083 jsonObjectFree( defaultselhash );
4084 free( join_clause );
4090 table = searchValueTransform( join_hash );
4092 table = strdup( curr_query->core.source_def );
4096 osrfAppSessionStatus(
4098 OSRF_STATUS_INTERNALSERVERERROR,
4099 "osrfMethodException",
4101 "Unable to identify table for core class"
4104 buffer_free( group_buf );
4105 if( defaultselhash )
4106 jsonObjectFree( defaultselhash );
4107 free( join_clause );
4111 // Put it all together
4112 growing_buffer* sql_buf = buffer_init( 128 );
4113 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4117 // Append the join clause, if any
4119 buffer_add(sql_buf, join_clause );
4120 free( join_clause );
4123 char* order_by_list = NULL;
4124 char* having_buf = NULL;
4126 if( !from_function ) {
4128 // Build a WHERE clause, if there is one
4130 buffer_add( sql_buf, " WHERE " );
4132 // and it's on the WHERE clause
4133 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4136 osrfAppSessionStatus(
4138 OSRF_STATUS_INTERNALSERVERERROR,
4139 "osrfMethodException",
4141 "Severe query error in WHERE predicate -- see error log for more details"
4144 buffer_free( group_buf );
4145 buffer_free( sql_buf );
4146 if( defaultselhash )
4147 jsonObjectFree( defaultselhash );
4151 buffer_add( sql_buf, pred );
4155 // Build a HAVING clause, if there is one
4158 // and it's on the the WHERE clause
4159 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4161 if( ! having_buf ) {
4163 osrfAppSessionStatus(
4165 OSRF_STATUS_INTERNALSERVERERROR,
4166 "osrfMethodException",
4168 "Severe query error in HAVING predicate -- see error log for more details"
4171 buffer_free( group_buf );
4172 buffer_free( sql_buf );
4173 if( defaultselhash )
4174 jsonObjectFree( defaultselhash );
4179 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4181 // Build an ORDER BY clause, if there is one
4182 if( NULL == order_hash )
4183 ; // No ORDER BY? do nothing
4184 else if( JSON_ARRAY == order_hash->type ) {
4185 // Array of field specifications, each specification being a
4186 // hash to define the class, field, and other details
4188 jsonObject* order_spec;
4189 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4191 if( JSON_HASH != order_spec->type ) {
4192 osrfLogError( OSRF_LOG_MARK,
4193 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4194 modulename, json_type( order_spec->type ) );
4196 osrfAppSessionStatus(
4198 OSRF_STATUS_INTERNALSERVERERROR,
4199 "osrfMethodException",
4201 "Malformed ORDER BY clause -- see error log for more details"
4203 buffer_free( order_buf );
4205 buffer_free( group_buf );
4206 buffer_free( sql_buf );
4207 if( defaultselhash )
4208 jsonObjectFree( defaultselhash );
4212 const char* class_alias =
4213 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4215 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4218 OSRF_BUFFER_ADD( order_buf, ", " );
4220 order_buf = buffer_init( 128 );
4222 if( !field || !class_alias ) {
4223 osrfLogError( OSRF_LOG_MARK,
4224 "%s: Missing class or field name in field specification "
4225 "of ORDER BY clause",
4228 osrfAppSessionStatus(
4230 OSRF_STATUS_INTERNALSERVERERROR,
4231 "osrfMethodException",
4233 "Malformed ORDER BY clause -- see error log for more details"
4235 buffer_free( order_buf );
4237 buffer_free( group_buf );
4238 buffer_free( sql_buf );
4239 if( defaultselhash )
4240 jsonObjectFree( defaultselhash );
4244 ClassInfo* order_class_info = search_alias( class_alias );
4245 if( ! order_class_info ) {
4246 osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4247 "not in FROM clause", modulename, class_alias );
4249 osrfAppSessionStatus(
4251 OSRF_STATUS_INTERNALSERVERERROR,
4252 "osrfMethodException",
4254 "Invalid class referenced in ORDER BY clause -- "
4255 "see error log for more details"
4258 buffer_free( group_buf );
4259 buffer_free( sql_buf );
4260 if( defaultselhash )
4261 jsonObjectFree( defaultselhash );
4265 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4267 osrfLogError( OSRF_LOG_MARK,
4268 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4269 modulename, class_alias, field );
4271 osrfAppSessionStatus(
4273 OSRF_STATUS_INTERNALSERVERERROR,
4274 "osrfMethodException",
4276 "Invalid field referenced in ORDER BY clause -- "
4277 "see error log for more details"
4280 buffer_free( group_buf );
4281 buffer_free( sql_buf );
4282 if( defaultselhash )
4283 jsonObjectFree( defaultselhash );
4285 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4286 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4287 modulename, field );
4289 osrfAppSessionStatus(
4291 OSRF_STATUS_INTERNALSERVERERROR,
4292 "osrfMethodException",
4294 "Virtual field in ORDER BY clause -- see error log for more details"
4296 buffer_free( order_buf );
4298 buffer_free( group_buf );
4299 buffer_free( sql_buf );
4300 if( defaultselhash )
4301 jsonObjectFree( defaultselhash );
4305 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4306 char* transform_str = searchFieldTransform(
4307 class_alias, field_def, order_spec );
4308 if( ! transform_str ) {
4310 osrfAppSessionStatus(
4312 OSRF_STATUS_INTERNALSERVERERROR,
4313 "osrfMethodException",
4315 "Severe query error in ORDER BY clause -- "
4316 "see error log for more details"
4318 buffer_free( order_buf );
4320 buffer_free( group_buf );
4321 buffer_free( sql_buf );
4322 if( defaultselhash )
4323 jsonObjectFree( defaultselhash );
4327 OSRF_BUFFER_ADD( order_buf, transform_str );
4328 free( transform_str );
4331 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4333 const char* direction =
4334 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4336 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4337 OSRF_BUFFER_ADD( order_buf, " DESC" );
4339 OSRF_BUFFER_ADD( order_buf, " ASC" );
4342 } else if( JSON_HASH == order_hash->type ) {
4343 // This hash is keyed on class alias. Each class has either
4344 // an array of field names or a hash keyed on field name.
4345 jsonIterator* class_itr = jsonNewIterator( order_hash );
4346 while( (snode = jsonIteratorNext( class_itr )) ) {
4348 ClassInfo* order_class_info = search_alias( class_itr->key );
4349 if( ! order_class_info ) {
4350 osrfLogError( OSRF_LOG_MARK,
4351 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4352 modulename, class_itr->key );
4354 osrfAppSessionStatus(
4356 OSRF_STATUS_INTERNALSERVERERROR,
4357 "osrfMethodException",
4359 "Invalid class referenced in ORDER BY clause -- "
4360 "see error log for more details"
4362 jsonIteratorFree( class_itr );
4363 buffer_free( order_buf );
4365 buffer_free( group_buf );
4366 buffer_free( sql_buf );
4367 if( defaultselhash )
4368 jsonObjectFree( defaultselhash );
4372 osrfHash* field_list_def = order_class_info->fields;
4374 if( snode->type == JSON_HASH ) {
4376 // Hash is keyed on field names from the current class. For each field
4377 // there is another layer of hash to define the sorting details, if any,
4378 // or a string to indicate direction of sorting.
4379 jsonIterator* order_itr = jsonNewIterator( snode );
4380 while( (onode = jsonIteratorNext( order_itr )) ) {
4382 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4384 osrfLogError( OSRF_LOG_MARK,
4385 "%s: Invalid field \"%s\" in ORDER BY clause",
4386 modulename, order_itr->key );
4388 osrfAppSessionStatus(
4390 OSRF_STATUS_INTERNALSERVERERROR,
4391 "osrfMethodException",
4393 "Invalid field in ORDER BY clause -- "
4394 "see error log for more details"
4396 jsonIteratorFree( order_itr );
4397 jsonIteratorFree( class_itr );
4398 buffer_free( order_buf );
4400 buffer_free( group_buf );
4401 buffer_free( sql_buf );
4402 if( defaultselhash )
4403 jsonObjectFree( defaultselhash );
4405 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4406 osrfLogError( OSRF_LOG_MARK,
4407 "%s: Virtual field \"%s\" in ORDER BY clause",
4408 modulename, order_itr->key );
4410 osrfAppSessionStatus(
4412 OSRF_STATUS_INTERNALSERVERERROR,
4413 "osrfMethodException",
4415 "Virtual field in ORDER BY clause -- "
4416 "see error log for more details"
4418 jsonIteratorFree( order_itr );
4419 jsonIteratorFree( class_itr );
4420 buffer_free( order_buf );
4422 buffer_free( group_buf );
4423 buffer_free( sql_buf );
4424 if( defaultselhash )
4425 jsonObjectFree( defaultselhash );
4429 const char* direction = NULL;
4430 if( onode->type == JSON_HASH ) {
4431 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4432 string = searchFieldTransform(
4434 osrfHashGet( field_list_def, order_itr->key ),
4438 if( ctx ) osrfAppSessionStatus(
4440 OSRF_STATUS_INTERNALSERVERERROR,
4441 "osrfMethodException",
4443 "Severe query error in ORDER BY clause -- "
4444 "see error log for more details"
4446 jsonIteratorFree( order_itr );
4447 jsonIteratorFree( class_itr );
4449 buffer_free( group_buf );
4450 buffer_free( order_buf);
4451 buffer_free( sql_buf );
4452 if( defaultselhash )
4453 jsonObjectFree( defaultselhash );
4457 growing_buffer* field_buf = buffer_init( 16 );
4458 buffer_fadd( field_buf, "\"%s\".%s",
4459 class_itr->key, order_itr->key );
4460 string = buffer_release( field_buf );
4463 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4464 const char* dir = jsonObjectGetString( tmp_const );
4465 if(!strncasecmp( dir, "d", 1 )) {
4466 direction = " DESC";
4472 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4473 osrfLogError( OSRF_LOG_MARK,
4474 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4475 modulename, json_type( onode->type ) );
4477 osrfAppSessionStatus(
4479 OSRF_STATUS_INTERNALSERVERERROR,
4480 "osrfMethodException",
4482 "Malformed ORDER BY clause -- see error log for more details"
4484 jsonIteratorFree( order_itr );
4485 jsonIteratorFree( class_itr );
4487 buffer_free( group_buf );
4488 buffer_free( order_buf );
4489 buffer_free( sql_buf );
4490 if( defaultselhash )
4491 jsonObjectFree( defaultselhash );
4495 string = strdup( order_itr->key );
4496 const char* dir = jsonObjectGetString( onode );
4497 if( !strncasecmp( dir, "d", 1 )) {
4498 direction = " DESC";
4505 OSRF_BUFFER_ADD( order_buf, ", " );
4507 order_buf = buffer_init( 128 );
4509 OSRF_BUFFER_ADD( order_buf, string );
4513 OSRF_BUFFER_ADD( order_buf, direction );
4517 jsonIteratorFree( order_itr );
4519 } else if( snode->type == JSON_ARRAY ) {
4521 // Array is a list of fields from the current class
4522 unsigned long order_idx = 0;
4523 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4525 const char* _f = jsonObjectGetString( onode );
4527 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4529 osrfLogError( OSRF_LOG_MARK,
4530 "%s: Invalid field \"%s\" in ORDER BY clause",
4533 osrfAppSessionStatus(
4535 OSRF_STATUS_INTERNALSERVERERROR,
4536 "osrfMethodException",
4538 "Invalid field in ORDER BY clause -- "
4539 "see error log for more details"
4541 jsonIteratorFree( class_itr );
4542 buffer_free( order_buf );
4544 buffer_free( group_buf );
4545 buffer_free( sql_buf );
4546 if( defaultselhash )
4547 jsonObjectFree( defaultselhash );
4549 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4550 osrfLogError( OSRF_LOG_MARK,
4551 "%s: Virtual field \"%s\" in ORDER BY clause",
4554 osrfAppSessionStatus(
4556 OSRF_STATUS_INTERNALSERVERERROR,
4557 "osrfMethodException",
4559 "Virtual field in ORDER BY clause -- "
4560 "see error log for more details"
4562 jsonIteratorFree( class_itr );
4563 buffer_free( order_buf );
4565 buffer_free( group_buf );
4566 buffer_free( sql_buf );
4567 if( defaultselhash )
4568 jsonObjectFree( defaultselhash );
4573 OSRF_BUFFER_ADD( order_buf, ", " );
4575 order_buf = buffer_init( 128 );
4577 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4581 // IT'S THE OOOOOOOOOOOLD STYLE!
4583 osrfLogError( OSRF_LOG_MARK,
4584 "%s: Possible SQL injection attempt; direct order by is not allowed",
4587 osrfAppSessionStatus(
4589 OSRF_STATUS_INTERNALSERVERERROR,
4590 "osrfMethodException",
4592 "Severe query error -- see error log for more details"
4597 buffer_free( group_buf );
4598 buffer_free( order_buf );
4599 buffer_free( sql_buf );
4600 if( defaultselhash )
4601 jsonObjectFree( defaultselhash );
4602 jsonIteratorFree( class_itr );
4606 jsonIteratorFree( class_itr );
4608 osrfLogError( OSRF_LOG_MARK,
4609 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4610 modulename, json_type( order_hash->type ) );
4612 osrfAppSessionStatus(
4614 OSRF_STATUS_INTERNALSERVERERROR,
4615 "osrfMethodException",
4617 "Malformed ORDER BY clause -- see error log for more details"
4619 buffer_free( order_buf );
4621 buffer_free( group_buf );
4622 buffer_free( sql_buf );
4623 if( defaultselhash )
4624 jsonObjectFree( defaultselhash );
4629 order_by_list = buffer_release( order_buf );
4633 string = buffer_release( group_buf );
4635 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4636 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4637 OSRF_BUFFER_ADD( sql_buf, string );
4642 if( having_buf && *having_buf ) {
4643 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4644 OSRF_BUFFER_ADD( sql_buf, having_buf );
4648 if( order_by_list ) {
4650 if( *order_by_list ) {
4651 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4652 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4655 free( order_by_list );
4659 const char* str = jsonObjectGetString( limit );
4660 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4664 const char* str = jsonObjectGetString( offset );
4665 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4668 if( !(flags & SUBSELECT) )
4669 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4671 if( defaultselhash )
4672 jsonObjectFree( defaultselhash );
4674 return buffer_release( sql_buf );
4676 } // end of SELECT()
4678 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4680 const char* locale = osrf_message_get_last_locale();
4682 osrfHash* fields = osrfHashGet( meta, "fields" );
4683 char* core_class = osrfHashGet( meta, "classname" );
4685 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4687 jsonObject* node = NULL;
4688 jsonObject* snode = NULL;
4689 jsonObject* onode = NULL;
4690 const jsonObject* _tmp = NULL;
4691 jsonObject* selhash = NULL;
4692 jsonObject* defaultselhash = NULL;
4694 growing_buffer* sql_buf = buffer_init( 128 );
4695 growing_buffer* select_buf = buffer_init( 128 );
4697 if( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4698 defaultselhash = jsonNewObjectType( JSON_HASH );
4699 selhash = defaultselhash;
4702 // If there's no SELECT list for the core class, build one
4703 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4704 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4706 // Add every non-virtual field to the field list
4707 osrfHash* field_def = NULL;
4708 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4709 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4710 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4711 const char* field = osrfHashIteratorKey( field_itr );
4712 jsonObjectPush( field_list, jsonNewObject( field ) );
4715 osrfHashIteratorFree( field_itr );
4716 jsonObjectSetKey( selhash, core_class, field_list );
4720 jsonIterator* class_itr = jsonNewIterator( selhash );
4721 while( (snode = jsonIteratorNext( class_itr )) ) {
4723 const char* cname = class_itr->key;
4724 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4728 if( strcmp(core_class,class_itr->key )) {
4732 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4733 if( !found->size ) {
4734 jsonObjectFree( found );
4738 jsonObjectFree( found );
4741 jsonIterator* select_itr = jsonNewIterator( snode );
4742 while( (node = jsonIteratorNext( select_itr )) ) {
4743 const char* item_str = jsonObjectGetString( node );
4744 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4745 char* fname = osrfHashGet( field, "name" );
4753 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4758 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4759 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4762 i18n = osrfHashGet( field, "i18n" );
4764 if( str_is_true( i18n ) ) {
4765 char* pkey = osrfHashGet( idlClass, "primarykey" );
4766 char* tname = osrfHashGet( idlClass, "tablename" );
4768 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4769 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4770 tname, cname, fname, pkey, cname, pkey, locale, fname );
4772 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4775 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4779 jsonIteratorFree( select_itr );
4782 jsonIteratorFree( class_itr );
4784 char* col_list = buffer_release( select_buf );
4785 char* table = oilsGetRelation( meta );
4787 table = strdup( "(null)" );
4789 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4793 // Clear the query stack (as a fail-safe precaution against possible
4794 // leftover garbage); then push the first query frame onto the stack.
4795 clear_query_stack();
4797 if( add_query_core( NULL, core_class ) ) {
4799 osrfAppSessionStatus(
4801 OSRF_STATUS_INTERNALSERVERERROR,
4802 "osrfMethodException",
4804 "Unable to build query frame for core class"
4810 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4811 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
4812 OSRF_BUFFER_ADD( sql_buf, join_clause );
4813 free( join_clause );
4816 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4817 modulename, OSRF_BUFFER_C_STR( sql_buf ));
4819 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
4821 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4823 osrfAppSessionStatus(
4825 OSRF_STATUS_INTERNALSERVERERROR,
4826 "osrfMethodException",
4828 "Severe query error -- see error log for more details"
4830 buffer_free( sql_buf );
4831 if( defaultselhash )
4832 jsonObjectFree( defaultselhash );
4833 clear_query_stack();
4836 buffer_add( sql_buf, pred );
4841 char* string = NULL;
4842 if( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4844 growing_buffer* order_buf = buffer_init( 128 );
4847 jsonIterator* class_itr = jsonNewIterator( _tmp );
4848 while( (snode = jsonIteratorNext( class_itr )) ) {
4850 if( !jsonObjectGetKeyConst( selhash,class_itr->key ))
4853 if( snode->type == JSON_HASH ) {
4855 jsonIterator* order_itr = jsonNewIterator( snode );
4856 while( (onode = jsonIteratorNext( order_itr )) ) {
4858 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4859 class_itr->key, order_itr->key );
4863 char* direction = NULL;
4864 if( onode->type == JSON_HASH ) {
4865 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4866 string = searchFieldTransform( class_itr->key, field_def, onode );
4868 osrfAppSessionStatus(
4870 OSRF_STATUS_INTERNALSERVERERROR,
4871 "osrfMethodException",
4873 "Severe query error in ORDER BY clause -- "
4874 "see error log for more details"
4876 jsonIteratorFree( order_itr );
4877 jsonIteratorFree( class_itr );
4878 buffer_free( order_buf );
4879 buffer_free( sql_buf );
4880 if( defaultselhash )
4881 jsonObjectFree( defaultselhash );
4882 clear_query_stack();
4886 growing_buffer* field_buf = buffer_init( 16 );
4887 buffer_fadd( field_buf, "\"%s\".%s",
4888 class_itr->key, order_itr->key );
4889 string = buffer_release( field_buf );
4892 if( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4893 const char* dir = jsonObjectGetString( _tmp );
4894 if(!strncasecmp( dir, "d", 1 )) {
4895 direction = " DESC";
4899 string = strdup( order_itr->key );
4900 const char* dir = jsonObjectGetString( onode );
4901 if( !strncasecmp( dir, "d", 1 )) {
4902 direction = " DESC";
4911 buffer_add( order_buf, ", " );
4914 buffer_add( order_buf, string );
4918 buffer_add( order_buf, direction );
4922 jsonIteratorFree( order_itr );
4925 const char* str = jsonObjectGetString( snode );
4926 buffer_add( order_buf, str );
4932 jsonIteratorFree( class_itr );
4934 string = buffer_release( order_buf );
4937 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4938 OSRF_BUFFER_ADD( sql_buf, string );
4944 if( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ) {
4945 const char* str = jsonObjectGetString( _tmp );
4953 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4955 const char* str = jsonObjectGetString( _tmp );
4964 if( defaultselhash )
4965 jsonObjectFree( defaultselhash );
4966 clear_query_stack();
4968 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4969 return buffer_release( sql_buf );
4972 int doJSONSearch ( osrfMethodContext* ctx ) {
4973 if(osrfMethodVerifyContext( ctx )) {
4974 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4978 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
4983 dbhandle = writehandle;
4985 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
4989 if( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4990 flags |= SELECT_DISTINCT;
4992 if( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4993 flags |= DISABLE_I18N;
4995 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
4996 clear_query_stack(); // a possibly needless precaution
4997 char* sql = buildQuery( ctx, hash, flags );
4998 clear_query_stack();
5005 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5006 dbi_result result = dbi_conn_query( dbhandle, sql );
5009 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5011 if( dbi_result_first_row( result )) {
5012 /* JSONify the result */
5013 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5016 jsonObject* return_val = oilsMakeJSONFromResult( result );
5017 osrfAppRespond( ctx, return_val );
5018 jsonObjectFree( return_val );
5019 } while( dbi_result_next_row( result ));
5022 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5025 osrfAppRespondComplete( ctx, NULL );
5027 /* clean up the query */
5028 dbi_result_free( result );
5032 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]", modulename, sql );
5033 osrfAppSessionStatus(
5035 OSRF_STATUS_INTERNALSERVERERROR,
5036 "osrfMethodException",
5038 "Severe query error -- see error log for more details"
5046 // The last parameter, err, is used to report an error condition by updating an int owned by
5047 // the calling code.
5049 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5050 // It is the responsibility of the calling code to initialize *err before the
5051 // call, so that it will be able to make sense of the result.
5053 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5054 // redundant anyway.
5055 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5056 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5059 dbhandle = writehandle;
5061 char* core_class = osrfHashGet( class_meta, "classname" );
5062 char* pkey = osrfHashGet( class_meta, "primarykey" );
5064 const jsonObject* _tmp;
5066 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5068 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5073 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5075 dbi_result result = dbi_conn_query( dbhandle, sql );
5076 if( NULL == result ) {
5077 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
5078 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql );
5079 osrfAppSessionStatus(
5081 OSRF_STATUS_INTERNALSERVERERROR,
5082 "osrfMethodException",
5084 "Severe query error -- see error log for more details"
5091 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5094 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5095 jsonObject* row_obj = NULL;
5097 if( dbi_result_first_row( result )) {
5099 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5100 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5101 // eliminate the duplicates.
5102 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5103 osrfHash* dedup = osrfNewHash();
5105 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5106 char* pkey_val = oilsFMGetString( row_obj, pkey );
5107 if( osrfHashGet( dedup, pkey_val ) ) {
5108 jsonObjectFree( row_obj );
5111 osrfHashSet( dedup, pkey_val, pkey_val );
5112 jsonObjectPush( res_list, row_obj );
5114 } while( dbi_result_next_row( result ));
5115 osrfHashFree( dedup );
5118 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5122 /* clean up the query */
5123 dbi_result_free( result );
5126 // If we're asked to flesh, and there's anything to flesh, then flesh.
5127 if( res_list->size && query_hash ) {
5128 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5130 // Get the flesh depth
5131 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5132 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5133 flesh_depth = max_flesh_depth;
5135 // We need a non-zero flesh depth, and a list of fields to flesh
5136 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5137 if( temp_blob && flesh_depth > 0 ) {
5139 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5140 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5142 osrfStringArray* link_fields = NULL;
5143 osrfHash* links = osrfHashGet( class_meta, "links" );
5145 // Make an osrfStringArray of the names of fields to be fleshed
5146 if( flesh_fields ) {
5147 if( flesh_fields->size == 1 ) {
5148 const char* _t = jsonObjectGetString(
5149 jsonObjectGetIndex( flesh_fields, 0 ) );
5150 if( !strcmp( _t, "*" ))
5151 link_fields = osrfHashKeys( links );
5154 if( !link_fields ) {
5156 link_fields = osrfNewStringArray( 1 );
5157 jsonIterator* _i = jsonNewIterator( flesh_fields );
5158 while ((_f = jsonIteratorNext( _i ))) {
5159 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5161 jsonIteratorFree( _i );
5165 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5167 // Iterate over the JSON_ARRAY of rows
5169 unsigned long res_idx = 0;
5170 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5173 const char* link_field;
5175 // Iterate over the list of fleshable fields
5176 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5178 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5180 osrfHash* kid_link = osrfHashGet( links, link_field );
5182 continue; // Not a link field; skip it
5184 osrfHash* field = osrfHashGet( fields, link_field );
5186 continue; // Not a field at all; skip it (IDL is ill-formed)
5188 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5189 osrfHashGet( kid_link, "class" ));
5191 continue; // The class it links to doesn't exist; skip it
5193 const char* reltype = osrfHashGet( kid_link, "reltype" );
5195 continue; // No reltype; skip it (IDL is ill-formed)
5197 osrfHash* value_field = field;
5199 if( !strcmp( reltype, "has_many" )
5200 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5201 value_field = osrfHashGet(
5202 fields, osrfHashGet( class_meta, "primarykey" ) );
5205 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5207 if( link_map->size > 0 ) {
5208 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5211 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5216 osrfHashGet( kid_link, "class" ),
5223 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5224 osrfHashGet( kid_link, "field" ),
5225 osrfHashGet( kid_link, "class" ),
5226 osrfHashGet( kid_link, "key" ),
5227 osrfHashGet( kid_link, "reltype" )
5230 const char* search_key = jsonObjectGetString(
5231 jsonObjectGetIndex( cur,
5232 atoi( osrfHashGet( value_field, "array_position" ) )
5237 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5241 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5243 // construct WHERE clause
5244 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5247 osrfHashGet( kid_link, "key" ),
5248 jsonNewObject( search_key )
5251 // construct the rest of the query, mostly
5252 // by copying pieces of the previous level of query
5253 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5254 jsonObjectSetKey( rest_of_query, "flesh",
5255 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5259 jsonObjectSetKey( rest_of_query, "flesh_fields",
5260 jsonObjectClone( flesh_blob ));
5262 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5263 jsonObjectSetKey( rest_of_query, "order_by",
5264 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5268 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5269 jsonObjectSetKey( rest_of_query, "select",
5270 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5274 // do the query, recursively, to expand the fleshable field
5275 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5276 where_clause, rest_of_query, err );
5278 jsonObjectFree( where_clause );
5279 jsonObjectFree( rest_of_query );
5282 osrfStringArrayFree( link_fields );
5283 jsonObjectFree( res_list );
5284 jsonObjectFree( flesh_blob );
5288 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5289 osrfHashGet( kid_link, "class" ), kids->size );
5291 // Traverse the result set
5292 jsonObject* X = NULL;
5293 if( link_map->size > 0 && kids->size > 0 ) {
5295 kids = jsonNewObjectType( JSON_ARRAY );
5297 jsonObject* _k_node;
5298 unsigned long res_idx = 0;
5299 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5305 (unsigned long) atoi(
5311 osrfHashGet( kid_link, "class" )
5315 osrfStringArrayGetString( link_map, 0 )
5323 } // end while loop traversing X
5326 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5327 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5328 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5329 osrfHashGet( kid_link, "field" ));
5332 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5333 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5337 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5339 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5340 osrfHashGet( kid_link, "field" ) );
5343 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5344 jsonObjectClone( kids )
5349 jsonObjectFree( kids );
5353 jsonObjectFree( kids );
5355 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5356 osrfHashGet( kid_link, "field" ) );
5357 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5359 } // end while loop traversing list of fleshable fields
5360 } // end while loop traversing res_list
5361 jsonObjectFree( flesh_blob );
5362 osrfStringArrayFree( link_fields );
5371 int doUpdate( osrfMethodContext* ctx ) {
5372 if( osrfMethodVerifyContext( ctx )) {
5373 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5378 timeout_needs_resetting = 1;
5380 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5382 jsonObject* target = NULL;
5384 target = jsonObjectGetIndex( ctx->params, 1 );
5386 target = jsonObjectGetIndex( ctx->params, 0 );
5388 if(!verifyObjectClass( ctx, target )) {
5389 osrfAppRespondComplete( ctx, NULL );
5393 if( getXactId( ctx ) == NULL ) {
5394 osrfAppSessionStatus(
5396 OSRF_STATUS_BADREQUEST,
5397 "osrfMethodException",
5399 "No active transaction -- required for UPDATE"
5401 osrfAppRespondComplete( ctx, NULL );
5405 // The following test is harmless but redundant. If a class is
5406 // readonly, we don't register an update method for it.
5407 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5408 osrfAppSessionStatus(
5410 OSRF_STATUS_BADREQUEST,
5411 "osrfMethodException",
5413 "Cannot UPDATE readonly class"
5415 osrfAppRespondComplete( ctx, NULL );
5419 dbhandle = writehandle;
5420 const char* trans_id = getXactId( ctx );
5422 // Set the last_xact_id
5423 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5425 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5426 trans_id, target->classname, index );
5427 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5430 char* pkey = osrfHashGet( meta, "primarykey" );
5431 osrfHash* fields = osrfHashGet( meta, "fields" );
5433 char* id = oilsFMGetString( target, pkey );
5437 "%s updating %s object with %s = %s",
5439 osrfHashGet( meta, "fieldmapper" ),
5444 growing_buffer* sql = buffer_init( 128 );
5445 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5448 osrfHash* field_def = NULL;
5449 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5450 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5452 // Skip virtual fields, and the primary key
5453 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5456 const char* field_name = osrfHashIteratorKey( field_itr );
5457 if( ! strcmp( field_name, pkey ) )
5460 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5462 int value_is_numeric = 0; // boolean
5464 if( field_object && field_object->classname ) {
5465 value = oilsFMGetString(
5467 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5469 } else if( field_object && JSON_BOOL == field_object->type ) {
5470 if( jsonBoolIsTrue( field_object ) )
5471 value = strdup( "t" );
5473 value = strdup( "f" );
5475 value = jsonObjectToSimpleString( field_object );
5476 if( field_object && JSON_NUMBER == field_object->type )
5477 value_is_numeric = 1;
5480 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5481 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5483 if( !field_object || field_object->type == JSON_NULL ) {
5484 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5485 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5489 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5490 buffer_fadd( sql, " %s = NULL", field_name );
5493 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5497 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5499 const char* numtype = get_datatype( field_def );
5500 if( !strncmp( numtype, "INT", 3 ) ) {
5501 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5502 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5503 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5505 // Must really be intended as a string, so quote it
5506 if( dbi_conn_quote_string( dbhandle, &value )) {
5507 buffer_fadd( sql, " %s = %s", field_name, value );
5509 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5510 modulename, value );
5511 osrfAppSessionStatus(
5513 OSRF_STATUS_INTERNALSERVERERROR,
5514 "osrfMethodException",
5516 "Error quoting string -- please see the error log for more details"
5520 osrfHashIteratorFree( field_itr );
5522 osrfAppRespondComplete( ctx, NULL );
5527 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5530 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5534 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5535 buffer_fadd( sql, " %s = %s", field_name, value );
5537 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5538 osrfAppSessionStatus(
5540 OSRF_STATUS_INTERNALSERVERERROR,
5541 "osrfMethodException",
5543 "Error quoting string -- please see the error log for more details"
5547 osrfHashIteratorFree( field_itr );
5549 osrfAppRespondComplete( ctx, NULL );
5558 osrfHashIteratorFree( field_itr );
5560 jsonObject* obj = jsonNewObject( id );
5562 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5563 dbi_conn_quote_string( dbhandle, &id );
5565 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5567 char* query = buffer_release( sql );
5568 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5570 dbi_result result = dbi_conn_query( dbhandle, query );
5574 jsonObjectFree( obj );
5575 obj = jsonNewObject( NULL );
5578 "%s ERROR updating %s object with %s = %s",
5580 osrfHashGet( meta, "fieldmapper" ),
5587 osrfAppRespondComplete( ctx, obj );
5588 jsonObjectFree( obj );
5592 int doDelete( osrfMethodContext* ctx ) {
5593 if( osrfMethodVerifyContext( ctx )) {
5594 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5599 timeout_needs_resetting = 1;
5601 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5603 if( getXactId( ctx ) == NULL ) {
5604 osrfAppSessionStatus(
5606 OSRF_STATUS_BADREQUEST,
5607 "osrfMethodException",
5609 "No active transaction -- required for DELETE"
5611 osrfAppRespondComplete( ctx, NULL );
5615 // The following test is harmless but redundant. If a class is
5616 // readonly, we don't register a delete method for it.
5617 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5618 osrfAppSessionStatus(
5620 OSRF_STATUS_BADREQUEST,
5621 "osrfMethodException",
5623 "Cannot DELETE readonly class"
5625 osrfAppRespondComplete( ctx, NULL );
5629 dbhandle = writehandle;
5631 char* pkey = osrfHashGet( meta, "primarykey" );
5638 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5639 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5640 osrfAppRespondComplete( ctx, NULL );
5644 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5646 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5647 osrfAppRespondComplete( ctx, NULL );
5650 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5655 "%s deleting %s object with %s = %s",
5657 osrfHashGet( meta, "fieldmapper" ),
5662 jsonObject* obj = jsonNewObject( id );
5664 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5665 dbi_conn_quote_string( writehandle, &id );
5667 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
5668 osrfHashGet( meta, "tablename" ), pkey, id );
5671 jsonObjectFree( obj );
5672 obj = jsonNewObject( NULL );
5675 "%s ERROR deleting %s object with %s = %s",
5677 osrfHashGet( meta, "fieldmapper" ),
5685 osrfAppRespondComplete( ctx, obj );
5686 jsonObjectFree( obj );
5691 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5692 @param result An iterator for a result set; we only look at the current row.
5693 @param @meta Pointer to the class metadata for the core class.
5694 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5696 If a column is not defined in the IDL, or if it has no array_position defined for it in
5697 the IDL, or if it is defined as virtual, ignore it.
5699 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5700 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5701 array_position in the IDL.
5703 A field defined in the IDL but not represented in the returned row will leave a hole
5704 in the JSON_ARRAY. In effect it will be treated as a null value.
5706 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5707 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5708 classname corresponding to the @a meta argument.
5710 The calling code is responsible for freeing the the resulting jsonObject by calling
5713 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5714 if( !( result && meta )) return NULL;
5716 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5717 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
5718 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
5720 osrfHash* fields = osrfHashGet( meta, "fields" );
5722 int columnIndex = 1;
5723 const char* columnName;
5725 /* cycle through the columns in the row returned from the database */
5726 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
5728 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5730 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5732 /* determine the field type and storage attributes */
5733 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
5734 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5736 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5737 // or if it has no sequence number there, or if it's virtual, skip it.
5738 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
5741 if( str_is_true( osrfHashGet( _f, "virtual" )))
5742 continue; // skip this column: IDL says it's virtual
5744 const char* pos = (char*) osrfHashGet( _f, "array_position" );
5745 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
5746 continue; // since we assign sequence numbers dynamically as we load the IDL.
5748 fmIndex = atoi( pos );
5749 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
5751 continue; // This field is not defined in the IDL
5754 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
5755 // sequence number from the IDL (which is likely to be different from the sequence
5756 // of columns in the SELECT clause).
5757 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5758 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
5763 case DBI_TYPE_INTEGER :
5765 if( attr & DBI_INTEGER_SIZE8 )
5766 jsonObjectSetIndex( object, fmIndex,
5767 jsonNewNumberObject(
5768 dbi_result_get_longlong_idx( result, columnIndex )));
5770 jsonObjectSetIndex( object, fmIndex,
5771 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
5775 case DBI_TYPE_DECIMAL :
5776 jsonObjectSetIndex( object, fmIndex,
5777 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
5780 case DBI_TYPE_STRING :
5785 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
5790 case DBI_TYPE_DATETIME : {
5792 char dt_string[ 256 ] = "";
5795 // Fetch the date column as a time_t
5796 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
5798 // Translate the time_t to a human-readable string
5799 if( !( attr & DBI_DATETIME_DATE )) {
5800 gmtime_r( &_tmp_dt, &gmdt );
5801 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
5802 } else if( !( attr & DBI_DATETIME_TIME )) {
5803 localtime_r( &_tmp_dt, &gmdt );
5804 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
5806 localtime_r( &_tmp_dt, &gmdt );
5807 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
5810 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
5814 case DBI_TYPE_BINARY :
5815 osrfLogError( OSRF_LOG_MARK,
5816 "Can't do binary at column %s : index %d", columnName, columnIndex );
5825 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5826 if( !result ) return NULL;
5828 jsonObject* object = jsonNewObject( NULL );
5831 char dt_string[ 256 ];
5835 int columnIndex = 1;
5837 unsigned short type;
5838 const char* columnName;
5840 /* cycle through the column list */
5841 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
5843 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5845 fmIndex = -1; // reset the position
5847 /* determine the field type and storage attributes */
5848 type = dbi_result_get_field_type_idx( result, columnIndex );
5849 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5851 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5852 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
5857 case DBI_TYPE_INTEGER :
5859 if( attr & DBI_INTEGER_SIZE8 )
5860 jsonObjectSetKey( object, columnName,
5861 jsonNewNumberObject( dbi_result_get_longlong_idx(
5862 result, columnIndex )) );
5864 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
5865 dbi_result_get_int_idx( result, columnIndex )) );
5868 case DBI_TYPE_DECIMAL :
5869 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
5870 dbi_result_get_double_idx( result, columnIndex )) );
5873 case DBI_TYPE_STRING :
5874 jsonObjectSetKey( object, columnName,
5875 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
5878 case DBI_TYPE_DATETIME :
5880 memset( dt_string, '\0', sizeof( dt_string ));
5881 memset( &gmdt, '\0', sizeof( gmdt ));
5883 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
5885 if( !( attr & DBI_DATETIME_DATE )) {
5886 gmtime_r( &_tmp_dt, &gmdt );
5887 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
5888 } else if( !( attr & DBI_DATETIME_TIME )) {
5889 localtime_r( &_tmp_dt, &gmdt );
5890 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
5892 localtime_r( &_tmp_dt, &gmdt );
5893 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
5896 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
5899 case DBI_TYPE_BINARY :
5900 osrfLogError( OSRF_LOG_MARK,
5901 "Can't do binary at column %s : index %d", columnName, columnIndex );
5905 } // end while loop traversing result
5910 // Interpret a string as true or false
5911 int str_is_true( const char* str ) {
5912 if( NULL == str || strcasecmp( str, "true" ) )
5918 // Interpret a jsonObject as true or false
5919 static int obj_is_true( const jsonObject* obj ) {
5922 else switch( obj->type )
5930 if( strcasecmp( obj->value.s, "true" ) )
5934 case JSON_NUMBER : // Support 1/0 for perl's sake
5935 if( jsonObjectGetNumber( obj ) == 1.0 )
5944 // Translate a numeric code into a text string identifying a type of
5945 // jsonObject. To be used for building error messages.
5946 static const char* json_type( int code ) {
5952 return "JSON_ARRAY";
5954 return "JSON_STRING";
5956 return "JSON_NUMBER";
5962 return "(unrecognized)";
5966 // Extract the "primitive" attribute from an IDL field definition.
5967 // If we haven't initialized the app, then we must be running in
5968 // some kind of testbed. In that case, default to "string".
5969 static const char* get_primitive( osrfHash* field ) {
5970 const char* s = osrfHashGet( field, "primitive" );
5972 if( child_initialized )
5975 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5977 osrfHashGet( field, "name" )
5985 // Extract the "datatype" attribute from an IDL field definition.
5986 // If we haven't initialized the app, then we must be running in
5987 // some kind of testbed. In that case, default to to NUMERIC,
5988 // since we look at the datatype only for numbers.
5989 static const char* get_datatype( osrfHash* field ) {
5990 const char* s = osrfHashGet( field, "datatype" );
5992 if( child_initialized )
5995 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5997 osrfHashGet( field, "name" )
6006 @brief Determine whether a string is potentially a valid SQL identifier.
6007 @param s The identifier to be tested.
6008 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6010 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6011 need to follow all the rules exactly, such as requiring that the first character not
6014 We allow leading and trailing white space. In between, we do not allow punctuation
6015 (except for underscores and dollar signs), control characters, or embedded white space.
6017 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6018 for the foreseeable future such quoted identifiers are not likely to be an issue.
6020 static int is_identifier( const char* s) {
6024 // Skip leading white space
6025 while( isspace( (unsigned char) *s ) )
6029 return 0; // Nothing but white space? Not okay.
6031 // Check each character until we reach white space or
6032 // end-of-string. Letters, digits, underscores, and
6033 // dollar signs are okay. With the exception of periods
6034 // (as in schema.identifier), control characters and other
6035 // punctuation characters are not okay. Anything else
6036 // is okay -- it could for example be part of a multibyte
6037 // UTF8 character such as a letter with diacritical marks,
6038 // and those are allowed.
6040 if( isalnum( (unsigned char) *s )
6044 ; // Fine; keep going
6045 else if( ispunct( (unsigned char) *s )
6046 || iscntrl( (unsigned char) *s ) )
6049 } while( *s && ! isspace( (unsigned char) *s ) );
6051 // If we found any white space in the above loop,
6052 // the rest had better be all white space.
6054 while( isspace( (unsigned char) *s ) )
6058 return 0; // White space was embedded within non-white space
6064 @brief Determine whether to accept a character string as a comparison operator.
6065 @param op The candidate comparison operator.
6066 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6068 We don't validate the operator for real. We just make sure that it doesn't contain
6069 any semicolons or white space (with special exceptions for a few specific operators).
6070 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6071 space but it's still not a valid operator, then the database will complain.
6073 Another approach would be to compare the string against a short list of approved operators.
6074 We don't do that because we want to allow custom operators like ">100*", which at this
6075 writing would be difficult or impossible to express otherwise in a JSON query.
6077 static int is_good_operator( const char* op ) {
6078 if( !op ) return 0; // Sanity check
6082 if( isspace( (unsigned char) *s ) ) {
6083 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6084 // and IS NOT DISTINCT FROM.
6085 if( !strcasecmp( op, "similar to" ) )
6087 else if( !strcasecmp( op, "is distinct from" ) )
6089 else if( !strcasecmp( op, "is not distinct from" ) )
6094 else if( ';' == *s )
6102 @name Query Frame Management
6104 The following machinery supports a stack of query frames for use by SELECT().
6106 A query frame caches information about one level of a SELECT query. When we enter
6107 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6109 The query frame stores information about the core class, and about any joined classes
6112 The main purpose is to map table aliases to classes and tables, so that a query can
6113 join to the same table more than once. A secondary goal is to reduce the number of
6114 lookups in the IDL by caching the results.
6118 #define STATIC_CLASS_INFO_COUNT 3
6120 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6123 @brief Allocate a ClassInfo as raw memory.
6124 @return Pointer to the newly allocated ClassInfo.
6126 Except for the in_use flag, which is used only by the allocation and deallocation
6127 logic, we don't initialize the ClassInfo here.
6129 static ClassInfo* allocate_class_info( void ) {
6130 // In order to reduce the number of mallocs and frees, we return a static
6131 // instance of ClassInfo, if we can find one that we're not already using.
6132 // We rely on the fact that the compiler will implicitly initialize the
6133 // static instances so that in_use == 0.
6136 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6137 if( ! static_class_info[ i ].in_use ) {
6138 static_class_info[ i ].in_use = 1;
6139 return static_class_info + i;
6143 // The static ones are all in use. Malloc one.
6145 return safe_malloc( sizeof( ClassInfo ) );
6149 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6150 @param info Pointer to the ClassInfo to be cleared.
6152 static void clear_class_info( ClassInfo* info ) {
6157 // Free any malloc'd strings
6159 if( info->alias != info->alias_store )
6160 free( info->alias );
6162 if( info->class_name != info->class_name_store )
6163 free( info->class_name );
6165 free( info->source_def );
6167 info->alias = info->class_name = info->source_def = NULL;
6172 @brief Free a ClassInfo and everything it owns.
6173 @param info Pointer to the ClassInfo to be freed.
6175 static void free_class_info( ClassInfo* info ) {
6180 clear_class_info( info );
6182 // If it's one of the static instances, just mark it as not in use
6185 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6186 if( info == static_class_info + i ) {
6187 static_class_info[ i ].in_use = 0;
6192 // Otherwise it must have been malloc'd, so free it
6198 @brief Populate an already-allocated ClassInfo.
6199 @param info Pointer to the ClassInfo to be populated.
6200 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6202 @param class Name of the class.
6203 @return Zero if successful, or 1 if not.
6205 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6206 the relevant portions of the IDL for the specified class.
6208 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6211 osrfLogError( OSRF_LOG_MARK,
6212 "%s ERROR: No ClassInfo available to populate", modulename );
6213 info->alias = info->class_name = info->source_def = NULL;
6214 info->class_def = info->fields = info->links = NULL;
6219 osrfLogError( OSRF_LOG_MARK,
6220 "%s ERROR: No class name provided for lookup", modulename );
6221 info->alias = info->class_name = info->source_def = NULL;
6222 info->class_def = info->fields = info->links = NULL;
6226 // Alias defaults to class name if not supplied
6227 if( ! alias || ! alias[ 0 ] )
6230 // Look up class info in the IDL
6231 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6233 osrfLogError( OSRF_LOG_MARK,
6234 "%s ERROR: Class %s not defined in IDL", modulename, class );
6235 info->alias = info->class_name = info->source_def = NULL;
6236 info->class_def = info->fields = info->links = NULL;
6238 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6239 osrfLogError( OSRF_LOG_MARK,
6240 "%s ERROR: Class %s is defined as virtual", modulename, class );
6241 info->alias = info->class_name = info->source_def = NULL;
6242 info->class_def = info->fields = info->links = NULL;
6246 osrfHash* links = osrfHashGet( class_def, "links" );
6248 osrfLogError( OSRF_LOG_MARK,
6249 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6250 info->alias = info->class_name = info->source_def = NULL;
6251 info->class_def = info->fields = info->links = NULL;
6255 osrfHash* fields = osrfHashGet( class_def, "fields" );
6257 osrfLogError( OSRF_LOG_MARK,
6258 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6259 info->alias = info->class_name = info->source_def = NULL;
6260 info->class_def = info->fields = info->links = NULL;
6264 char* source_def = oilsGetRelation( class_def );
6268 // We got everything we need, so populate the ClassInfo
6269 if( strlen( alias ) > ALIAS_STORE_SIZE )
6270 info->alias = strdup( alias );
6272 strcpy( info->alias_store, alias );
6273 info->alias = info->alias_store;
6276 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6277 info->class_name = strdup( class );
6279 strcpy( info->class_name_store, class );
6280 info->class_name = info->class_name_store;
6283 info->source_def = source_def;
6285 info->class_def = class_def;
6286 info->links = links;
6287 info->fields = fields;
6292 #define STATIC_FRAME_COUNT 3
6294 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6297 @brief Allocate a QueryFrame as raw memory.
6298 @return Pointer to the newly allocated QueryFrame.
6300 Except for the in_use flag, which is used only by the allocation and deallocation
6301 logic, we don't initialize the QueryFrame here.
6303 static QueryFrame* allocate_frame( void ) {
6304 // In order to reduce the number of mallocs and frees, we return a static
6305 // instance of QueryFrame, if we can find one that we're not already using.
6306 // We rely on the fact that the compiler will implicitly initialize the
6307 // static instances so that in_use == 0.
6310 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6311 if( ! static_frame[ i ].in_use ) {
6312 static_frame[ i ].in_use = 1;
6313 return static_frame + i;
6317 // The static ones are all in use. Malloc one.
6319 return safe_malloc( sizeof( QueryFrame ) );
6323 @brief Free a QueryFrame, and all the memory it owns.
6324 @param frame Pointer to the QueryFrame to be freed.
6326 static void free_query_frame( QueryFrame* frame ) {
6331 clear_class_info( &frame->core );
6333 // Free the join list
6335 ClassInfo* info = frame->join_list;
6338 free_class_info( info );
6342 frame->join_list = NULL;
6345 // If the frame is a static instance, just mark it as unused
6347 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6348 if( frame == static_frame + i ) {
6349 static_frame[ i ].in_use = 0;
6354 // Otherwise it must have been malloc'd, so free it
6360 @brief Search a given QueryFrame for a specified alias.
6361 @param frame Pointer to the QueryFrame to be searched.
6362 @param target The alias for which to search.
6363 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6365 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6366 if( ! frame || ! target ) {
6370 ClassInfo* found_class = NULL;
6372 if( !strcmp( target, frame->core.alias ) )
6373 return &(frame->core);
6375 ClassInfo* curr_class = frame->join_list;
6376 while( curr_class ) {
6377 if( strcmp( target, curr_class->alias ) )
6378 curr_class = curr_class->next;
6380 found_class = curr_class;
6390 @brief Push a new (blank) QueryFrame onto the stack.
6392 static void push_query_frame( void ) {
6393 QueryFrame* frame = allocate_frame();
6394 frame->join_list = NULL;
6395 frame->next = curr_query;
6397 // Initialize the ClassInfo for the core class
6398 ClassInfo* core = &frame->core;
6399 core->alias = core->class_name = core->source_def = NULL;
6400 core->class_def = core->fields = core->links = NULL;
6406 @brief Pop a QueryFrame off the stack and destroy it.
6408 static void pop_query_frame( void ) {
6413 QueryFrame* popped = curr_query;
6414 curr_query = popped->next;
6416 free_query_frame( popped );
6420 @brief Populate the ClassInfo for the core class.
6421 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6422 class name as an alias.
6423 @param class_name Name of the core class.
6424 @return Zero if successful, or 1 if not.
6426 Populate the ClassInfo of the core class with copies of the alias and class name, and
6427 with pointers to the relevant portions of the IDL for the core class.
6429 static int add_query_core( const char* alias, const char* class_name ) {
6432 if( ! curr_query ) {
6433 osrfLogError( OSRF_LOG_MARK,
6434 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6436 } else if( curr_query->core.alias ) {
6437 osrfLogError( OSRF_LOG_MARK,
6438 "%s ERROR: Core class %s already populated as %s",
6439 modulename, curr_query->core.class_name, curr_query->core.alias );
6443 build_class_info( &curr_query->core, alias, class_name );
6444 if( curr_query->core.alias )
6447 osrfLogError( OSRF_LOG_MARK,
6448 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6454 @brief Search the current QueryFrame for a specified alias.
6455 @param target The alias for which to search.
6456 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6458 static inline ClassInfo* search_alias( const char* target ) {
6459 return search_alias_in_frame( curr_query, target );
6463 @brief Search all levels of query for a specified alias, starting with the current query.
6464 @param target The alias for which to search.
6465 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6467 static ClassInfo* search_all_alias( const char* target ) {
6468 ClassInfo* found_class = NULL;
6469 QueryFrame* curr_frame = curr_query;
6471 while( curr_frame ) {
6472 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6475 curr_frame = curr_frame->next;
6482 @brief Add a class to the list of classes joined to the current query.
6483 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6484 the class name as an alias.
6485 @param classname The name of the class to be added.
6486 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6488 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6490 if( ! classname || ! *classname ) { // sanity check
6491 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6498 const ClassInfo* conflict = search_alias( alias );
6500 osrfLogError( OSRF_LOG_MARK,
6501 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6502 modulename, alias, conflict->class_name );
6506 ClassInfo* info = allocate_class_info();
6508 if( build_class_info( info, alias, classname ) ) {
6509 free_class_info( info );
6513 // Add the new ClassInfo to the join list of the current QueryFrame
6514 info->next = curr_query->join_list;
6515 curr_query->join_list = info;
6521 @brief Destroy all nodes on the query stack.
6523 static void clear_query_stack( void ) {