3 @brief Utility routines for translating JSON into SQL.
11 #include "opensrf/utils.h"
12 #include "opensrf/log.h"
13 #include "opensrf/osrf_application.h"
14 #include "openils/oils_utils.h"
15 #include "openils/oils_sql.h"
17 // The next four macros are OR'd together as needed to form a set
18 // of bitflags. SUBCOMBO enables an extra pair of parentheses when
19 // nesting one UNION, INTERSECT or EXCEPT inside another.
20 // SUBSELECT tells us we're in a subquery, so don't add the
21 // terminal semicolon yet.
24 #define DISABLE_I18N 2
25 #define SELECT_DISTINCT 1
30 struct ClassInfoStruct;
31 typedef struct ClassInfoStruct ClassInfo;
33 #define ALIAS_STORE_SIZE 16
34 #define CLASS_NAME_STORE_SIZE 16
36 struct ClassInfoStruct {
40 osrfHash* class_def; // Points into IDL
41 osrfHash* fields; // Points into IDL
42 osrfHash* links; // Points into IDL
44 // The remaining members are private and internal. Client code should not
45 // access them directly.
47 ClassInfo* next; // Supports linked list of joined classes
48 int in_use; // boolean
50 // We usually store the alias and class name in the following arrays, and
51 // point the corresponding pointers at them. When the string is too big
52 // for the array (which will probably never happen in practice), we strdup it.
54 char alias_store[ ALIAS_STORE_SIZE + 1 ];
55 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
58 struct QueryFrameStruct;
59 typedef struct QueryFrameStruct QueryFrame;
61 struct QueryFrameStruct {
63 ClassInfo* join_list; // linked list of classes joined to the core class
64 QueryFrame* next; // implements stack as linked list
65 int in_use; // boolean
68 static int timeout_needs_resetting;
69 static time_t time_next_reset;
71 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
73 static void setXactId( osrfMethodContext* ctx );
74 static inline const char* getXactId( osrfMethodContext* ctx );
75 static inline void clearXactId( osrfMethodContext* ctx );
77 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
78 jsonObject* where_hash, jsonObject* query_hash, int* err );
79 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
80 static jsonObject* oilsMakeJSONFromResult( dbi_result );
82 static char* searchSimplePredicate ( const char* op, const char* class_alias,
83 osrfHash* field, const jsonObject* node );
84 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
85 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject* );
86 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*,
88 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
89 static char* searchINPredicate ( const char*, osrfHash*,
90 jsonObject*, const char*, osrfMethodContext* );
91 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
92 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
93 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo*, int, osrfMethodContext* );
94 static char* buildSELECT( const jsonObject*, jsonObject* rest_of_query,
95 osrfHash* meta, osrfMethodContext* ctx );
96 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array );
98 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
100 char* SELECT ( osrfMethodContext*, jsonObject*, const jsonObject*, const jsonObject*,
101 const jsonObject*, const jsonObject*, const jsonObject*, const jsonObject*, int );
103 static osrfStringArray* getPermLocationCache( osrfMethodContext*, const char* );
104 static void setPermLocationCache( osrfMethodContext*, const char*, osrfStringArray* );
106 void userDataFree( void* );
107 static void sessionDataFree( char*, void* );
108 static void pcacheFree( char*, void* );
109 static int obj_is_true( const jsonObject* obj );
110 static const char* json_type( int code );
111 static const char* get_primitive( osrfHash* field );
112 static const char* get_datatype( osrfHash* field );
113 static void pop_query_frame( void );
114 static void push_query_frame( void );
115 static int add_query_core( const char* alias, const char* class_name );
116 static inline ClassInfo* search_alias( const char* target );
117 static ClassInfo* search_all_alias( const char* target );
118 static ClassInfo* add_joined_class( const char* alias, const char* classname );
119 static void clear_query_stack( void );
121 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
122 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject*, const int );
123 static const char* org_tree_root( osrfMethodContext* ctx );
124 static jsonObject* single_hash( const char* key, const char* value );
126 static int child_initialized = 0; /* boolean */
128 static dbi_conn writehandle; /* our MASTER db connection */
129 static dbi_conn dbhandle; /* our CURRENT db connection */
130 //static osrfHash * readHandles;
132 // The following points to the top of a stack of QueryFrames. It's a little
133 // confusing because the top level of the query is at the bottom of the stack.
134 static QueryFrame* curr_query = NULL;
136 static dbi_conn writehandle; /* our MASTER db connection */
137 static dbi_conn dbhandle; /* our CURRENT db connection */
138 //static osrfHash * readHandles;
140 static int max_flesh_depth = 100;
142 static int perm_at_threshold = 5;
143 static int enforce_pcrud = 0; // Boolean
144 static char* modulename = NULL;
147 @brief Connect to the database.
148 @return A database connection if successful, or NULL if not.
150 dbi_conn oilsConnectDB( const char* mod_name ) {
152 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
153 if( dbi_initialize( NULL ) == -1 ) {
154 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
157 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
159 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
160 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
161 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
162 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
163 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
164 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
166 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
167 dbi_conn handle = dbi_conn_new( driver );
170 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
173 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
175 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
176 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
178 if( host ) dbi_conn_set_option( handle, "host", host );
179 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
180 if( user ) dbi_conn_set_option( handle, "username", user );
181 if( pw ) dbi_conn_set_option( handle, "password", pw );
182 if( db ) dbi_conn_set_option( handle, "dbname", db );
190 if( dbi_conn_connect( handle ) < 0 ) {
192 if( dbi_conn_connect( handle ) < 0 ) {
194 dbi_conn_error( handle, &msg );
195 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
196 msg ? msg : "(No description available)" );
201 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
207 @brief Select some options.
208 @param module_name: Name of the server.
209 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
211 This source file is used (at this writing) to implement three different servers:
212 - open-ils.reporter-store
216 These servers behave mostly the same, but they implement different combinations of
217 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
219 Here we use the server name in messages to identify which kind of server issued them.
220 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
222 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
224 module_name = "open-ils.cstore"; // bulletproofing with a default
229 modulename = strdup( module_name );
230 enforce_pcrud = do_pcrud;
231 max_flesh_depth = flesh_depth;
235 @brief Install a database connection.
236 @param conn Pointer to a database connection.
238 In some contexts, @a conn may merely provide a driver so that we can process strings
239 properly, without providing an open database connection.
241 void oilsSetDBConnection( dbi_conn conn ) {
242 dbhandle = writehandle = conn;
246 @brief Determine whether a database connection is alive.
247 @param handle Handle for a database connection.
248 @return 1 if the connection is alive, or zero if it isn't.
250 int oilsIsDBConnected( dbi_conn handle ) {
251 // Do an innocuous SELECT. If it succeeds, the database connection is still good.
252 dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
254 dbi_result_free( result );
257 // This is a terrible, horrible, no good, very bad kludge.
258 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
259 // but because (due to a previous error) the database is ignoring all commands,
260 // even innocuous SELECTs, until the current transaction is rolled back. The only
261 // known way to detect this condition via the dbi library is by looking at the error
262 // message. This approach will break if the language or wording of the message ever
264 // Note: the dbi_conn_ping function purports to determine whether the doatabase
265 // connection is live, but at this writing this function is unreliable and useless.
266 static const char* ok_msg = "ERROR: current transaction is aborted, commands "
267 "ignored until end of transaction block\n";
269 dbi_conn_error( handle, &msg );
270 if( strcmp( msg, ok_msg )) {
271 osrfLogError( OSRF_LOG_MARK, "Database connection isn't working" );
274 return 1; // ignoring SELECT due to previous error; that's okay
279 @brief Get a table name, view name, or subquery for use in a FROM clause.
280 @param class Pointer to the IDL class entry.
281 @return A table name, a view name, or a subquery in parentheses.
283 In some cases the IDL defines a class, not with a table name or a view name, but with
284 a SELECT statement, which may be used as a subquery.
286 char* oilsGetRelation( osrfHash* classdef ) {
288 char* source_def = NULL;
289 const char* tabledef = osrfHashGet( classdef, "tablename" );
292 source_def = strdup( tabledef ); // Return the name of a table or view
294 tabledef = osrfHashGet( classdef, "source_definition" );
296 // Return a subquery, enclosed in parentheses
297 source_def = safe_malloc( strlen( tabledef ) + 3 );
298 source_def[ 0 ] = '(';
299 strcpy( source_def + 1, tabledef );
300 strcat( source_def, ")" );
302 // Not found: return an error
303 const char* classname = osrfHashGet( classdef, "classname" );
308 "%s ERROR No tablename or source_definition for class \"%s\"",
319 @brief Add datatypes from the database to the fields in the IDL.
320 @param handle Handle for a database connection
321 @return Zero if successful, or 1 upon error.
323 For each relevant class in the IDL: ask the database for the datatype of every field.
324 In particular, determine which fields are text fields and which fields are numeric
325 fields, so that we know whether to enclose their values in quotes.
327 int oilsExtendIDL( dbi_conn handle ) {
328 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
329 osrfHash* class = NULL;
330 growing_buffer* query_buf = buffer_init( 64 );
331 int results_found = 0; // boolean
333 // For each class in the IDL...
334 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
335 const char* classname = osrfHashIteratorKey( class_itr );
336 osrfHash* fields = osrfHashGet( class, "fields" );
338 // If the class is virtual, ignore it
339 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
340 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
344 char* tabledef = oilsGetRelation( class );
346 continue; // No such relation -- a query of it would be doomed to failure
348 buffer_reset( query_buf );
349 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
353 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
354 modulename, OSRF_BUFFER_C_STR( query_buf ) );
356 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
361 const char* columnName;
362 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
364 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
367 /* fetch the fieldmapper index */
368 osrfHash* _f = osrfHashGet(fields, columnName);
371 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
373 /* determine the field type and storage attributes */
375 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
377 case DBI_TYPE_INTEGER : {
379 if( !osrfHashGet(_f, "primitive") )
380 osrfHashSet(_f, "number", "primitive");
382 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
383 if( attr & DBI_INTEGER_SIZE8 )
384 osrfHashSet( _f, "INT8", "datatype" );
386 osrfHashSet( _f, "INT", "datatype" );
389 case DBI_TYPE_DECIMAL :
390 if( !osrfHashGet( _f, "primitive" ))
391 osrfHashSet( _f, "number", "primitive" );
393 osrfHashSet( _f, "NUMERIC", "datatype" );
396 case DBI_TYPE_STRING :
397 if( !osrfHashGet( _f, "primitive" ))
398 osrfHashSet( _f, "string", "primitive" );
400 osrfHashSet( _f,"TEXT", "datatype" );
403 case DBI_TYPE_DATETIME :
404 if( !osrfHashGet( _f, "primitive" ))
405 osrfHashSet( _f, "string", "primitive" );
407 osrfHashSet( _f, "TIMESTAMP", "datatype" );
410 case DBI_TYPE_BINARY :
411 if( !osrfHashGet( _f, "primitive" ))
412 osrfHashSet( _f, "string", "primitive" );
414 osrfHashSet( _f, "BYTEA", "datatype" );
419 "Setting [%s] to primitive [%s] and datatype [%s]...",
421 osrfHashGet( _f, "primitive" ),
422 osrfHashGet( _f, "datatype" )
426 } // end while loop for traversing columns of result
427 dbi_result_free( result );
430 int errnum = dbi_conn_error( handle, &msg );
431 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
432 errnum, msg ? msg : "(No description available)" );
433 // We don't check the database connection here. It's routine to get failures at
434 // this point; we routinely try to query tables that don't exist, because they
435 // are defined in the IDL but not in the database.
437 } // end for each class in IDL
439 buffer_free( query_buf );
440 osrfHashIteratorFree( class_itr );
441 child_initialized = 1;
443 if( !results_found ) {
444 osrfLogError( OSRF_LOG_MARK,
445 "No results found for any class -- bad database connection?" );
447 } else if( ! oilsIsDBConnected( handle )) {
448 osrfLogError( OSRF_LOG_MARK,
449 "Unable to extend IDL: database connection isn't working" );
457 @brief Free an osrfHash that stores a transaction ID.
458 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
460 This function is a callback, to be called by the application session when it ends.
461 The application session stores the osrfHash via an opaque pointer.
463 If the osrfHash contains an entry for the key "xact_id", it means that an
464 uncommitted transaction is pending. Roll it back.
466 void userDataFree( void* blob ) {
467 osrfHash* hash = (osrfHash*) blob;
468 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
469 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
471 int errnum = dbi_conn_error( writehandle, &msg );
472 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
473 errnum, msg ? msg : "(No description available)" );
477 osrfHashFree( hash );
481 @name Managing session data
482 @brief Maintain data stored via the userData pointer of the application session.
484 Currently, session-level data is stored in an osrfHash. Other arrangements are
485 possible, and some would be more efficient. The application session calls a
486 callback function to free userData before terminating.
488 Currently, the only data we store at the session level is the transaction id. By this
489 means we can ensure that any pending transactions are rolled back before the application
495 @brief Free an item in the application session's userData.
496 @param key The name of a key for an osrfHash.
497 @param item An opaque pointer to the item associated with the key.
499 We store an osrfHash as userData with the application session, and arrange (by
500 installing userDataFree() as a different callback) for the session to free that
501 osrfHash before terminating.
503 This function is a callback for freeing items in the osrfHash. Currently we store
505 - Transaction id of a pending transaction; a character string. Key: "xact_id".
506 - Authkey; a character string. Key: "authkey".
507 - User object from the authentication server; a jsonObject. Key: "user_login".
509 If we ever store anything else in userData, we will need to revisit this function so
510 that it will free whatever else needs freeing.
512 static void sessionDataFree( char* key, void* item ) {
513 if( !strcmp( key, "xact_id" ) || !strcmp( key, "authkey" ) )
515 else if( !strcmp( key, "user_login" ) )
516 jsonObjectFree( (jsonObject*) item );
517 else if( !strcmp( key, "pcache" ) )
518 osrfHashFree( (osrfHash*) item );
521 static void pcacheFree( char* key, void* item ) {
522 osrfStringArrayFree( (osrfStringArray*) item );
526 @brief Save a transaction id.
527 @param ctx Pointer to the method context.
529 Save the session_id of the current application session as a transaction id.
531 static void setXactId( osrfMethodContext* ctx ) {
532 if( ctx && ctx->session ) {
533 osrfAppSession* session = ctx->session;
535 osrfHash* cache = session->userData;
537 // If the session doesn't already have a hash, create one. Make sure
538 // that the application session frees the hash when it terminates.
539 if( NULL == cache ) {
540 session->userData = cache = osrfNewHash();
541 osrfHashSetCallback( cache, &sessionDataFree );
542 ctx->session->userDataFree = &userDataFree;
545 // Save the transaction id in the hash, with the key "xact_id"
546 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
551 @brief Get the transaction ID for the current transaction, if any.
552 @param ctx Pointer to the method context.
553 @return Pointer to the transaction ID.
555 The return value points to an internal buffer, and will become invalid upon issuing
556 a commit or rollback.
558 static inline const char* getXactId( osrfMethodContext* ctx ) {
559 if( ctx && ctx->session && ctx->session->userData )
560 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
566 @brief Clear the current transaction id.
567 @param ctx Pointer to the method context.
569 static inline void clearXactId( osrfMethodContext* ctx ) {
570 if( ctx && ctx->session && ctx->session->userData )
571 osrfHashRemove( ctx->session->userData, "xact_id" );
576 @brief Stash the location for a particular perm in the sessionData cache
577 @param ctx Pointer to the method context.
578 @param perm Name of the permission we're looking at
579 @param array StringArray of perm location ids
581 static void setPermLocationCache( osrfMethodContext* ctx, const char* perm, osrfStringArray* locations ) {
582 if( ctx && ctx->session ) {
583 osrfAppSession* session = ctx->session;
585 osrfHash* cache = session->userData;
587 // If the session doesn't already have a hash, create one. Make sure
588 // that the application session frees the hash when it terminates.
589 if( NULL == cache ) {
590 session->userData = cache = osrfNewHash();
591 osrfHashSetCallback( cache, &sessionDataFree );
592 ctx->session->userDataFree = &userDataFree;
595 osrfHash* pcache = osrfHashGet(cache, "pcache");
597 if( NULL == pcache ) {
598 pcache = osrfNewHash();
599 osrfHashSetCallback( pcache, &pcacheFree );
600 osrfHashSet( cache, pcache, "pcache" );
603 if( perm && locations )
604 osrfHashSet( pcache, locations, strdup(perm) );
609 @brief Grab stashed location for a particular perm in the sessionData cache
610 @param ctx Pointer to the method context.
611 @param perm Name of the permission we're looking at
613 static osrfStringArray* getPermLocationCache( osrfMethodContext* ctx, const char* perm ) {
614 if( ctx && ctx->session ) {
615 osrfAppSession* session = ctx->session;
616 osrfHash* cache = session->userData;
618 osrfHash* pcache = osrfHashGet(cache, "pcache");
620 return osrfHashGet( pcache, perm );
629 @brief Save the user's login in the userData for the current application session.
630 @param ctx Pointer to the method context.
631 @param user_login Pointer to the user login object to be cached (we cache the original,
634 If @a user_login is NULL, remove the user login if one is already cached.
636 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
637 if( ctx && ctx->session ) {
638 osrfAppSession* session = ctx->session;
640 osrfHash* cache = session->userData;
642 // If the session doesn't already have a hash, create one. Make sure
643 // that the application session frees the hash when it terminates.
644 if( NULL == cache ) {
645 session->userData = cache = osrfNewHash();
646 osrfHashSetCallback( cache, &sessionDataFree );
647 ctx->session->userDataFree = &userDataFree;
651 osrfHashSet( cache, user_login, "user_login" );
653 osrfHashRemove( cache, "user_login" );
658 @brief Get the user login object for the current application session, if any.
659 @param ctx Pointer to the method context.
660 @return Pointer to the user login object if found; otherwise NULL.
662 The user login object was returned from the authentication server, and then cached so
663 we don't have to call the authentication server again for the same user.
665 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
666 if( ctx && ctx->session && ctx->session->userData )
667 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
673 @brief Save a copy of an authkey in the userData of the current application session.
674 @param ctx Pointer to the method context.
675 @param authkey The authkey to be saved.
677 If @a authkey is NULL, remove the authkey if one is already cached.
679 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
680 if( ctx && ctx->session && authkey ) {
681 osrfAppSession* session = ctx->session;
682 osrfHash* cache = session->userData;
684 // If the session doesn't already have a hash, create one. Make sure
685 // that the application session frees the hash when it terminates.
686 if( NULL == cache ) {
687 session->userData = cache = osrfNewHash();
688 osrfHashSetCallback( cache, &sessionDataFree );
689 ctx->session->userDataFree = &userDataFree;
692 // Save the transaction id in the hash, with the key "xact_id"
693 if( authkey && *authkey )
694 osrfHashSet( cache, strdup( authkey ), "authkey" );
696 osrfHashRemove( cache, "authkey" );
701 @brief Reset the login timeout.
702 @param authkey The authentication key for the current login session.
703 @param now The current time.
704 @return Zero if successful, or 1 if not.
706 Tell the authentication server to reset the timeout so that the login session won't
707 expire for a while longer.
709 We could dispense with the @a now parameter by calling time(). But we just called
710 time() in order to decide whether to reset the timeout, so we might as well reuse
711 the result instead of calling time() again.
713 static int reset_timeout( const char* authkey, time_t now ) {
714 jsonObject* auth_object = jsonNewObject( authkey );
716 // Ask the authentication server to reset the timeout. It returns an event
717 // indicating success or failure.
718 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
719 "open-ils.auth.session.reset_timeout", auth_object );
720 jsonObjectFree( auth_object );
722 if( !result || result->type != JSON_HASH ) {
723 osrfLogError( OSRF_LOG_MARK,
724 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
725 jsonObjectFree( result );
726 return 1; // Not the right sort of object returned
729 const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
730 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
731 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
732 jsonObjectFree( result );
733 return 1; // Return code from method not available
736 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
737 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
739 desc = "(No reason available)"; // failsafe; shouldn't happen
740 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
741 jsonObjectFree( result );
745 // Revise our local proxy for the timeout deadline
746 // by a smallish fraction of the timeout interval
747 const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
749 timeout = "1"; // failsafe; shouldn't happen
750 time_next_reset = now + atoi( timeout ) / 15;
752 jsonObjectFree( result );
753 return 0; // Successfully reset timeout
757 @brief Get the authkey string for the current application session, if any.
758 @param ctx Pointer to the method context.
759 @return Pointer to the cached authkey if found; otherwise NULL.
761 If present, the authkey string was cached from a previous method call.
763 static const char* getAuthkey( osrfMethodContext* ctx ) {
764 if( ctx && ctx->session && ctx->session->userData ) {
765 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
767 // Possibly reset the authentication timeout to keep the login alive. We do so
768 // no more than once per method call, and not at all if it has been only a short
769 // time since the last reset.
771 // Here we reset explicitly, if at all. We also implicitly reset the timeout
772 // whenever we call the "open-ils.auth.session.retrieve" method.
773 if( timeout_needs_resetting ) {
774 time_t now = time( NULL );
775 if( now >= time_next_reset && reset_timeout( authkey, now ) )
776 authkey = NULL; // timeout has apparently expired already
779 timeout_needs_resetting = 0;
787 @brief Implement the transaction.begin method.
788 @param ctx Pointer to the method context.
789 @return Zero if successful, or -1 upon error.
791 Start a transaction. Save a transaction ID for future reference.
794 - authkey (PCRUD only)
796 Return to client: Transaction ID
798 int beginTransaction( osrfMethodContext* ctx ) {
799 if(osrfMethodVerifyContext( ctx )) {
800 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
804 if( enforce_pcrud ) {
805 timeout_needs_resetting = 1;
806 const jsonObject* user = verifyUserPCRUD( ctx );
811 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
814 int errnum = dbi_conn_error( writehandle, &msg );
815 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
816 modulename, errnum, msg ? msg : "(No description available)" );
817 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
818 "osrfMethodException", ctx->request, "Error starting transaction" );
819 if( !oilsIsDBConnected( writehandle ))
820 osrfAppSessionPanic( ctx->session );
823 dbi_result_free( result );
825 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
826 osrfAppRespondComplete( ctx, ret );
827 jsonObjectFree( ret );
833 @brief Implement the savepoint.set method.
834 @param ctx Pointer to the method context.
835 @return Zero if successful, or -1 if not.
837 Issue a SAVEPOINT to the database server.
840 - authkey (PCRUD only)
843 Return to client: Savepoint name
845 int setSavepoint( osrfMethodContext* ctx ) {
846 if(osrfMethodVerifyContext( ctx )) {
847 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
852 if( enforce_pcrud ) {
854 timeout_needs_resetting = 1;
855 const jsonObject* user = verifyUserPCRUD( ctx );
860 // Verify that a transaction is pending
861 const char* trans_id = getXactId( ctx );
862 if( NULL == trans_id ) {
863 osrfAppSessionStatus(
865 OSRF_STATUS_INTERNALSERVERERROR,
866 "osrfMethodException",
868 "No active transaction -- required for savepoints"
873 // Get the savepoint name from the method params
874 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
876 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
879 int errnum = dbi_conn_error( writehandle, &msg );
882 "%s: Error creating savepoint %s in transaction %s: %d %s",
887 msg ? msg : "(No description available)"
889 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
890 "osrfMethodException", ctx->request, "Error creating savepoint" );
891 if( !oilsIsDBConnected( writehandle ))
892 osrfAppSessionPanic( ctx->session );
895 dbi_result_free( result );
896 jsonObject* ret = jsonNewObject( spName );
897 osrfAppRespondComplete( ctx, ret );
898 jsonObjectFree( ret );
904 @brief Implement the savepoint.release method.
905 @param ctx Pointer to the method context.
906 @return Zero if successful, or -1 if not.
908 Issue a RELEASE SAVEPOINT to the database server.
911 - authkey (PCRUD only)
914 Return to client: Savepoint name
916 int releaseSavepoint( osrfMethodContext* ctx ) {
917 if(osrfMethodVerifyContext( ctx )) {
918 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
923 if( enforce_pcrud ) {
925 timeout_needs_resetting = 1;
926 const jsonObject* user = verifyUserPCRUD( ctx );
931 // Verify that a transaction is pending
932 const char* trans_id = getXactId( ctx );
933 if( NULL == trans_id ) {
934 osrfAppSessionStatus(
936 OSRF_STATUS_INTERNALSERVERERROR,
937 "osrfMethodException",
939 "No active transaction -- required for savepoints"
944 // Get the savepoint name from the method params
945 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
947 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
950 int errnum = dbi_conn_error( writehandle, &msg );
953 "%s: Error releasing savepoint %s in transaction %s: %d %s",
958 msg ? msg : "(No description available)"
960 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
961 "osrfMethodException", ctx->request, "Error releasing savepoint" );
962 if( !oilsIsDBConnected( writehandle ))
963 osrfAppSessionPanic( ctx->session );
966 dbi_result_free( result );
967 jsonObject* ret = jsonNewObject( spName );
968 osrfAppRespondComplete( ctx, ret );
969 jsonObjectFree( ret );
975 @brief Implement the savepoint.rollback method.
976 @param ctx Pointer to the method context.
977 @return Zero if successful, or -1 if not.
979 Issue a ROLLBACK TO SAVEPOINT to the database server.
982 - authkey (PCRUD only)
985 Return to client: Savepoint name
987 int rollbackSavepoint( osrfMethodContext* ctx ) {
988 if(osrfMethodVerifyContext( ctx )) {
989 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
994 if( enforce_pcrud ) {
996 timeout_needs_resetting = 1;
997 const jsonObject* user = verifyUserPCRUD( ctx );
1002 // Verify that a transaction is pending
1003 const char* trans_id = getXactId( ctx );
1004 if( NULL == trans_id ) {
1005 osrfAppSessionStatus(
1007 OSRF_STATUS_INTERNALSERVERERROR,
1008 "osrfMethodException",
1010 "No active transaction -- required for savepoints"
1015 // Get the savepoint name from the method params
1016 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1018 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
1021 int errnum = dbi_conn_error( writehandle, &msg );
1024 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
1029 msg ? msg : "(No description available)"
1031 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1032 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1033 if( !oilsIsDBConnected( writehandle ))
1034 osrfAppSessionPanic( ctx->session );
1037 dbi_result_free( result );
1038 jsonObject* ret = jsonNewObject( spName );
1039 osrfAppRespondComplete( ctx, ret );
1040 jsonObjectFree( ret );
1046 @brief Implement the transaction.commit method.
1047 @param ctx Pointer to the method context.
1048 @return Zero if successful, or -1 if not.
1050 Issue a COMMIT to the database server.
1053 - authkey (PCRUD only)
1055 Return to client: Transaction ID.
1057 int commitTransaction( osrfMethodContext* ctx ) {
1058 if(osrfMethodVerifyContext( ctx )) {
1059 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1063 if( enforce_pcrud ) {
1064 timeout_needs_resetting = 1;
1065 const jsonObject* user = verifyUserPCRUD( ctx );
1070 // Verify that a transaction is pending
1071 const char* trans_id = getXactId( ctx );
1072 if( NULL == trans_id ) {
1073 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1074 "osrfMethodException", ctx->request, "No active transaction to commit" );
1078 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1081 int errnum = dbi_conn_error( writehandle, &msg );
1082 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1083 modulename, errnum, msg ? msg : "(No description available)" );
1084 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1085 "osrfMethodException", ctx->request, "Error committing transaction" );
1086 if( !oilsIsDBConnected( writehandle ))
1087 osrfAppSessionPanic( ctx->session );
1090 dbi_result_free( result );
1091 jsonObject* ret = jsonNewObject( trans_id );
1092 osrfAppRespondComplete( ctx, ret );
1093 jsonObjectFree( ret );
1100 @brief Implement the transaction.rollback method.
1101 @param ctx Pointer to the method context.
1102 @return Zero if successful, or -1 if not.
1104 Issue a ROLLBACK to the database server.
1107 - authkey (PCRUD only)
1109 Return to client: Transaction ID
1111 int rollbackTransaction( osrfMethodContext* ctx ) {
1112 if( osrfMethodVerifyContext( ctx )) {
1113 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1117 if( enforce_pcrud ) {
1118 timeout_needs_resetting = 1;
1119 const jsonObject* user = verifyUserPCRUD( ctx );
1124 // Verify that a transaction is pending
1125 const char* trans_id = getXactId( ctx );
1126 if( NULL == trans_id ) {
1127 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1128 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1132 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1135 int errnum = dbi_conn_error( writehandle, &msg );
1136 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1137 modulename, errnum, msg ? msg : "(No description available)" );
1138 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1139 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1140 if( !oilsIsDBConnected( writehandle ))
1141 osrfAppSessionPanic( ctx->session );
1144 dbi_result_free( result );
1145 jsonObject* ret = jsonNewObject( trans_id );
1146 osrfAppRespondComplete( ctx, ret );
1147 jsonObjectFree( ret );
1154 @brief Implement the "search" method.
1155 @param ctx Pointer to the method context.
1156 @return Zero if successful, or -1 if not.
1159 - authkey (PCRUD only)
1160 - WHERE clause, as jsonObject
1161 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1163 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1164 Optionally flesh linked fields.
1166 int doSearch( osrfMethodContext* ctx ) {
1167 if( osrfMethodVerifyContext( ctx )) {
1168 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1173 timeout_needs_resetting = 1;
1175 jsonObject* where_clause;
1176 jsonObject* rest_of_query;
1178 if( enforce_pcrud ) {
1179 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1180 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1182 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1183 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1186 // Get the class metadata
1187 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1188 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1192 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1194 osrfAppRespondComplete( ctx, NULL );
1198 // Return each row to the client (except that some may be suppressed by PCRUD)
1199 jsonObject* cur = 0;
1200 unsigned long res_idx = 0;
1201 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1202 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur, obj->size ))
1204 osrfAppRespond( ctx, cur );
1206 jsonObjectFree( obj );
1208 osrfAppRespondComplete( ctx, NULL );
1213 @brief Implement the "id_list" method.
1214 @param ctx Pointer to the method context.
1215 @param err Pointer through which to return an error code.
1216 @return Zero if successful, or -1 if not.
1219 - authkey (PCRUD only)
1220 - WHERE clause, as jsonObject
1221 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1223 Return to client: The primary key values for all rows of the relevant class that
1224 satisfy a specified WHERE clause.
1226 This method relies on the assumption that every class has a primary key consisting of
1229 int doIdList( osrfMethodContext* ctx ) {
1230 if( osrfMethodVerifyContext( ctx )) {
1231 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1236 timeout_needs_resetting = 1;
1238 jsonObject* where_clause;
1239 jsonObject* rest_of_query;
1241 // We use the where clause without change. But we need to massage the rest of the
1242 // query, so we work with a copy of it instead of modifying the original.
1244 if( enforce_pcrud ) {
1245 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1246 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1248 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1249 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1252 // Eliminate certain SQL clauses, if present.
1253 if( rest_of_query ) {
1254 jsonObjectRemoveKey( rest_of_query, "select" );
1255 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1256 jsonObjectRemoveKey( rest_of_query, "flesh" );
1257 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1259 rest_of_query = jsonNewObjectType( JSON_HASH );
1262 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1264 // Get the class metadata
1265 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1266 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1268 // Build a SELECT list containing just the primary key,
1269 // i.e. like { "classname":["keyname"] }
1270 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1272 // Load array with name of primary key
1273 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1274 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1275 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1277 jsonObjectSetKey( rest_of_query, "select", select_clause );
1282 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1284 jsonObjectFree( rest_of_query );
1286 osrfAppRespondComplete( ctx, NULL );
1290 // Return each primary key value to the client
1292 unsigned long res_idx = 0;
1293 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1294 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur, obj->size ))
1295 continue; // Suppress due to lack of permission
1297 osrfAppRespond( ctx,
1298 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1301 jsonObjectFree( obj );
1302 osrfAppRespondComplete( ctx, NULL );
1307 @brief Verify that we have a valid class reference.
1308 @param ctx Pointer to the method context.
1309 @param param Pointer to the method parameters.
1310 @return 1 if the class reference is valid, or zero if it isn't.
1312 The class of the method params must match the class to which the method id devoted.
1313 For PCRUD there are additional restrictions.
1315 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1317 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1318 osrfHash* class = osrfHashGet( method_meta, "class" );
1320 // Compare the method's class to the parameters' class
1321 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1323 // Oops -- they don't match. Complain.
1324 growing_buffer* msg = buffer_init( 128 );
1327 "%s: %s method for type %s was passed a %s",
1329 osrfHashGet( method_meta, "methodtype" ),
1330 osrfHashGet( class, "classname" ),
1331 param->classname ? param->classname : "(null)"
1334 char* m = buffer_release( msg );
1335 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1343 return verifyObjectPCRUD( ctx, param, 1 );
1349 @brief (PCRUD only) Verify that the user is properly logged in.
1350 @param ctx Pointer to the method context.
1351 @return If the user is logged in, a pointer to the user object from the authentication
1352 server; otherwise NULL.
1354 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1356 // Get the authkey (the first method parameter)
1357 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1359 // See if we have the same authkey, and a user object,
1360 // locally cached from a previous call
1361 const char* cached_authkey = getAuthkey( ctx );
1362 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1363 const jsonObject* cached_user = getUserLogin( ctx );
1368 // We have no matching authentication data in the cache. Authenticate from scratch.
1369 jsonObject* auth_object = jsonNewObject( auth );
1371 // Fetch the user object from the authentication server
1372 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1374 jsonObjectFree( auth_object );
1376 if( !user->classname || strcmp(user->classname, "au" )) {
1378 growing_buffer* msg = buffer_init( 128 );
1381 "%s: permacrud received a bad auth token: %s",
1386 char* m = buffer_release( msg );
1387 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1391 jsonObjectFree( user );
1395 setUserLogin( ctx, user );
1396 setAuthkey( ctx, auth );
1398 // Allow ourselves up to a second before we have to reset the login timeout.
1399 // It would be nice to use some fraction of the timeout interval enforced by the
1400 // authentication server, but that value is not readily available at this point.
1401 // Instead, we use a conservative default interval.
1402 time_next_reset = time( NULL ) + 1;
1408 @brief For PCRUD: Determine whether the current user may access the current row.
1409 @param ctx Pointer to the method context.
1410 @param obj Pointer to the row being potentially accessed.
1411 @return 1 if access is permitted, or 0 if it isn't.
1413 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1415 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj, const int rs_size ) {
1417 dbhandle = writehandle;
1419 // Figure out what class and method are involved
1420 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1421 osrfHash* class = osrfHashGet( method_metadata, "class" );
1422 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1424 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1425 // contexts we will do another lookup of the current row, even if we already have a
1426 // previously fetched row image, because the row image in hand may not include the
1427 // foreign key(s) that we need.
1429 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1430 // but they aren't implemented yet.
1433 if( *method_type == 's' || *method_type == 'i' ) {
1434 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1436 } else if( *method_type == 'u' || *method_type == 'd' ) {
1437 fetch = 1; // MUST go to the db for the object for update and delete
1440 // Get the appropriate permacrud entry from the IDL, depending on method type
1441 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1443 // No permacrud for this method type on this class
1445 growing_buffer* msg = buffer_init( 128 );
1448 "%s: %s on class %s has no permacrud IDL entry",
1450 osrfHashGet( method_metadata, "methodtype" ),
1451 osrfHashGet( class, "classname" )
1454 char* m = buffer_release( msg );
1455 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1456 "osrfMethodException", ctx->request, m );
1463 // Get the user id, and make sure the user is logged in
1464 const jsonObject* user = verifyUserPCRUD( ctx );
1466 return 0; // Not logged in? No access.
1468 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1470 // Get a list of permissions from the permacrud entry.
1471 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1472 if( permission->size == 0 ) {
1473 osrfLogDebug( OSRF_LOG_MARK, "No permissions required for this action, passing through" );
1477 // Build a list of org units that own the row. This is fairly convoluted because there
1478 // are several different ways that an org unit may own the row, as defined by the
1481 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1482 // identifying an owning org_unit..
1483 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1485 // Foreign context adds a layer of indirection. The row points to some other row that
1486 // an org unit may own. The "jump" attribute, if present, adds another layer of
1488 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1490 // The following string array stores the list of org units. (We don't have a thingie
1491 // for storing lists of integers, so we fake it with a list of strings.)
1492 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1495 const char* pkey_value = NULL;
1496 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1497 // If the global_required attribute is present and true, then the only owning
1498 // org unit is the root org unit, i.e. the one with no parent.
1499 osrfLogDebug( OSRF_LOG_MARK,
1500 "global-level permissions required, fetching top of the org tree" );
1502 // check for perm at top of org tree
1503 const char* org_tree_root_id = org_tree_root( ctx );
1504 if( org_tree_root_id ) {
1505 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1506 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1508 osrfStringArrayFree( context_org_array );
1513 // If the global_required attribute is absent or false, then we look for
1514 // local and/or foreign context. In order to find the relevant foreign
1515 // keys, we must either read the relevant row from the database, or look at
1516 // the image of the row that we already have in memory.
1518 // Even if we have an image of the row in memory, that image may not include the
1519 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1520 // of the row to make sure that we have what we need.
1522 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1523 "fetching context org ids" );
1524 const char* pkey = osrfHashGet( class, "primarykey" );
1525 jsonObject *param = NULL;
1528 // There is no primary key, so we can't do a fresh lookup. Use the row
1529 // image that we already have. If it doesn't have everything we need, too bad.
1531 param = jsonObjectClone( obj );
1532 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1533 } else if( obj->classname ) {
1534 pkey_value = oilsFMGetStringConst( obj, pkey );
1536 param = jsonObjectClone( obj );
1537 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1540 pkey_value = jsonObjectGetString( obj );
1542 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1543 "of %s and retrieving from the database", pkey_value );
1547 // Fetch the row so that we can look at the foreign key(s)
1548 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1549 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1550 jsonObjectFree( _tmp_params );
1552 param = jsonObjectExtractIndex( _list, 0 );
1553 jsonObjectFree( _list );
1557 // The row doesn't exist. Complain, and deny access.
1558 osrfLogDebug( OSRF_LOG_MARK,
1559 "Object not found in the database with primary key %s of %s",
1562 growing_buffer* msg = buffer_init( 128 );
1565 "%s: no object found with primary key %s of %s",
1571 char* m = buffer_release( msg );
1572 osrfAppSessionStatus(
1574 OSRF_STATUS_INTERNALSERVERERROR,
1575 "osrfMethodException",
1584 if( local_context && local_context->size > 0 ) {
1585 // The IDL provides a list of column names for the foreign keys denoting
1586 // local context, i.e. columns identifying owing org units directly. Look up
1587 // the value of each one, and if it isn't null, add it to the list of org units.
1588 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1589 local_context->size );
1591 const char* lcontext = NULL;
1592 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1593 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1594 if( fkey_value ) { // if not null
1595 osrfStringArrayAdd( context_org_array, fkey_value );
1598 "adding class-local field %s (value: %s) to the context org list",
1600 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1606 if( foreign_context ) {
1607 unsigned long class_count = osrfHashGetCount( foreign_context );
1608 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1610 if( class_count > 0 ) {
1612 // The IDL provides a list of foreign key columns pointing to rows that
1613 // an org unit may own. Follow each link, identify the owning org unit,
1614 // and add it to the list.
1615 osrfHash* fcontext = NULL;
1616 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1617 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1618 // For each class to which a foreign key points:
1619 const char* class_name = osrfHashIteratorKey( class_itr );
1620 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1624 "%d foreign context fields(s) specified for class %s",
1625 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1629 // Get the name of the key field in the foreign table
1630 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1632 // Get the value of the foreign key pointing to the foreign table
1633 char* foreign_pkey_value =
1634 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1635 if( !foreign_pkey_value )
1636 continue; // Foreign key value is null; skip it
1638 // Look up the row to which the foreign key points
1639 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1640 jsonObject* _list = doFieldmapperSearch(
1641 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1643 jsonObject* _fparam = NULL;
1644 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1645 _fparam = jsonObjectExtractIndex( _list, 0 );
1647 jsonObjectFree( _tmp_params );
1648 jsonObjectFree( _list );
1650 // At this point _fparam either points to the row identified by the
1651 // foreign key, or it's NULL (no such row found).
1653 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1655 const char* bad_class = NULL; // For noting failed lookups
1657 bad_class = class_name; // Referenced row not found
1658 else if( jump_list ) {
1659 // Follow a chain of rows, linked by foreign keys, to find an owner
1660 const char* flink = NULL;
1662 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1663 // For each entry in the jump list. Each entry (i.e. flink) is
1664 // the name of a foreign key column in the current row.
1666 // From the IDL, get the linkage information for the next jump
1667 osrfHash* foreign_link_hash =
1668 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1670 // Get the class metadata for the class
1671 // to which the foreign key points
1672 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1673 osrfHashGet( foreign_link_hash, "class" ));
1675 // Get the name of the referenced key of that class
1676 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1678 // Get the value of the foreign key pointing to that class
1679 free( foreign_pkey_value );
1680 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1681 if( !foreign_pkey_value )
1682 break; // Foreign key is null; quit looking
1684 // Build a WHERE clause for the lookup
1685 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1688 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1689 _tmp_params, NULL, &err );
1691 // Get the resulting row
1692 jsonObjectFree( _fparam );
1693 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1694 _fparam = jsonObjectExtractIndex( _list, 0 );
1696 // Referenced row not found
1698 bad_class = osrfHashGet( foreign_link_hash, "class" );
1701 jsonObjectFree( _tmp_params );
1702 jsonObjectFree( _list );
1708 // We had a foreign key pointing to such-and-such a row, but then
1709 // we couldn't fetch that row. The data in the database are in an
1710 // inconsistent state; the database itself may even be corrupted.
1711 growing_buffer* msg = buffer_init( 128 );
1714 "%s: no object of class %s found with primary key %s of %s",
1718 foreign_pkey_value ? foreign_pkey_value : "(null)"
1721 char* m = buffer_release( msg );
1722 osrfAppSessionStatus(
1724 OSRF_STATUS_INTERNALSERVERERROR,
1725 "osrfMethodException",
1731 osrfHashIteratorFree( class_itr );
1732 free( foreign_pkey_value );
1733 jsonObjectFree( param );
1738 free( foreign_pkey_value );
1741 // Examine each context column of the foreign row,
1742 // and add its value to the list of org units.
1744 const char* foreign_field = NULL;
1745 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1746 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1747 osrfStringArrayAdd( context_org_array,
1748 oilsFMGetStringConst( _fparam, foreign_field ));
1749 osrfLogDebug( OSRF_LOG_MARK,
1750 "adding foreign class %s field %s (value: %s) "
1751 "to the context org list",
1754 osrfStringArrayGetString(
1755 context_org_array, context_org_array->size - 1 )
1759 jsonObjectFree( _fparam );
1763 osrfHashIteratorFree( class_itr );
1767 jsonObjectFree( param );
1770 const char* context_org = NULL;
1771 const char* perm = NULL;
1774 // For every combination of permission and context org unit: call a stored procedure
1775 // to determine if the user has this permission in the context of this org unit.
1776 // If the answer is yes at any point, then we're done, and the user has permission.
1777 // In other words permissions are additive.
1779 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1782 osrfStringArray* pcache = NULL;
1783 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
1784 pcache = getPermLocationCache(ctx, perm);
1787 pcache = osrfNewStringArray(0);
1789 result = dbi_conn_queryf(
1791 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
1799 "Received a result for permission [%s] for user %d",
1804 if( dbi_result_first_row( result )) {
1806 jsonObject* return_val = oilsMakeJSONFromResult( result );
1807 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
1808 jsonObjectFree( return_val );
1809 } while( dbi_result_next_row( result ));
1811 setPermLocationCache(ctx, perm, pcache);
1814 dbi_result_free( result );
1820 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1822 if (rs_size > perm_at_threshold) {
1823 if (osrfStringArrayContains( pcache, context_org )) {
1832 "Checking object permission [%s] for user %d "
1833 "on object %s (class %s) at org %d",
1837 osrfHashGet( class, "classname" ),
1841 result = dbi_conn_queryf(
1843 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1846 osrfHashGet( class, "classname" ),
1854 "Received a result for object permission [%s] "
1855 "for user %d on object %s (class %s) at org %d",
1859 osrfHashGet( class, "classname" ),
1863 if( dbi_result_first_row( result )) {
1864 jsonObject* return_val = oilsMakeJSONFromResult( result );
1865 const char* has_perm = jsonObjectGetString(
1866 jsonObjectGetKeyConst( return_val, "has_perm" ));
1870 "Status of object permission [%s] for user %d "
1871 "on object %s (class %s) at org %d is %s",
1875 osrfHashGet(class, "classname"),
1880 if( *has_perm == 't' )
1882 jsonObjectFree( return_val );
1885 dbi_result_free( result );
1890 int errnum = dbi_conn_error( writehandle, &msg );
1891 osrfLogWarning( OSRF_LOG_MARK,
1892 "Unable to call check object permissions: %d, %s",
1893 errnum, msg ? msg : "(No description available)" );
1894 if( !oilsIsDBConnected( writehandle ))
1895 osrfAppSessionPanic( ctx->session );
1899 if (rs_size > perm_at_threshold) break;
1901 osrfLogDebug( OSRF_LOG_MARK,
1902 "Checking non-object permission [%s] for user %d at org %d",
1903 perm, userid, atoi(context_org) );
1904 result = dbi_conn_queryf(
1906 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1913 osrfLogDebug( OSRF_LOG_MARK,
1914 "Received a result for permission [%s] for user %d at org %d",
1915 perm, userid, atoi( context_org ));
1916 if( dbi_result_first_row( result )) {
1917 jsonObject* return_val = oilsMakeJSONFromResult( result );
1918 const char* has_perm = jsonObjectGetString(
1919 jsonObjectGetKeyConst( return_val, "has_perm" ));
1920 osrfLogDebug( OSRF_LOG_MARK,
1921 "Status of permission [%s] for user %d at org %d is [%s]",
1922 perm, userid, atoi( context_org ), has_perm );
1923 if( *has_perm == 't' )
1925 jsonObjectFree( return_val );
1928 dbi_result_free( result );
1933 int errnum = dbi_conn_error( writehandle, &msg );
1934 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
1935 errnum, msg ? msg : "(No description available)" );
1936 if( !oilsIsDBConnected( writehandle ))
1937 osrfAppSessionPanic( ctx->session );
1946 osrfStringArrayFree( context_org_array );
1952 @brief Look up the root of the org_unit tree.
1953 @param ctx Pointer to the method context.
1954 @return The id of the root org unit, as a character string.
1956 Query actor.org_unit where parent_ou is null, and return the id as a string.
1958 This function assumes that there is only one root org unit, i.e. that we
1959 have a single tree, not a forest.
1961 The calling code is responsible for freeing the returned string.
1963 static const char* org_tree_root( osrfMethodContext* ctx ) {
1965 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1966 static time_t last_lookup_time = 0;
1967 time_t current_time = time( NULL );
1969 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1970 // We successfully looked this up less than an hour ago.
1971 // It's not likely to have changed since then.
1972 return strdup( cached_root_id );
1974 last_lookup_time = current_time;
1977 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1978 jsonObject* result = doFieldmapperSearch(
1979 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1980 jsonObjectFree( where_clause );
1982 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1985 jsonObjectFree( result );
1987 growing_buffer* msg = buffer_init( 128 );
1988 OSRF_BUFFER_ADD( msg, modulename );
1989 OSRF_BUFFER_ADD( msg,
1990 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1992 char* m = buffer_release( msg );
1993 osrfAppSessionStatus( ctx->session,
1994 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1997 cached_root_id[ 0 ] = '\0';
2001 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2002 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2004 strcpy( cached_root_id, root_org_unit_id );
2005 jsonObjectFree( result );
2006 return cached_root_id;
2010 @brief Create a JSON_HASH with a single key/value pair.
2011 @param key The key of the key/value pair.
2012 @param value the value of the key/value pair.
2013 @return Pointer to a newly created jsonObject of type JSON_HASH.
2015 The value of the key/value is either a string or (if @a value is NULL) a null.
2017 static jsonObject* single_hash( const char* key, const char* value ) {
2019 if( ! key ) key = "";
2021 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2022 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2027 int doCreate( osrfMethodContext* ctx ) {
2028 if(osrfMethodVerifyContext( ctx )) {
2029 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2034 timeout_needs_resetting = 1;
2036 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2037 jsonObject* target = NULL;
2038 jsonObject* options = NULL;
2040 if( enforce_pcrud ) {
2041 target = jsonObjectGetIndex( ctx->params, 1 );
2042 options = jsonObjectGetIndex( ctx->params, 2 );
2044 target = jsonObjectGetIndex( ctx->params, 0 );
2045 options = jsonObjectGetIndex( ctx->params, 1 );
2048 if( !verifyObjectClass( ctx, target )) {
2049 osrfAppRespondComplete( ctx, NULL );
2053 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2055 const char* trans_id = getXactId( ctx );
2057 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2059 osrfAppSessionStatus(
2061 OSRF_STATUS_BADREQUEST,
2062 "osrfMethodException",
2064 "No active transaction -- required for CREATE"
2066 osrfAppRespondComplete( ctx, NULL );
2070 // The following test is harmless but redundant. If a class is
2071 // readonly, we don't register a create method for it.
2072 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2073 osrfAppSessionStatus(
2075 OSRF_STATUS_BADREQUEST,
2076 "osrfMethodException",
2078 "Cannot INSERT readonly class"
2080 osrfAppRespondComplete( ctx, NULL );
2084 // Set the last_xact_id
2085 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2087 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2088 trans_id, target->classname, index);
2089 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2092 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2094 dbhandle = writehandle;
2096 osrfHash* fields = osrfHashGet( meta, "fields" );
2097 char* pkey = osrfHashGet( meta, "primarykey" );
2098 char* seq = osrfHashGet( meta, "sequence" );
2100 growing_buffer* table_buf = buffer_init( 128 );
2101 growing_buffer* col_buf = buffer_init( 128 );
2102 growing_buffer* val_buf = buffer_init( 128 );
2104 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2105 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2106 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2107 buffer_add( val_buf,"VALUES (" );
2111 osrfHash* field = NULL;
2112 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2113 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2115 const char* field_name = osrfHashIteratorKey( field_itr );
2117 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2120 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2123 if( field_object && field_object->classname ) {
2124 value = oilsFMGetString(
2126 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2128 } else if( field_object && JSON_BOOL == field_object->type ) {
2129 if( jsonBoolIsTrue( field_object ) )
2130 value = strdup( "t" );
2132 value = strdup( "f" );
2134 value = jsonObjectToSimpleString( field_object );
2140 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2141 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2144 buffer_add( col_buf, field_name );
2146 if( !field_object || field_object->type == JSON_NULL ) {
2147 buffer_add( val_buf, "DEFAULT" );
2149 } else if( !strcmp( get_primitive( field ), "number" )) {
2150 const char* numtype = get_datatype( field );
2151 if( !strcmp( numtype, "INT8" )) {
2152 buffer_fadd( val_buf, "%lld", atoll( value ));
2154 } else if( !strcmp( numtype, "INT" )) {
2155 buffer_fadd( val_buf, "%d", atoi( value ));
2157 } else if( !strcmp( numtype, "NUMERIC" )) {
2158 buffer_fadd( val_buf, "%f", atof( value ));
2161 if( dbi_conn_quote_string( writehandle, &value )) {
2162 OSRF_BUFFER_ADD( val_buf, value );
2165 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2166 osrfAppSessionStatus(
2168 OSRF_STATUS_INTERNALSERVERERROR,
2169 "osrfMethodException",
2171 "Error quoting string -- please see the error log for more details"
2174 buffer_free( table_buf );
2175 buffer_free( col_buf );
2176 buffer_free( val_buf );
2177 osrfAppRespondComplete( ctx, NULL );
2185 osrfHashIteratorFree( field_itr );
2187 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2188 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2190 char* table_str = buffer_release( table_buf );
2191 char* col_str = buffer_release( col_buf );
2192 char* val_str = buffer_release( val_buf );
2193 growing_buffer* sql = buffer_init( 128 );
2194 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2199 char* query = buffer_release( sql );
2201 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2203 jsonObject* obj = NULL;
2206 dbi_result result = dbi_conn_query( writehandle, query );
2208 obj = jsonNewObject( NULL );
2210 int errnum = dbi_conn_error( writehandle, &msg );
2213 "%s ERROR inserting %s object using query [%s]: %d %s",
2215 osrfHashGet(meta, "fieldmapper"),
2218 msg ? msg : "(No description available)"
2220 osrfAppSessionStatus(
2222 OSRF_STATUS_INTERNALSERVERERROR,
2223 "osrfMethodException",
2225 "INSERT error -- please see the error log for more details"
2227 if( !oilsIsDBConnected( writehandle ))
2228 osrfAppSessionPanic( ctx->session );
2231 dbi_result_free( result );
2233 char* id = oilsFMGetString( target, pkey );
2235 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2236 growing_buffer* _id = buffer_init( 10 );
2237 buffer_fadd( _id, "%lld", new_id );
2238 id = buffer_release( _id );
2241 // Find quietness specification, if present
2242 const char* quiet_str = NULL;
2244 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2246 quiet_str = jsonObjectGetString( quiet_obj );
2249 if( str_is_true( quiet_str )) { // if quietness is specified
2250 obj = jsonNewObject( id );
2254 // Fetch the row that we just inserted, so that we can return it to the client
2255 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2256 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2259 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2263 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2265 jsonObjectFree( list );
2266 jsonObjectFree( where_clause );
2273 osrfAppRespondComplete( ctx, obj );
2274 jsonObjectFree( obj );
2279 @brief Implement the retrieve method.
2280 @param ctx Pointer to the method context.
2281 @param err Pointer through which to return an error code.
2282 @return If successful, a pointer to the result to be returned to the client;
2285 From the method's class, fetch a row with a specified value in the primary key. This
2286 method relies on the database design convention that a primary key consists of a single
2290 - authkey (PCRUD only)
2291 - value of the primary key for the desired row, for building the WHERE clause
2292 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2294 Return to client: One row from the query.
2296 int doRetrieve( osrfMethodContext* ctx ) {
2297 if(osrfMethodVerifyContext( ctx )) {
2298 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2303 timeout_needs_resetting = 1;
2308 if( enforce_pcrud ) {
2313 // Get the class metadata
2314 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2316 // Get the value of the primary key, from a method parameter
2317 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2321 "%s retrieving %s object with primary key value of %s",
2323 osrfHashGet( class_def, "fieldmapper" ),
2324 jsonObjectGetString( id_obj )
2327 // Build a WHERE clause based on the key value
2328 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2331 osrfHashGet( class_def, "primarykey" ), // name of key column
2332 jsonObjectClone( id_obj ) // value of key column
2335 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2339 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2341 jsonObjectFree( where_clause );
2343 osrfAppRespondComplete( ctx, NULL );
2347 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2348 jsonObjectFree( list );
2350 if( enforce_pcrud ) {
2351 if(!verifyObjectPCRUD( ctx, obj, 1 )) {
2352 jsonObjectFree( obj );
2354 growing_buffer* msg = buffer_init( 128 );
2355 OSRF_BUFFER_ADD( msg, modulename );
2356 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2358 char* m = buffer_release( msg );
2359 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2363 osrfAppRespondComplete( ctx, NULL );
2368 osrfAppRespondComplete( ctx, obj );
2369 jsonObjectFree( obj );
2374 @brief Translate a numeric value to a string representation for the database.
2375 @param field Pointer to the IDL field definition.
2376 @param value Pointer to a jsonObject holding the value of a field.
2377 @return Pointer to a newly allocated string.
2379 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2380 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2381 or (what is worse) valid SQL that is wrong.
2383 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2385 The calling code is responsible for freeing the resulting string by calling free().
2387 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2388 growing_buffer* val_buf = buffer_init( 32 );
2389 const char* numtype = get_datatype( field );
2391 // For historical reasons the following contains cruft that could be cleaned up.
2392 if( !strncmp( numtype, "INT", 3 ) ) {
2393 if( value->type == JSON_NUMBER )
2394 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2395 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2397 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2400 } else if( !strcmp( numtype, "NUMERIC" )) {
2401 if( value->type == JSON_NUMBER )
2402 buffer_fadd( val_buf, jsonObjectGetString( value ));
2404 buffer_fadd( val_buf, jsonObjectGetString( value ));
2408 // Presumably this was really intended to be a string, so quote it
2409 char* str = jsonObjectToSimpleString( value );
2410 if( dbi_conn_quote_string( dbhandle, &str )) {
2411 OSRF_BUFFER_ADD( val_buf, str );
2414 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2416 buffer_free( val_buf );
2421 return buffer_release( val_buf );
2424 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2425 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2426 growing_buffer* sql_buf = buffer_init( 32 );
2432 osrfHashGet( field, "name" )
2436 buffer_add( sql_buf, "IN (" );
2437 } else if( !strcasecmp( op,"not in" )) {
2438 buffer_add( sql_buf, "NOT IN (" );
2440 buffer_add( sql_buf, "IN (" );
2443 if( node->type == JSON_HASH ) {
2444 // subquery predicate
2445 char* subpred = buildQuery( ctx, node, SUBSELECT );
2447 buffer_free( sql_buf );
2451 buffer_add( sql_buf, subpred );
2454 } else if( node->type == JSON_ARRAY ) {
2455 // literal value list
2456 int in_item_index = 0;
2457 int in_item_first = 1;
2458 const jsonObject* in_item;
2459 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2464 buffer_add( sql_buf, ", " );
2467 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2468 osrfLogError( OSRF_LOG_MARK,
2469 "%s: Expected string or number within IN list; found %s",
2470 modulename, json_type( in_item->type ) );
2471 buffer_free( sql_buf );
2475 // Append the literal value -- quoted if not a number
2476 if( JSON_NUMBER == in_item->type ) {
2477 char* val = jsonNumberToDBString( field, in_item );
2478 OSRF_BUFFER_ADD( sql_buf, val );
2481 } else if( !strcmp( get_primitive( field ), "number" )) {
2482 char* val = jsonNumberToDBString( field, in_item );
2483 OSRF_BUFFER_ADD( sql_buf, val );
2487 char* key_string = jsonObjectToSimpleString( in_item );
2488 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2489 OSRF_BUFFER_ADD( sql_buf, key_string );
2492 osrfLogError( OSRF_LOG_MARK,
2493 "%s: Error quoting key string [%s]", modulename, key_string );
2495 buffer_free( sql_buf );
2501 if( in_item_first ) {
2502 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2503 buffer_free( sql_buf );
2507 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2508 modulename, json_type( node->type ));
2509 buffer_free( sql_buf );
2513 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2515 return buffer_release( sql_buf );
2518 // Receive a JSON_ARRAY representing a function call. The first
2519 // entry in the array is the function name. The rest are parameters.
2520 static char* searchValueTransform( const jsonObject* array ) {
2522 if( array->size < 1 ) {
2523 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2527 // Get the function name
2528 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2529 if( func_item->type != JSON_STRING ) {
2530 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2531 modulename, json_type( func_item->type ));
2535 growing_buffer* sql_buf = buffer_init( 32 );
2537 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2538 OSRF_BUFFER_ADD( sql_buf, "( " );
2540 // Get the parameters
2541 int func_item_index = 1; // We already grabbed the zeroth entry
2542 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2544 // Add a separator comma, if we need one
2545 if( func_item_index > 2 )
2546 buffer_add( sql_buf, ", " );
2548 // Add the current parameter
2549 if( func_item->type == JSON_NULL ) {
2550 buffer_add( sql_buf, "NULL" );
2552 char* val = jsonObjectToSimpleString( func_item );
2553 if( dbi_conn_quote_string( dbhandle, &val )) {
2554 OSRF_BUFFER_ADD( sql_buf, val );
2557 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2559 buffer_free( sql_buf );
2566 buffer_add( sql_buf, " )" );
2568 return buffer_release( sql_buf );
2571 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2572 const jsonObject* node, const char* op ) {
2574 if( ! is_good_operator( op ) ) {
2575 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2579 char* val = searchValueTransform( node );
2583 growing_buffer* sql_buf = buffer_init( 32 );
2588 osrfHashGet( field, "name" ),
2595 return buffer_release( sql_buf );
2598 // class_alias is a class name or other table alias
2599 // field is a field definition as stored in the IDL
2600 // node comes from the method parameter, and may represent an entry in the SELECT list
2601 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2602 const jsonObject* node ) {
2603 growing_buffer* sql_buf = buffer_init( 32 );
2605 const char* field_transform = jsonObjectGetString(
2606 jsonObjectGetKeyConst( node, "transform" ) );
2607 const char* transform_subcolumn = jsonObjectGetString(
2608 jsonObjectGetKeyConst( node, "result_field" ) );
2610 if( transform_subcolumn ) {
2611 if( ! is_identifier( transform_subcolumn ) ) {
2612 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2613 modulename, transform_subcolumn );
2614 buffer_free( sql_buf );
2617 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2620 if( field_transform ) {
2622 if( ! is_identifier( field_transform ) ) {
2623 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2624 modulename, field_transform );
2625 buffer_free( sql_buf );
2629 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2630 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2631 field_transform, class_alias, osrfHashGet( field, "name" ));
2633 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2634 field_transform, class_alias, osrfHashGet( field, "name" ));
2637 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2640 if( array->type != JSON_ARRAY ) {
2641 osrfLogError( OSRF_LOG_MARK,
2642 "%s: Expected JSON_ARRAY for function params; found %s",
2643 modulename, json_type( array->type ) );
2644 buffer_free( sql_buf );
2647 int func_item_index = 0;
2648 jsonObject* func_item;
2649 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2651 char* val = jsonObjectToSimpleString( func_item );
2654 buffer_add( sql_buf, ",NULL" );
2655 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2656 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2657 OSRF_BUFFER_ADD( sql_buf, val );
2659 osrfLogError( OSRF_LOG_MARK,
2660 "%s: Error quoting key string [%s]", modulename, val );
2662 buffer_free( sql_buf );
2669 buffer_add( sql_buf, " )" );
2672 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2675 if( transform_subcolumn )
2676 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2678 return buffer_release( sql_buf );
2681 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2682 const jsonObject* node, const char* op ) {
2684 if( ! is_good_operator( op ) ) {
2685 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2689 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2690 if( ! field_transform )
2693 int extra_parens = 0; // boolean
2695 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2697 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2699 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2701 free( field_transform );
2705 } else if( value_obj->type == JSON_ARRAY ) {
2706 value = searchValueTransform( value_obj );
2708 osrfLogError( OSRF_LOG_MARK,
2709 "%s: Error building value transform for field transform", modulename );
2710 free( field_transform );
2713 } else if( value_obj->type == JSON_HASH ) {
2714 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2716 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2718 free( field_transform );
2722 } else if( value_obj->type == JSON_NUMBER ) {
2723 value = jsonNumberToDBString( field, value_obj );
2724 } else if( value_obj->type == JSON_NULL ) {
2725 osrfLogError( OSRF_LOG_MARK,
2726 "%s: Error building predicate for field transform: null value", modulename );
2727 free( field_transform );
2729 } else if( value_obj->type == JSON_BOOL ) {
2730 osrfLogError( OSRF_LOG_MARK,
2731 "%s: Error building predicate for field transform: boolean value", modulename );
2732 free( field_transform );
2735 if( !strcmp( get_primitive( field ), "number") ) {
2736 value = jsonNumberToDBString( field, value_obj );
2738 value = jsonObjectToSimpleString( value_obj );
2739 if( !dbi_conn_quote_string( dbhandle, &value )) {
2740 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2741 modulename, value );
2743 free( field_transform );
2749 const char* left_parens = "";
2750 const char* right_parens = "";
2752 if( extra_parens ) {
2757 growing_buffer* sql_buf = buffer_init( 32 );
2761 "%s%s %s %s %s %s%s",
2772 free( field_transform );
2774 return buffer_release( sql_buf );
2777 static char* searchSimplePredicate( const char* op, const char* class_alias,
2778 osrfHash* field, const jsonObject* node ) {
2780 if( ! is_good_operator( op ) ) {
2781 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2787 // Get the value to which we are comparing the specified column
2788 if( node->type != JSON_NULL ) {
2789 if( node->type == JSON_NUMBER ) {
2790 val = jsonNumberToDBString( field, node );
2791 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2792 val = jsonNumberToDBString( field, node );
2794 val = jsonObjectToSimpleString( node );
2799 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2800 // Value is not numeric; enclose it in quotes
2801 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2802 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2809 // Compare to a null value
2810 val = strdup( "NULL" );
2811 if( strcmp( op, "=" ))
2817 growing_buffer* sql_buf = buffer_init( 32 );
2818 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2819 char* pred = buffer_release( sql_buf );
2826 static char* searchBETWEENPredicate( const char* class_alias,
2827 osrfHash* field, const jsonObject* node ) {
2829 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2830 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2832 if( NULL == y_node ) {
2833 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2836 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2837 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2844 if( !strcmp( get_primitive( field ), "number") ) {
2845 x_string = jsonNumberToDBString( field, x_node );
2846 y_string = jsonNumberToDBString( field, y_node );
2849 x_string = jsonObjectToSimpleString( x_node );
2850 y_string = jsonObjectToSimpleString( y_node );
2851 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2852 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2853 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2854 modulename, x_string, y_string );
2861 growing_buffer* sql_buf = buffer_init( 32 );
2862 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2863 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2867 return buffer_release( sql_buf );
2870 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2871 jsonObject* node, osrfMethodContext* ctx ) {
2874 if( node->type == JSON_ARRAY ) { // equality IN search
2875 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2876 } else if( node->type == JSON_HASH ) { // other search
2877 jsonIterator* pred_itr = jsonNewIterator( node );
2878 if( !jsonIteratorHasNext( pred_itr ) ) {
2879 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2880 modulename, osrfHashGet(field, "name" ));
2882 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2884 // Verify that there are no additional predicates
2885 if( jsonIteratorHasNext( pred_itr ) ) {
2886 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2887 modulename, osrfHashGet(field, "name" ));
2888 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2889 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2890 else if( !(strcasecmp( pred_itr->key,"in" ))
2891 || !(strcasecmp( pred_itr->key,"not in" )) )
2892 pred = searchINPredicate(
2893 class_info->alias, field, pred_node, pred_itr->key, ctx );
2894 else if( pred_node->type == JSON_ARRAY )
2895 pred = searchFunctionPredicate(
2896 class_info->alias, field, pred_node, pred_itr->key );
2897 else if( pred_node->type == JSON_HASH )
2898 pred = searchFieldTransformPredicate(
2899 class_info, field, pred_node, pred_itr->key );
2901 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2903 jsonIteratorFree( pred_itr );
2905 } else if( node->type == JSON_NULL ) { // IS NULL search
2906 growing_buffer* _p = buffer_init( 64 );
2909 "\"%s\".%s IS NULL",
2910 class_info->class_name,
2911 osrfHashGet( field, "name" )
2913 pred = buffer_release( _p );
2914 } else { // equality search
2915 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2934 field : call_number,
2950 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2952 const jsonObject* working_hash;
2953 jsonObject* freeable_hash = NULL;
2955 if( join_hash->type == JSON_HASH ) {
2956 working_hash = join_hash;
2957 } else if( join_hash->type == JSON_STRING ) {
2958 // turn it into a JSON_HASH by creating a wrapper
2959 // around a copy of the original
2960 const char* _tmp = jsonObjectGetString( join_hash );
2961 freeable_hash = jsonNewObjectType( JSON_HASH );
2962 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2963 working_hash = freeable_hash;
2967 "%s: JOIN failed; expected JSON object type not found",
2973 growing_buffer* join_buf = buffer_init( 128 );
2974 const char* leftclass = left_info->class_name;
2976 jsonObject* snode = NULL;
2977 jsonIterator* search_itr = jsonNewIterator( working_hash );
2979 while ( (snode = jsonIteratorNext( search_itr )) ) {
2980 const char* right_alias = search_itr->key;
2982 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2984 class = right_alias;
2986 const ClassInfo* right_info = add_joined_class( right_alias, class );
2990 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2994 jsonIteratorFree( search_itr );
2995 buffer_free( join_buf );
2997 jsonObjectFree( freeable_hash );
3000 osrfHash* links = right_info->links;
3001 const char* table = right_info->source_def;
3003 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3004 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3006 if( field && !fkey ) {
3007 // Look up the corresponding join column in the IDL.
3008 // The link must be defined in the child table,
3009 // and point to the right parent table.
3010 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3011 const char* reltype = NULL;
3012 const char* other_class = NULL;
3013 reltype = osrfHashGet( idl_link, "reltype" );
3014 if( reltype && strcmp( reltype, "has_many" ) )
3015 other_class = osrfHashGet( idl_link, "class" );
3016 if( other_class && !strcmp( other_class, leftclass ) )
3017 fkey = osrfHashGet( idl_link, "key" );
3021 "%s: JOIN failed. No link defined from %s.%s to %s",
3027 buffer_free( join_buf );
3029 jsonObjectFree( freeable_hash );
3030 jsonIteratorFree( search_itr );
3034 } else if( !field && fkey ) {
3035 // Look up the corresponding join column in the IDL.
3036 // The link must be defined in the child table,
3037 // and point to the right parent table.
3038 osrfHash* left_links = left_info->links;
3039 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3040 const char* reltype = NULL;
3041 const char* other_class = NULL;
3042 reltype = osrfHashGet( idl_link, "reltype" );
3043 if( reltype && strcmp( reltype, "has_many" ) )
3044 other_class = osrfHashGet( idl_link, "class" );
3045 if( other_class && !strcmp( other_class, class ) )
3046 field = osrfHashGet( idl_link, "key" );
3050 "%s: JOIN failed. No link defined from %s.%s to %s",
3056 buffer_free( join_buf );
3058 jsonObjectFree( freeable_hash );
3059 jsonIteratorFree( search_itr );
3063 } else if( !field && !fkey ) {
3064 osrfHash* left_links = left_info->links;
3066 // For each link defined for the left class:
3067 // see if the link references the joined class
3068 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3069 osrfHash* curr_link = NULL;
3070 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3071 const char* other_class = osrfHashGet( curr_link, "class" );
3072 if( other_class && !strcmp( other_class, class ) ) {
3074 // In the IDL, the parent class doesn't always know then names of the child
3075 // columns that are pointing to it, so don't use that end of the link
3076 const char* reltype = osrfHashGet( curr_link, "reltype" );
3077 if( reltype && strcmp( reltype, "has_many" ) ) {
3078 // Found a link between the classes
3079 fkey = osrfHashIteratorKey( itr );
3080 field = osrfHashGet( curr_link, "key" );
3085 osrfHashIteratorFree( itr );
3087 if( !field || !fkey ) {
3088 // Do another such search, with the classes reversed
3090 // For each link defined for the joined class:
3091 // see if the link references the left class
3092 osrfHashIterator* itr = osrfNewHashIterator( links );
3093 osrfHash* curr_link = NULL;
3094 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3095 const char* other_class = osrfHashGet( curr_link, "class" );
3096 if( other_class && !strcmp( other_class, leftclass ) ) {
3098 // In the IDL, the parent class doesn't know then names of the child
3099 // columns that are pointing to it, so don't use that end of the link
3100 const char* reltype = osrfHashGet( curr_link, "reltype" );
3101 if( reltype && strcmp( reltype, "has_many" ) ) {
3102 // Found a link between the classes
3103 field = osrfHashIteratorKey( itr );
3104 fkey = osrfHashGet( curr_link, "key" );
3109 osrfHashIteratorFree( itr );
3112 if( !field || !fkey ) {
3115 "%s: JOIN failed. No link defined between %s and %s",
3120 buffer_free( join_buf );
3122 jsonObjectFree( freeable_hash );
3123 jsonIteratorFree( search_itr );
3128 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3130 if( !strcasecmp( type,"left" )) {
3131 buffer_add( join_buf, " LEFT JOIN" );
3132 } else if( !strcasecmp( type,"right" )) {
3133 buffer_add( join_buf, " RIGHT JOIN" );
3134 } else if( !strcasecmp( type,"full" )) {
3135 buffer_add( join_buf, " FULL JOIN" );
3137 buffer_add( join_buf, " INNER JOIN" );
3140 buffer_add( join_buf, " INNER JOIN" );
3143 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3144 table, right_alias, right_alias, field, left_info->alias, fkey );
3146 // Add any other join conditions as specified by "filter"
3147 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3149 const char* filter_op = jsonObjectGetString(
3150 jsonObjectGetKeyConst( snode, "filter_op" ) );
3151 if( filter_op && !strcasecmp( "or",filter_op )) {
3152 buffer_add( join_buf, " OR " );
3154 buffer_add( join_buf, " AND " );
3157 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3159 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3160 OSRF_BUFFER_ADD( join_buf, jpred );
3165 "%s: JOIN failed. Invalid conditional expression.",
3168 jsonIteratorFree( search_itr );
3169 buffer_free( join_buf );
3171 jsonObjectFree( freeable_hash );
3176 buffer_add( join_buf, " ) " );
3178 // Recursively add a nested join, if one is present
3179 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3181 char* jpred = searchJOIN( join_filter, right_info );
3183 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3184 OSRF_BUFFER_ADD( join_buf, jpred );
3187 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3188 jsonIteratorFree( search_itr );
3189 buffer_free( join_buf );
3191 jsonObjectFree( freeable_hash );
3198 jsonObjectFree( freeable_hash );
3199 jsonIteratorFree( search_itr );
3201 return buffer_release( join_buf );
3206 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3207 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3208 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3210 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3212 search_hash is the JSON expression of the conditions.
3213 meta is the class definition from the IDL, for the relevant table.
3214 opjoin_type indicates whether multiple conditions, if present, should be
3215 connected by AND or OR.
3216 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3217 to pass it to other functions -- and all they do with it is to use the session
3218 and request members to send error messages back to the client.
3222 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3223 int opjoin_type, osrfMethodContext* ctx ) {
3227 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3228 "opjoin_type = %d, ctx addr = %p",
3231 class_info->class_def,
3236 growing_buffer* sql_buf = buffer_init( 128 );
3238 jsonObject* node = NULL;
3241 if( search_hash->type == JSON_ARRAY ) {
3242 if( 0 == search_hash->size ) {
3245 "%s: Invalid predicate structure: empty JSON array",
3248 buffer_free( sql_buf );
3252 unsigned long i = 0;
3253 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3257 if( opjoin_type == OR_OP_JOIN )
3258 buffer_add( sql_buf, " OR " );
3260 buffer_add( sql_buf, " AND " );
3263 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3265 buffer_free( sql_buf );
3269 buffer_fadd( sql_buf, "( %s )", subpred );
3273 } else if( search_hash->type == JSON_HASH ) {
3274 osrfLogDebug( OSRF_LOG_MARK,
3275 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3276 jsonIterator* search_itr = jsonNewIterator( search_hash );
3277 if( !jsonIteratorHasNext( search_itr ) ) {
3280 "%s: Invalid predicate structure: empty JSON object",
3283 jsonIteratorFree( search_itr );
3284 buffer_free( sql_buf );
3288 while( (node = jsonIteratorNext( search_itr )) ) {
3293 if( opjoin_type == OR_OP_JOIN )
3294 buffer_add( sql_buf, " OR " );
3296 buffer_add( sql_buf, " AND " );
3299 if( '+' == search_itr->key[ 0 ] ) {
3301 // This plus sign prefixes a class name or other table alias;
3302 // make sure the table alias is in scope
3303 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3304 if( ! alias_info ) {
3307 "%s: Invalid table alias \"%s\" in WHERE clause",
3311 jsonIteratorFree( search_itr );
3312 buffer_free( sql_buf );
3316 if( node->type == JSON_STRING ) {
3317 // It's the name of a column; make sure it belongs to the class
3318 const char* fieldname = jsonObjectGetString( node );
3319 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3322 "%s: Invalid column name \"%s\" in WHERE clause "
3323 "for table alias \"%s\"",
3328 jsonIteratorFree( search_itr );
3329 buffer_free( sql_buf );
3333 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3335 // It's something more complicated
3336 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3338 jsonIteratorFree( search_itr );
3339 buffer_free( sql_buf );
3343 buffer_fadd( sql_buf, "( %s )", subpred );
3346 } else if( '-' == search_itr->key[ 0 ] ) {
3347 if( !strcasecmp( "-or", search_itr->key )) {
3348 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3350 jsonIteratorFree( search_itr );
3351 buffer_free( sql_buf );
3355 buffer_fadd( sql_buf, "( %s )", subpred );
3357 } else if( !strcasecmp( "-and", search_itr->key )) {
3358 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3360 jsonIteratorFree( search_itr );
3361 buffer_free( sql_buf );
3365 buffer_fadd( sql_buf, "( %s )", subpred );
3367 } else if( !strcasecmp("-not",search_itr->key) ) {
3368 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3370 jsonIteratorFree( search_itr );
3371 buffer_free( sql_buf );
3375 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3377 } else if( !strcasecmp( "-exists", search_itr->key )) {
3378 char* subpred = buildQuery( ctx, node, SUBSELECT );
3380 jsonIteratorFree( search_itr );
3381 buffer_free( sql_buf );
3385 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3387 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3388 char* subpred = buildQuery( ctx, node, SUBSELECT );
3390 jsonIteratorFree( search_itr );
3391 buffer_free( sql_buf );
3395 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3397 } else { // Invalid "minus" operator
3400 "%s: Invalid operator \"%s\" in WHERE clause",
3404 jsonIteratorFree( search_itr );
3405 buffer_free( sql_buf );
3411 const char* class = class_info->class_name;
3412 osrfHash* fields = class_info->fields;
3413 osrfHash* field = osrfHashGet( fields, search_itr->key );
3416 const char* table = class_info->source_def;
3419 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3422 table ? table : "?",
3425 jsonIteratorFree( search_itr );
3426 buffer_free( sql_buf );
3430 char* subpred = searchPredicate( class_info, field, node, ctx );
3432 buffer_free( sql_buf );
3433 jsonIteratorFree( search_itr );
3437 buffer_add( sql_buf, subpred );
3441 jsonIteratorFree( search_itr );
3444 // ERROR ... only hash and array allowed at this level
3445 char* predicate_string = jsonObjectToJSON( search_hash );
3448 "%s: Invalid predicate structure: %s",
3452 buffer_free( sql_buf );
3453 free( predicate_string );
3457 return buffer_release( sql_buf );
3460 /* Build a JSON_ARRAY of field names for a given table alias
3462 static jsonObject* defaultSelectList( const char* table_alias ) {
3467 ClassInfo* class_info = search_all_alias( table_alias );
3468 if( ! class_info ) {
3471 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3478 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3479 osrfHash* field_def = NULL;
3480 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3481 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3482 const char* field_name = osrfHashIteratorKey( field_itr );
3483 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3484 jsonObjectPush( array, jsonNewObject( field_name ) );
3487 osrfHashIteratorFree( field_itr );
3492 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3493 // The jsonObject must be a JSON_HASH with an single entry for "union",
3494 // "intersect", or "except". The data associated with this key must be an
3495 // array of hashes, each hash being a query.
3496 // Also allowed but currently ignored: entries for "order_by" and "alias".
3497 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3499 if( ! combo || combo->type != JSON_HASH )
3500 return NULL; // should be impossible; validated by caller
3502 const jsonObject* query_array = NULL; // array of subordinate queries
3503 const char* op = NULL; // name of operator, e.g. UNION
3504 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3505 int op_count = 0; // for detecting conflicting operators
3506 int excepting = 0; // boolean
3507 int all = 0; // boolean
3508 jsonObject* order_obj = NULL;
3510 // Identify the elements in the hash
3511 jsonIterator* query_itr = jsonNewIterator( combo );
3512 jsonObject* curr_obj = NULL;
3513 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3514 if( ! strcmp( "union", query_itr->key ) ) {
3517 query_array = curr_obj;
3518 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3521 query_array = curr_obj;
3522 } else if( ! strcmp( "except", query_itr->key ) ) {
3526 query_array = curr_obj;
3527 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3530 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3533 order_obj = curr_obj;
3534 } else if( ! strcmp( "alias", query_itr->key ) ) {
3535 if( curr_obj->type != JSON_STRING ) {
3536 jsonIteratorFree( query_itr );
3539 alias = jsonObjectGetString( curr_obj );
3540 } else if( ! strcmp( "all", query_itr->key ) ) {
3541 if( obj_is_true( curr_obj ) )
3545 osrfAppSessionStatus(
3547 OSRF_STATUS_INTERNALSERVERERROR,
3548 "osrfMethodException",
3550 "Malformed query; unexpected entry in query object"
3554 "%s: Unexpected entry for \"%s\" in%squery",
3559 jsonIteratorFree( query_itr );
3563 jsonIteratorFree( query_itr );
3565 // More sanity checks
3566 if( ! query_array ) {
3568 osrfAppSessionStatus(
3570 OSRF_STATUS_INTERNALSERVERERROR,
3571 "osrfMethodException",
3573 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3577 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3580 return NULL; // should be impossible...
3581 } else if( op_count > 1 ) {
3583 osrfAppSessionStatus(
3585 OSRF_STATUS_INTERNALSERVERERROR,
3586 "osrfMethodException",
3588 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3592 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3596 } if( query_array->type != JSON_ARRAY ) {
3598 osrfAppSessionStatus(
3600 OSRF_STATUS_INTERNALSERVERERROR,
3601 "osrfMethodException",
3603 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3607 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3610 json_type( query_array->type )
3613 } if( query_array->size < 2 ) {
3615 osrfAppSessionStatus(
3617 OSRF_STATUS_INTERNALSERVERERROR,
3618 "osrfMethodException",
3620 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3624 "%s:%srequires multiple queries as operands",
3629 } else if( excepting && query_array->size > 2 ) {
3631 osrfAppSessionStatus(
3633 OSRF_STATUS_INTERNALSERVERERROR,
3634 "osrfMethodException",
3636 "EXCEPT operator has too many queries as operands"
3640 "%s:EXCEPT operator has too many queries as operands",
3644 } else if( order_obj && ! alias ) {
3646 osrfAppSessionStatus(
3648 OSRF_STATUS_INTERNALSERVERERROR,
3649 "osrfMethodException",
3651 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3655 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3661 // So far so good. Now build the SQL.
3662 growing_buffer* sql = buffer_init( 256 );
3664 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3665 // Add a layer of parentheses
3666 if( flags & SUBCOMBO )
3667 OSRF_BUFFER_ADD( sql, "( " );
3669 // Traverse the query array. Each entry should be a hash.
3670 int first = 1; // boolean
3672 jsonObject* query = NULL;
3673 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3674 if( query->type != JSON_HASH ) {
3676 osrfAppSessionStatus(
3678 OSRF_STATUS_INTERNALSERVERERROR,
3679 "osrfMethodException",
3681 "Malformed query under UNION, INTERSECT or EXCEPT"
3685 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3688 json_type( query->type )
3697 OSRF_BUFFER_ADD( sql, op );
3699 OSRF_BUFFER_ADD( sql, "ALL " );
3702 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3706 "%s: Error building query under%s",
3714 OSRF_BUFFER_ADD( sql, query_str );
3717 if( flags & SUBCOMBO )
3718 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3720 if( !(flags & SUBSELECT) )
3721 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3723 return buffer_release( sql );
3726 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3727 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3728 // or "except" to indicate the type of query.
3729 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3733 osrfAppSessionStatus(
3735 OSRF_STATUS_INTERNALSERVERERROR,
3736 "osrfMethodException",
3738 "Malformed query; no query object"
3740 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3742 } else if( query->type != JSON_HASH ) {
3744 osrfAppSessionStatus(
3746 OSRF_STATUS_INTERNALSERVERERROR,
3747 "osrfMethodException",
3749 "Malformed query object"
3753 "%s: Query object is %s instead of JSON_HASH",
3755 json_type( query->type )
3760 // Determine what kind of query it purports to be, and dispatch accordingly.
3761 if( jsonObjectGetKeyConst( query, "union" ) ||
3762 jsonObjectGetKeyConst( query, "intersect" ) ||
3763 jsonObjectGetKeyConst( query, "except" )) {
3764 return doCombo( ctx, query, flags );
3766 // It is presumably a SELECT query
3768 // Push a node onto the stack for the current query. Every level of
3769 // subquery gets its own QueryFrame on the Stack.
3772 // Build an SQL SELECT statement
3775 jsonObjectGetKey( query, "select" ),
3776 jsonObjectGetKeyConst( query, "from" ),
3777 jsonObjectGetKeyConst( query, "where" ),
3778 jsonObjectGetKeyConst( query, "having" ),
3779 jsonObjectGetKeyConst( query, "order_by" ),
3780 jsonObjectGetKeyConst( query, "limit" ),
3781 jsonObjectGetKeyConst( query, "offset" ),
3790 /* method context */ osrfMethodContext* ctx,
3792 /* SELECT */ jsonObject* selhash,
3793 /* FROM */ const jsonObject* join_hash,
3794 /* WHERE */ const jsonObject* search_hash,
3795 /* HAVING */ const jsonObject* having_hash,
3796 /* ORDER BY */ const jsonObject* order_hash,
3797 /* LIMIT */ const jsonObject* limit,
3798 /* OFFSET */ const jsonObject* offset,
3799 /* flags */ int flags
3801 const char* locale = osrf_message_get_last_locale();
3803 // general tmp objects
3804 const jsonObject* tmp_const;
3805 jsonObject* selclass = NULL;
3806 jsonObject* snode = NULL;
3807 jsonObject* onode = NULL;
3809 char* string = NULL;
3810 int from_function = 0;
3815 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3817 // punt if there's no FROM clause
3818 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3821 "%s: FROM clause is missing or empty",
3825 osrfAppSessionStatus(
3827 OSRF_STATUS_INTERNALSERVERERROR,
3828 "osrfMethodException",
3830 "FROM clause is missing or empty in JSON query"
3835 // the core search class
3836 const char* core_class = NULL;
3838 // get the core class -- the only key of the top level FROM clause, or a string
3839 if( join_hash->type == JSON_HASH ) {
3840 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3841 snode = jsonIteratorNext( tmp_itr );
3843 // Populate the current QueryFrame with information
3844 // about the core class
3845 if( add_query_core( NULL, tmp_itr->key ) ) {
3847 osrfAppSessionStatus(
3849 OSRF_STATUS_INTERNALSERVERERROR,
3850 "osrfMethodException",
3852 "Unable to look up core class"
3856 core_class = curr_query->core.class_name;
3859 jsonObject* extra = jsonIteratorNext( tmp_itr );
3861 jsonIteratorFree( tmp_itr );
3864 // There shouldn't be more than one entry in join_hash
3868 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3872 osrfAppSessionStatus(
3874 OSRF_STATUS_INTERNALSERVERERROR,
3875 "osrfMethodException",
3877 "Malformed FROM clause in JSON query"
3879 return NULL; // Malformed join_hash; extra entry
3881 } else if( join_hash->type == JSON_ARRAY ) {
3882 // We're selecting from a function, not from a table
3884 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3887 } else if( join_hash->type == JSON_STRING ) {
3888 // Populate the current QueryFrame with information
3889 // about the core class
3890 core_class = jsonObjectGetString( join_hash );
3892 if( add_query_core( NULL, core_class ) ) {
3894 osrfAppSessionStatus(
3896 OSRF_STATUS_INTERNALSERVERERROR,
3897 "osrfMethodException",
3899 "Unable to look up core class"
3907 "%s: FROM clause is unexpected JSON type: %s",
3909 json_type( join_hash->type )
3912 osrfAppSessionStatus(
3914 OSRF_STATUS_INTERNALSERVERERROR,
3915 "osrfMethodException",
3917 "Ill-formed FROM clause in JSON query"
3922 // Build the join clause, if any, while filling out the list
3923 // of joined classes in the current QueryFrame.
3924 char* join_clause = NULL;
3925 if( join_hash && ! from_function ) {
3927 join_clause = searchJOIN( join_hash, &curr_query->core );
3928 if( ! join_clause ) {
3930 osrfAppSessionStatus(
3932 OSRF_STATUS_INTERNALSERVERERROR,
3933 "osrfMethodException",
3935 "Unable to construct JOIN clause(s)"
3941 // For in case we don't get a select list
3942 jsonObject* defaultselhash = NULL;
3944 // if there is no select list, build a default select list ...
3945 if( !selhash && !from_function ) {
3946 jsonObject* default_list = defaultSelectList( core_class );
3947 if( ! default_list ) {
3949 osrfAppSessionStatus(
3951 OSRF_STATUS_INTERNALSERVERERROR,
3952 "osrfMethodException",
3954 "Unable to build default SELECT clause in JSON query"
3956 free( join_clause );
3961 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3962 jsonObjectSetKey( selhash, core_class, default_list );
3965 // The SELECT clause can be encoded only by a hash
3966 if( !from_function && selhash->type != JSON_HASH ) {
3969 "%s: Expected JSON_HASH for SELECT clause; found %s",
3971 json_type( selhash->type )
3975 osrfAppSessionStatus(
3977 OSRF_STATUS_INTERNALSERVERERROR,
3978 "osrfMethodException",
3980 "Malformed SELECT clause in JSON query"
3982 free( join_clause );
3986 // If you see a null or wild card specifier for the core class, or an
3987 // empty array, replace it with a default SELECT list
3988 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3990 int default_needed = 0; // boolean
3991 if( JSON_STRING == tmp_const->type
3992 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3994 else if( JSON_NULL == tmp_const->type )
3997 if( default_needed ) {
3998 // Build a default SELECT list
3999 jsonObject* default_list = defaultSelectList( core_class );
4000 if( ! default_list ) {
4002 osrfAppSessionStatus(
4004 OSRF_STATUS_INTERNALSERVERERROR,
4005 "osrfMethodException",
4007 "Can't build default SELECT clause in JSON query"
4009 free( join_clause );
4014 jsonObjectSetKey( selhash, core_class, default_list );
4018 // temp buffers for the SELECT list and GROUP BY clause
4019 growing_buffer* select_buf = buffer_init( 128 );
4020 growing_buffer* group_buf = buffer_init( 128 );
4022 int aggregate_found = 0; // boolean
4024 // Build a select list
4025 if( from_function ) // From a function we select everything
4026 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4029 // Build the SELECT list as SQL
4033 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4034 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4036 const char* cname = selclass_itr->key;
4038 // Make sure the target relation is in the FROM clause.
4040 // At this point join_hash is a step down from the join_hash we
4041 // received as a parameter. If the original was a JSON_STRING,
4042 // then json_hash is now NULL. If the original was a JSON_HASH,
4043 // then json_hash is now the first (and only) entry in it,
4044 // denoting the core class. We've already excluded the
4045 // possibility that the original was a JSON_ARRAY, because in
4046 // that case from_function would be non-NULL, and we wouldn't
4049 // If the current table alias isn't in scope, bail out
4050 ClassInfo* class_info = search_alias( cname );
4051 if( ! class_info ) {
4054 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4059 osrfAppSessionStatus(
4061 OSRF_STATUS_INTERNALSERVERERROR,
4062 "osrfMethodException",
4064 "Selected class not in FROM clause in JSON query"
4066 jsonIteratorFree( selclass_itr );
4067 buffer_free( select_buf );
4068 buffer_free( group_buf );
4069 if( defaultselhash )
4070 jsonObjectFree( defaultselhash );
4071 free( join_clause );
4075 if( selclass->type != JSON_ARRAY ) {
4078 "%s: Malformed SELECT list for class \"%s\"; not an array",
4083 osrfAppSessionStatus(
4085 OSRF_STATUS_INTERNALSERVERERROR,
4086 "osrfMethodException",
4088 "Selected class not in FROM clause in JSON query"
4091 jsonIteratorFree( selclass_itr );
4092 buffer_free( select_buf );
4093 buffer_free( group_buf );
4094 if( defaultselhash )
4095 jsonObjectFree( defaultselhash );
4096 free( join_clause );
4100 // Look up some attributes of the current class
4101 osrfHash* idlClass = class_info->class_def;
4102 osrfHash* class_field_set = class_info->fields;
4103 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4104 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4106 if( 0 == selclass->size ) {
4109 "%s: No columns selected from \"%s\"",
4115 // stitch together the column list for the current table alias...
4116 unsigned long field_idx = 0;
4117 jsonObject* selfield = NULL;
4118 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4120 // If we need a separator comma, add one
4124 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4127 // if the field specification is a string, add it to the list
4128 if( selfield->type == JSON_STRING ) {
4130 // Look up the field in the IDL
4131 const char* col_name = jsonObjectGetString( selfield );
4132 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4134 // No such field in current class
4137 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4143 osrfAppSessionStatus(
4145 OSRF_STATUS_INTERNALSERVERERROR,
4146 "osrfMethodException",
4148 "Selected column not defined in JSON query"
4150 jsonIteratorFree( selclass_itr );
4151 buffer_free( select_buf );
4152 buffer_free( group_buf );
4153 if( defaultselhash )
4154 jsonObjectFree( defaultselhash );
4155 free( join_clause );
4157 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4158 // Virtual field not allowed
4161 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4167 osrfAppSessionStatus(
4169 OSRF_STATUS_INTERNALSERVERERROR,
4170 "osrfMethodException",
4172 "Selected column may not be virtual in JSON query"
4174 jsonIteratorFree( selclass_itr );
4175 buffer_free( select_buf );
4176 buffer_free( group_buf );
4177 if( defaultselhash )
4178 jsonObjectFree( defaultselhash );
4179 free( join_clause );
4185 if( flags & DISABLE_I18N )
4188 i18n = osrfHashGet( field_def, "i18n" );
4190 if( str_is_true( i18n ) ) {
4191 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4192 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4193 class_tname, cname, col_name, class_pkey,
4194 cname, class_pkey, locale, col_name );
4196 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4197 cname, col_name, col_name );
4200 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4201 cname, col_name, col_name );
4204 // ... but it could be an object, in which case we check for a Field Transform
4205 } else if( selfield->type == JSON_HASH ) {
4207 const char* col_name = jsonObjectGetString(
4208 jsonObjectGetKeyConst( selfield, "column" ) );
4210 // Get the field definition from the IDL
4211 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4213 // No such field in current class
4216 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4222 osrfAppSessionStatus(
4224 OSRF_STATUS_INTERNALSERVERERROR,
4225 "osrfMethodException",
4227 "Selected column is not defined in JSON query"
4229 jsonIteratorFree( selclass_itr );
4230 buffer_free( select_buf );
4231 buffer_free( group_buf );
4232 if( defaultselhash )
4233 jsonObjectFree( defaultselhash );
4234 free( join_clause );
4236 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4237 // No such field in current class
4240 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4246 osrfAppSessionStatus(
4248 OSRF_STATUS_INTERNALSERVERERROR,
4249 "osrfMethodException",
4251 "Selected column is virtual in JSON query"
4253 jsonIteratorFree( selclass_itr );
4254 buffer_free( select_buf );
4255 buffer_free( group_buf );
4256 if( defaultselhash )
4257 jsonObjectFree( defaultselhash );
4258 free( join_clause );
4262 // Decide what to use as a column alias
4264 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4265 _alias = jsonObjectGetString( tmp_const );
4266 } else { // Use field name as the alias
4270 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4271 char* transform_str = searchFieldTransform(
4272 class_info->alias, field_def, selfield );
4273 if( transform_str ) {
4274 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4275 free( transform_str );
4278 osrfAppSessionStatus(
4280 OSRF_STATUS_INTERNALSERVERERROR,
4281 "osrfMethodException",
4283 "Unable to generate transform function in JSON query"
4285 jsonIteratorFree( selclass_itr );
4286 buffer_free( select_buf );
4287 buffer_free( group_buf );
4288 if( defaultselhash )
4289 jsonObjectFree( defaultselhash );
4290 free( join_clause );
4297 if( flags & DISABLE_I18N )
4300 i18n = osrfHashGet( field_def, "i18n" );
4302 if( str_is_true( i18n ) ) {
4303 buffer_fadd( select_buf,
4304 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4305 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4306 class_tname, cname, col_name, class_pkey, cname,
4307 class_pkey, locale, _alias );
4309 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4310 cname, col_name, _alias );
4313 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4314 cname, col_name, _alias );
4321 "%s: Selected item is unexpected JSON type: %s",
4323 json_type( selfield->type )
4326 osrfAppSessionStatus(
4328 OSRF_STATUS_INTERNALSERVERERROR,
4329 "osrfMethodException",
4331 "Ill-formed SELECT item in JSON query"
4333 jsonIteratorFree( selclass_itr );
4334 buffer_free( select_buf );
4335 buffer_free( group_buf );
4336 if( defaultselhash )
4337 jsonObjectFree( defaultselhash );
4338 free( join_clause );
4342 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4343 if( obj_is_true( agg_obj ) )
4344 aggregate_found = 1;
4346 // Append a comma (except for the first one)
4347 // and add the column to a GROUP BY clause
4351 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4353 buffer_fadd( group_buf, " %d", sel_pos );
4357 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4359 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( elfield, "aggregate");
4360 if ( ! obj_is_true( aggregate_obj ) ) {
4364 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4367 buffer_fadd(group_buf, " %d", sel_pos);
4370 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4374 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4377 _column = searchFieldTransform(class_info->alias, field, selfield);
4378 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4379 OSRF_BUFFER_ADD(group_buf, _column);
4380 _column = searchFieldTransform(class_info->alias, field, selfield);
4387 } // end while -- iterating across SELECT columns
4389 } // end while -- iterating across classes
4391 jsonIteratorFree( selclass_itr );
4394 char* col_list = buffer_release( select_buf );
4396 // Make sure the SELECT list isn't empty. This can happen, for example,
4397 // if we try to build a default SELECT clause from a non-core table.
4400 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4402 osrfAppSessionStatus(
4404 OSRF_STATUS_INTERNALSERVERERROR,
4405 "osrfMethodException",
4407 "SELECT list is empty"
4410 buffer_free( group_buf );
4411 if( defaultselhash )
4412 jsonObjectFree( defaultselhash );
4413 free( join_clause );
4419 table = searchValueTransform( join_hash );
4421 table = strdup( curr_query->core.source_def );
4425 osrfAppSessionStatus(
4427 OSRF_STATUS_INTERNALSERVERERROR,
4428 "osrfMethodException",
4430 "Unable to identify table for core class"
4433 buffer_free( group_buf );
4434 if( defaultselhash )
4435 jsonObjectFree( defaultselhash );
4436 free( join_clause );
4440 // Put it all together
4441 growing_buffer* sql_buf = buffer_init( 128 );
4442 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4446 // Append the join clause, if any
4448 buffer_add(sql_buf, join_clause );
4449 free( join_clause );
4452 char* order_by_list = NULL;
4453 char* having_buf = NULL;
4455 if( !from_function ) {
4457 // Build a WHERE clause, if there is one
4459 buffer_add( sql_buf, " WHERE " );
4461 // and it's on the WHERE clause
4462 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4465 osrfAppSessionStatus(
4467 OSRF_STATUS_INTERNALSERVERERROR,
4468 "osrfMethodException",
4470 "Severe query error in WHERE predicate -- see error log for more details"
4473 buffer_free( group_buf );
4474 buffer_free( sql_buf );
4475 if( defaultselhash )
4476 jsonObjectFree( defaultselhash );
4480 buffer_add( sql_buf, pred );
4484 // Build a HAVING clause, if there is one
4487 // and it's on the the WHERE clause
4488 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4490 if( ! having_buf ) {
4492 osrfAppSessionStatus(
4494 OSRF_STATUS_INTERNALSERVERERROR,
4495 "osrfMethodException",
4497 "Severe query error in HAVING predicate -- see error log for more details"
4500 buffer_free( group_buf );
4501 buffer_free( sql_buf );
4502 if( defaultselhash )
4503 jsonObjectFree( defaultselhash );
4508 // Build an ORDER BY clause, if there is one
4509 if( NULL == order_hash )
4510 ; // No ORDER BY? do nothing
4511 else if( JSON_ARRAY == order_hash->type ) {
4512 order_by_list = buildOrderByFromArray( ctx, order_hash );
4513 if( !order_by_list ) {
4515 buffer_free( group_buf );
4516 buffer_free( sql_buf );
4517 if( defaultselhash )
4518 jsonObjectFree( defaultselhash );
4521 } else if( JSON_HASH == order_hash->type ) {
4522 // This hash is keyed on class alias. Each class has either
4523 // an array of field names or a hash keyed on field name.
4524 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4525 jsonIterator* class_itr = jsonNewIterator( order_hash );
4526 while( (snode = jsonIteratorNext( class_itr )) ) {
4528 ClassInfo* order_class_info = search_alias( class_itr->key );
4529 if( ! order_class_info ) {
4530 osrfLogError( OSRF_LOG_MARK,
4531 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4532 modulename, class_itr->key );
4534 osrfAppSessionStatus(
4536 OSRF_STATUS_INTERNALSERVERERROR,
4537 "osrfMethodException",
4539 "Invalid class referenced in ORDER BY clause -- "
4540 "see error log for more details"
4542 jsonIteratorFree( class_itr );
4543 buffer_free( order_buf );
4545 buffer_free( group_buf );
4546 buffer_free( sql_buf );
4547 if( defaultselhash )
4548 jsonObjectFree( defaultselhash );
4552 osrfHash* field_list_def = order_class_info->fields;
4554 if( snode->type == JSON_HASH ) {
4556 // Hash is keyed on field names from the current class. For each field
4557 // there is another layer of hash to define the sorting details, if any,
4558 // or a string to indicate direction of sorting.
4559 jsonIterator* order_itr = jsonNewIterator( snode );
4560 while( (onode = jsonIteratorNext( order_itr )) ) {
4562 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4564 osrfLogError( OSRF_LOG_MARK,
4565 "%s: Invalid field \"%s\" in ORDER BY clause",
4566 modulename, order_itr->key );
4568 osrfAppSessionStatus(
4570 OSRF_STATUS_INTERNALSERVERERROR,
4571 "osrfMethodException",
4573 "Invalid field in ORDER BY clause -- "
4574 "see error log for more details"
4576 jsonIteratorFree( order_itr );
4577 jsonIteratorFree( class_itr );
4578 buffer_free( order_buf );
4580 buffer_free( group_buf );
4581 buffer_free( sql_buf );
4582 if( defaultselhash )
4583 jsonObjectFree( defaultselhash );
4585 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4586 osrfLogError( OSRF_LOG_MARK,
4587 "%s: Virtual field \"%s\" in ORDER BY clause",
4588 modulename, order_itr->key );
4590 osrfAppSessionStatus(
4592 OSRF_STATUS_INTERNALSERVERERROR,
4593 "osrfMethodException",
4595 "Virtual field in ORDER BY clause -- "
4596 "see error log for more details"
4598 jsonIteratorFree( order_itr );
4599 jsonIteratorFree( class_itr );
4600 buffer_free( order_buf );
4602 buffer_free( group_buf );
4603 buffer_free( sql_buf );
4604 if( defaultselhash )
4605 jsonObjectFree( defaultselhash );
4609 const char* direction = NULL;
4610 if( onode->type == JSON_HASH ) {
4611 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4612 string = searchFieldTransform(
4614 osrfHashGet( field_list_def, order_itr->key ),
4618 if( ctx ) osrfAppSessionStatus(
4620 OSRF_STATUS_INTERNALSERVERERROR,
4621 "osrfMethodException",
4623 "Severe query error in ORDER BY clause -- "
4624 "see error log for more details"
4626 jsonIteratorFree( order_itr );
4627 jsonIteratorFree( class_itr );
4629 buffer_free( group_buf );
4630 buffer_free( order_buf);
4631 buffer_free( sql_buf );
4632 if( defaultselhash )
4633 jsonObjectFree( defaultselhash );
4637 growing_buffer* field_buf = buffer_init( 16 );
4638 buffer_fadd( field_buf, "\"%s\".%s",
4639 class_itr->key, order_itr->key );
4640 string = buffer_release( field_buf );
4643 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4644 const char* dir = jsonObjectGetString( tmp_const );
4645 if(!strncasecmp( dir, "d", 1 )) {
4646 direction = " DESC";
4652 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4653 osrfLogError( OSRF_LOG_MARK,
4654 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4655 modulename, json_type( onode->type ) );
4657 osrfAppSessionStatus(
4659 OSRF_STATUS_INTERNALSERVERERROR,
4660 "osrfMethodException",
4662 "Malformed ORDER BY clause -- see error log for more details"
4664 jsonIteratorFree( order_itr );
4665 jsonIteratorFree( class_itr );
4667 buffer_free( group_buf );
4668 buffer_free( order_buf );
4669 buffer_free( sql_buf );
4670 if( defaultselhash )
4671 jsonObjectFree( defaultselhash );
4675 string = strdup( order_itr->key );
4676 const char* dir = jsonObjectGetString( onode );
4677 if( !strncasecmp( dir, "d", 1 )) {
4678 direction = " DESC";
4685 OSRF_BUFFER_ADD( order_buf, ", " );
4687 order_buf = buffer_init( 128 );
4689 OSRF_BUFFER_ADD( order_buf, string );
4693 OSRF_BUFFER_ADD( order_buf, direction );
4697 jsonIteratorFree( order_itr );
4699 } else if( snode->type == JSON_ARRAY ) {
4701 // Array is a list of fields from the current class
4702 unsigned long order_idx = 0;
4703 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4705 const char* _f = jsonObjectGetString( onode );
4707 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4709 osrfLogError( OSRF_LOG_MARK,
4710 "%s: Invalid field \"%s\" in ORDER BY clause",
4713 osrfAppSessionStatus(
4715 OSRF_STATUS_INTERNALSERVERERROR,
4716 "osrfMethodException",
4718 "Invalid field in ORDER BY clause -- "
4719 "see error log for more details"
4721 jsonIteratorFree( class_itr );
4722 buffer_free( order_buf );
4724 buffer_free( group_buf );
4725 buffer_free( sql_buf );
4726 if( defaultselhash )
4727 jsonObjectFree( defaultselhash );
4729 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4730 osrfLogError( OSRF_LOG_MARK,
4731 "%s: Virtual field \"%s\" in ORDER BY clause",
4734 osrfAppSessionStatus(
4736 OSRF_STATUS_INTERNALSERVERERROR,
4737 "osrfMethodException",
4739 "Virtual field in ORDER BY clause -- "
4740 "see error log for more details"
4742 jsonIteratorFree( class_itr );
4743 buffer_free( order_buf );
4745 buffer_free( group_buf );
4746 buffer_free( sql_buf );
4747 if( defaultselhash )
4748 jsonObjectFree( defaultselhash );
4753 OSRF_BUFFER_ADD( order_buf, ", " );
4755 order_buf = buffer_init( 128 );
4757 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4761 // IT'S THE OOOOOOOOOOOLD STYLE!
4763 osrfLogError( OSRF_LOG_MARK,
4764 "%s: Possible SQL injection attempt; direct order by is not allowed",
4767 osrfAppSessionStatus(
4769 OSRF_STATUS_INTERNALSERVERERROR,
4770 "osrfMethodException",
4772 "Severe query error -- see error log for more details"
4777 buffer_free( group_buf );
4778 buffer_free( order_buf );
4779 buffer_free( sql_buf );
4780 if( defaultselhash )
4781 jsonObjectFree( defaultselhash );
4782 jsonIteratorFree( class_itr );
4786 jsonIteratorFree( class_itr );
4788 order_by_list = buffer_release( order_buf );
4790 osrfLogError( OSRF_LOG_MARK,
4791 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4792 modulename, json_type( order_hash->type ) );
4794 osrfAppSessionStatus(
4796 OSRF_STATUS_INTERNALSERVERERROR,
4797 "osrfMethodException",
4799 "Malformed ORDER BY clause -- see error log for more details"
4802 buffer_free( group_buf );
4803 buffer_free( sql_buf );
4804 if( defaultselhash )
4805 jsonObjectFree( defaultselhash );
4810 string = buffer_release( group_buf );
4812 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4813 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4814 OSRF_BUFFER_ADD( sql_buf, string );
4819 if( having_buf && *having_buf ) {
4820 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4821 OSRF_BUFFER_ADD( sql_buf, having_buf );
4825 if( order_by_list ) {
4827 if( *order_by_list ) {
4828 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4829 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4832 free( order_by_list );
4836 const char* str = jsonObjectGetString( limit );
4837 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4841 const char* str = jsonObjectGetString( offset );
4842 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4845 if( !(flags & SUBSELECT) )
4846 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4848 if( defaultselhash )
4849 jsonObjectFree( defaultselhash );
4851 return buffer_release( sql_buf );
4853 } // end of SELECT()
4856 @brief Build a list of ORDER BY expressions.
4857 @param ctx Pointer to the method context.
4858 @param order_array Pointer to a JSON_ARRAY of field specifications.
4859 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
4860 Each expression may be either a column reference or a function call whose first parameter
4861 is a column reference.
4863 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
4864 It may optionally include entries for "direction" and/or "transform".
4866 The calling code is responsible for freeing the returned string.
4868 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
4869 if( ! order_array ) {
4870 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
4873 osrfAppSessionStatus(
4875 OSRF_STATUS_INTERNALSERVERERROR,
4876 "osrfMethodException",
4878 "Logic error: ORDER BY clause expected, not found; "
4879 "see error log for more details"
4882 } else if( order_array->type != JSON_ARRAY ) {
4883 osrfLogError( OSRF_LOG_MARK,
4884 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
4886 osrfAppSessionStatus(
4888 OSRF_STATUS_INTERNALSERVERERROR,
4889 "osrfMethodException",
4891 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
4895 growing_buffer* order_buf = buffer_init( 128 );
4896 int first = 1; // boolean
4898 jsonObject* order_spec;
4899 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
4901 if( JSON_HASH != order_spec->type ) {
4902 osrfLogError( OSRF_LOG_MARK,
4903 "%s: Malformed field specification in ORDER BY clause; "
4904 "expected JSON_HASH, found %s",
4905 modulename, json_type( order_spec->type ) );
4907 osrfAppSessionStatus(
4909 OSRF_STATUS_INTERNALSERVERERROR,
4910 "osrfMethodException",
4912 "Malformed ORDER BY clause -- see error log for more details"
4914 buffer_free( order_buf );
4918 const char* class_alias =
4919 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
4921 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
4923 if( !field || !class_alias ) {
4924 osrfLogError( OSRF_LOG_MARK,
4925 "%s: Missing class or field name in field specification of ORDER BY clause",
4928 osrfAppSessionStatus(
4930 OSRF_STATUS_INTERNALSERVERERROR,
4931 "osrfMethodException",
4933 "Malformed ORDER BY clause -- see error log for more details"
4935 buffer_free( order_buf );
4939 const ClassInfo* order_class_info = search_alias( class_alias );
4940 if( ! order_class_info ) {
4941 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4942 "not in FROM clause, skipping it", modulename, class_alias );
4946 // Add a separating comma, except at the beginning
4950 OSRF_BUFFER_ADD( order_buf, ", " );
4952 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4954 osrfLogError( OSRF_LOG_MARK,
4955 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4956 modulename, class_alias, field );
4958 osrfAppSessionStatus(
4960 OSRF_STATUS_INTERNALSERVERERROR,
4961 "osrfMethodException",
4963 "Invalid field referenced in ORDER BY clause -- "
4964 "see error log for more details"
4968 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4969 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4970 modulename, field );
4972 osrfAppSessionStatus(
4974 OSRF_STATUS_INTERNALSERVERERROR,
4975 "osrfMethodException",
4977 "Virtual field in ORDER BY clause -- see error log for more details"
4979 buffer_free( order_buf );
4983 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
4984 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
4985 if( ! transform_str ) {
4987 osrfAppSessionStatus(
4989 OSRF_STATUS_INTERNALSERVERERROR,
4990 "osrfMethodException",
4992 "Severe query error in ORDER BY clause -- "
4993 "see error log for more details"
4995 buffer_free( order_buf );
4999 OSRF_BUFFER_ADD( order_buf, transform_str );
5000 free( transform_str );
5003 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5005 const char* direction =
5006 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5008 if( direction[ 0 ] || 'D' == direction[ 0 ] )
5009 OSRF_BUFFER_ADD( order_buf, " DESC" );
5011 OSRF_BUFFER_ADD( order_buf, " ASC" );
5015 return buffer_release( order_buf );
5019 @brief Build a SELECT statement.
5020 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5021 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5022 @param meta Pointer to the class metadata for the core class.
5023 @param ctx Pointer to the method context.
5024 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5026 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5027 "order_by", "limit", and "offset".
5029 The SELECT statements built here are distinct from those built for the json_query method.
5031 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5032 osrfHash* meta, osrfMethodContext* ctx ) {
5034 const char* locale = osrf_message_get_last_locale();
5036 osrfHash* fields = osrfHashGet( meta, "fields" );
5037 const char* core_class = osrfHashGet( meta, "classname" );
5039 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5041 jsonObject* selhash = NULL;
5042 jsonObject* defaultselhash = NULL;
5044 growing_buffer* sql_buf = buffer_init( 128 );
5045 growing_buffer* select_buf = buffer_init( 128 );
5047 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5048 defaultselhash = jsonNewObjectType( JSON_HASH );
5049 selhash = defaultselhash;
5052 // If there's no SELECT list for the core class, build one
5053 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5054 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5056 // Add every non-virtual field to the field list
5057 osrfHash* field_def = NULL;
5058 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5059 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5060 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5061 const char* field = osrfHashIteratorKey( field_itr );
5062 jsonObjectPush( field_list, jsonNewObject( field ) );
5065 osrfHashIteratorFree( field_itr );
5066 jsonObjectSetKey( selhash, core_class, field_list );
5069 // Build a list of columns for the SELECT clause
5071 const jsonObject* snode = NULL;
5072 jsonIterator* class_itr = jsonNewIterator( selhash );
5073 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5075 // If the class isn't in the IDL, ignore it
5076 const char* cname = class_itr->key;
5077 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5081 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5082 if( strcmp( core_class, class_itr->key )) {
5086 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5087 if( !found->size ) {
5088 jsonObjectFree( found );
5092 jsonObjectFree( found );
5095 const jsonObject* node = NULL;
5096 jsonIterator* select_itr = jsonNewIterator( snode );
5097 while( (node = jsonIteratorNext( select_itr )) ) {
5098 const char* item_str = jsonObjectGetString( node );
5099 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5100 char* fname = osrfHashGet( field, "name" );
5108 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5113 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5114 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5117 i18n = osrfHashGet( field, "i18n" );
5119 if( str_is_true( i18n ) ) {
5120 char* pkey = osrfHashGet( idlClass, "primarykey" );
5121 char* tname = osrfHashGet( idlClass, "tablename" );
5123 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5124 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5125 tname, cname, fname, pkey, cname, pkey, locale, fname );
5127 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5130 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5134 jsonIteratorFree( select_itr );
5137 jsonIteratorFree( class_itr );
5139 char* col_list = buffer_release( select_buf );
5140 char* table = oilsGetRelation( meta );
5142 table = strdup( "(null)" );
5144 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5148 // Clear the query stack (as a fail-safe precaution against possible
5149 // leftover garbage); then push the first query frame onto the stack.
5150 clear_query_stack();
5152 if( add_query_core( NULL, core_class ) ) {
5154 osrfAppSessionStatus(
5156 OSRF_STATUS_INTERNALSERVERERROR,
5157 "osrfMethodException",
5159 "Unable to build query frame for core class"
5161 buffer_free( sql_buf );
5162 if( defaultselhash )
5163 jsonObjectFree( defaultselhash );
5167 // Add the JOIN clauses, if any
5169 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5170 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5171 OSRF_BUFFER_ADD( sql_buf, join_clause );
5172 free( join_clause );
5175 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5176 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5178 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5180 // Add the conditions in the WHERE clause
5181 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5183 osrfAppSessionStatus(
5185 OSRF_STATUS_INTERNALSERVERERROR,
5186 "osrfMethodException",
5188 "Severe query error -- see error log for more details"
5190 buffer_free( sql_buf );
5191 if( defaultselhash )
5192 jsonObjectFree( defaultselhash );
5193 clear_query_stack();
5196 buffer_add( sql_buf, pred );
5200 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5201 if( rest_of_query ) {
5202 const jsonObject* order_by = NULL;
5203 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5205 char* order_by_list = NULL;
5207 if( JSON_ARRAY == order_by->type ) {
5208 order_by_list = buildOrderByFromArray( ctx, order_by );
5209 if( !order_by_list ) {
5210 buffer_free( sql_buf );
5211 if( defaultselhash )
5212 jsonObjectFree( defaultselhash );
5213 clear_query_stack();
5216 } else if( JSON_HASH == order_by->type ) {
5217 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5218 // and build a list of ORDER BY expressions.
5219 growing_buffer* order_buf = buffer_init( 128 );
5221 jsonIterator* class_itr = jsonNewIterator( order_by );
5222 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5224 ClassInfo* order_class_info = search_alias( class_itr->key );
5225 if( ! order_class_info )
5226 continue; // class not referenced by FROM clause? Ignore it.
5228 if( JSON_HASH == snode->type ) {
5230 // If the data for the current class is a JSON_HASH, then it is
5231 // keyed on field name.
5233 const jsonObject* onode = NULL;
5234 jsonIterator* order_itr = jsonNewIterator( snode );
5235 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5237 osrfHash* field_def = osrfHashGet(
5238 order_class_info->fields, order_itr->key );
5240 continue; // Field not defined in IDL? Ignore it.
5241 if( str_is_true( osrfHashGet( field_def, "virtual")))
5242 continue; // Field is virtual? Ignore it.
5244 char* field_str = NULL;
5245 char* direction = NULL;
5246 if( onode->type == JSON_HASH ) {
5247 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5248 field_str = searchFieldTransform(
5249 class_itr->key, field_def, onode );
5251 osrfAppSessionStatus(
5253 OSRF_STATUS_INTERNALSERVERERROR,
5254 "osrfMethodException",
5256 "Severe query error in ORDER BY clause -- "
5257 "see error log for more details"
5259 jsonIteratorFree( order_itr );
5260 jsonIteratorFree( class_itr );
5261 buffer_free( order_buf );
5262 buffer_free( sql_buf );
5263 if( defaultselhash )
5264 jsonObjectFree( defaultselhash );
5265 clear_query_stack();
5269 growing_buffer* field_buf = buffer_init( 16 );
5270 buffer_fadd( field_buf, "\"%s\".%s",
5271 class_itr->key, order_itr->key );
5272 field_str = buffer_release( field_buf );
5275 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5276 const char* dir = jsonObjectGetString( order_by );
5277 if(!strncasecmp( dir, "d", 1 )) {
5278 direction = " DESC";
5282 field_str = strdup( order_itr->key );
5283 const char* dir = jsonObjectGetString( onode );
5284 if( !strncasecmp( dir, "d", 1 )) {
5285 direction = " DESC";
5294 buffer_add( order_buf, ", " );
5297 buffer_add( order_buf, field_str );
5301 buffer_add( order_buf, direction );
5303 } // end while; looping over ORDER BY expressions
5305 jsonIteratorFree( order_itr );
5307 } else if( JSON_STRING == snode->type ) {
5308 // We expect a comma-separated list of sort fields.
5309 const char* str = jsonObjectGetString( snode );
5310 if( strchr( str, ';' )) {
5311 // No semicolons allowed. It is theoretically possible for a
5312 // legitimate semicolon to occur within quotes, but it's not likely
5313 // to occur in practice in the context of an ORDER BY list.
5314 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5315 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5317 osrfAppSessionStatus(
5319 OSRF_STATUS_INTERNALSERVERERROR,
5320 "osrfMethodException",
5322 "Possible attempt at SOL injection -- "
5323 "semicolon found in ORDER BY list"
5326 jsonIteratorFree( class_itr );
5327 buffer_free( order_buf );
5328 buffer_free( sql_buf );
5329 if( defaultselhash )
5330 jsonObjectFree( defaultselhash );
5331 clear_query_stack();
5334 buffer_add( order_buf, str );
5338 } // end while; looping over order_by classes
5340 jsonIteratorFree( class_itr );
5341 order_by_list = buffer_release( order_buf );
5344 osrfLogWarning( OSRF_LOG_MARK,
5345 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5346 "no ORDER BY generated" );
5349 if( order_by_list && *order_by_list ) {
5350 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5351 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5354 free( order_by_list );
5357 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5359 const char* str = jsonObjectGetString( limit );
5367 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5369 const char* str = jsonObjectGetString( offset );
5378 if( defaultselhash )
5379 jsonObjectFree( defaultselhash );
5380 clear_query_stack();
5382 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5383 return buffer_release( sql_buf );
5386 int doJSONSearch ( osrfMethodContext* ctx ) {
5387 if(osrfMethodVerifyContext( ctx )) {
5388 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5392 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5396 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5400 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5401 flags |= SELECT_DISTINCT;
5403 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5404 flags |= DISABLE_I18N;
5406 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5407 clear_query_stack(); // a possibly needless precaution
5408 char* sql = buildQuery( ctx, hash, flags );
5409 clear_query_stack();
5416 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5419 dbhandle = writehandle;
5421 dbi_result result = dbi_conn_query( dbhandle, sql );
5424 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5426 if( dbi_result_first_row( result )) {
5427 /* JSONify the result */
5428 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5431 jsonObject* return_val = oilsMakeJSONFromResult( result );
5432 osrfAppRespond( ctx, return_val );
5433 jsonObjectFree( return_val );
5434 } while( dbi_result_next_row( result ));
5437 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5440 osrfAppRespondComplete( ctx, NULL );
5442 /* clean up the query */
5443 dbi_result_free( result );
5448 int errnum = dbi_conn_error( dbhandle, &msg );
5449 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5450 modulename, sql, errnum, msg ? msg : "(No description available)" );
5451 osrfAppSessionStatus(
5453 OSRF_STATUS_INTERNALSERVERERROR,
5454 "osrfMethodException",
5456 "Severe query error -- see error log for more details"
5458 if( !oilsIsDBConnected( dbhandle ))
5459 osrfAppSessionPanic( ctx->session );
5466 // The last parameter, err, is used to report an error condition by updating an int owned by
5467 // the calling code.
5469 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5470 // It is the responsibility of the calling code to initialize *err before the
5471 // call, so that it will be able to make sense of the result.
5473 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5474 // redundant anyway.
5475 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5476 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5479 dbhandle = writehandle;
5481 char* core_class = osrfHashGet( class_meta, "classname" );
5482 char* pkey = osrfHashGet( class_meta, "primarykey" );
5484 const jsonObject* _tmp;
5486 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5488 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5493 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5495 dbi_result result = dbi_conn_query( dbhandle, sql );
5496 if( NULL == result ) {
5498 int errnum = dbi_conn_error( dbhandle, &msg );
5499 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5500 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5501 msg ? msg : "(No description available)" );
5502 if( !oilsIsDBConnected( dbhandle ))
5503 osrfAppSessionPanic( ctx->session );
5504 osrfAppSessionStatus(
5506 OSRF_STATUS_INTERNALSERVERERROR,
5507 "osrfMethodException",
5509 "Severe query error -- see error log for more details"
5516 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5519 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5520 jsonObject* row_obj = NULL;
5522 if( dbi_result_first_row( result )) {
5524 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5525 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5526 // eliminate the duplicates.
5527 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5528 osrfHash* dedup = osrfNewHash();
5530 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5531 char* pkey_val = oilsFMGetString( row_obj, pkey );
5532 if( osrfHashGet( dedup, pkey_val ) ) {
5533 jsonObjectFree( row_obj );
5536 osrfHashSet( dedup, pkey_val, pkey_val );
5537 jsonObjectPush( res_list, row_obj );
5539 } while( dbi_result_next_row( result ));
5540 osrfHashFree( dedup );
5543 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5547 /* clean up the query */
5548 dbi_result_free( result );
5551 // If we're asked to flesh, and there's anything to flesh, then flesh it
5552 // (but not for PCRUD, lest the user to bypass permissions by fleshing
5553 // something that he has no permission to look at).
5554 if( res_list->size && query_hash && ! enforce_pcrud ) {
5555 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5557 // Get the flesh depth
5558 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5559 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5560 flesh_depth = max_flesh_depth;
5562 // We need a non-zero flesh depth, and a list of fields to flesh
5563 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5564 if( temp_blob && flesh_depth > 0 ) {
5566 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5567 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5569 osrfStringArray* link_fields = NULL;
5570 osrfHash* links = osrfHashGet( class_meta, "links" );
5572 // Make an osrfStringArray of the names of fields to be fleshed
5573 if( flesh_fields ) {
5574 if( flesh_fields->size == 1 ) {
5575 const char* _t = jsonObjectGetString(
5576 jsonObjectGetIndex( flesh_fields, 0 ) );
5577 if( !strcmp( _t, "*" ))
5578 link_fields = osrfHashKeys( links );
5581 if( !link_fields ) {
5583 link_fields = osrfNewStringArray( 1 );
5584 jsonIterator* _i = jsonNewIterator( flesh_fields );
5585 while ((_f = jsonIteratorNext( _i ))) {
5586 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5588 jsonIteratorFree( _i );
5592 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5594 // Iterate over the JSON_ARRAY of rows
5596 unsigned long res_idx = 0;
5597 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5600 const char* link_field;
5602 // Iterate over the list of fleshable fields
5603 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5605 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5607 osrfHash* kid_link = osrfHashGet( links, link_field );
5609 continue; // Not a link field; skip it
5611 osrfHash* field = osrfHashGet( fields, link_field );
5613 continue; // Not a field at all; skip it (IDL is ill-formed)
5615 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5616 osrfHashGet( kid_link, "class" ));
5618 continue; // The class it links to doesn't exist; skip it
5620 const char* reltype = osrfHashGet( kid_link, "reltype" );
5622 continue; // No reltype; skip it (IDL is ill-formed)
5624 osrfHash* value_field = field;
5626 if( !strcmp( reltype, "has_many" )
5627 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5628 value_field = osrfHashGet(
5629 fields, osrfHashGet( class_meta, "primarykey" ) );
5632 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5634 if( link_map->size > 0 ) {
5635 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5638 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5643 osrfHashGet( kid_link, "class" ),
5650 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5651 osrfHashGet( kid_link, "field" ),
5652 osrfHashGet( kid_link, "class" ),
5653 osrfHashGet( kid_link, "key" ),
5654 osrfHashGet( kid_link, "reltype" )
5657 const char* search_key = jsonObjectGetString(
5658 jsonObjectGetIndex( cur,
5659 atoi( osrfHashGet( value_field, "array_position" ) )
5664 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5668 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5670 // construct WHERE clause
5671 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5674 osrfHashGet( kid_link, "key" ),
5675 jsonNewObject( search_key )
5678 // construct the rest of the query, mostly
5679 // by copying pieces of the previous level of query
5680 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5681 jsonObjectSetKey( rest_of_query, "flesh",
5682 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5686 jsonObjectSetKey( rest_of_query, "flesh_fields",
5687 jsonObjectClone( flesh_blob ));
5689 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5690 jsonObjectSetKey( rest_of_query, "order_by",
5691 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5695 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5696 jsonObjectSetKey( rest_of_query, "select",
5697 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5701 // do the query, recursively, to expand the fleshable field
5702 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5703 where_clause, rest_of_query, err );
5705 jsonObjectFree( where_clause );
5706 jsonObjectFree( rest_of_query );
5709 osrfStringArrayFree( link_fields );
5710 jsonObjectFree( res_list );
5711 jsonObjectFree( flesh_blob );
5715 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5716 osrfHashGet( kid_link, "class" ), kids->size );
5718 // Traverse the result set
5719 jsonObject* X = NULL;
5720 if( link_map->size > 0 && kids->size > 0 ) {
5722 kids = jsonNewObjectType( JSON_ARRAY );
5724 jsonObject* _k_node;
5725 unsigned long res_idx = 0;
5726 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5732 (unsigned long) atoi(
5738 osrfHashGet( kid_link, "class" )
5742 osrfStringArrayGetString( link_map, 0 )
5750 } // end while loop traversing X
5753 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5754 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5755 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5756 osrfHashGet( kid_link, "field" ));
5759 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5760 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5764 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5766 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5767 osrfHashGet( kid_link, "field" ) );
5770 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5771 jsonObjectClone( kids )
5776 jsonObjectFree( kids );
5780 jsonObjectFree( kids );
5782 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5783 osrfHashGet( kid_link, "field" ) );
5784 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5786 } // end while loop traversing list of fleshable fields
5787 } // end while loop traversing res_list
5788 jsonObjectFree( flesh_blob );
5789 osrfStringArrayFree( link_fields );
5798 int doUpdate( osrfMethodContext* ctx ) {
5799 if( osrfMethodVerifyContext( ctx )) {
5800 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5805 timeout_needs_resetting = 1;
5807 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5809 jsonObject* target = NULL;
5811 target = jsonObjectGetIndex( ctx->params, 1 );
5813 target = jsonObjectGetIndex( ctx->params, 0 );
5815 if(!verifyObjectClass( ctx, target )) {
5816 osrfAppRespondComplete( ctx, NULL );
5820 if( getXactId( ctx ) == NULL ) {
5821 osrfAppSessionStatus(
5823 OSRF_STATUS_BADREQUEST,
5824 "osrfMethodException",
5826 "No active transaction -- required for UPDATE"
5828 osrfAppRespondComplete( ctx, NULL );
5832 // The following test is harmless but redundant. If a class is
5833 // readonly, we don't register an update method for it.
5834 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5835 osrfAppSessionStatus(
5837 OSRF_STATUS_BADREQUEST,
5838 "osrfMethodException",
5840 "Cannot UPDATE readonly class"
5842 osrfAppRespondComplete( ctx, NULL );
5846 const char* trans_id = getXactId( ctx );
5848 // Set the last_xact_id
5849 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5851 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5852 trans_id, target->classname, index );
5853 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5856 char* pkey = osrfHashGet( meta, "primarykey" );
5857 osrfHash* fields = osrfHashGet( meta, "fields" );
5859 char* id = oilsFMGetString( target, pkey );
5863 "%s updating %s object with %s = %s",
5865 osrfHashGet( meta, "fieldmapper" ),
5870 dbhandle = writehandle;
5871 growing_buffer* sql = buffer_init( 128 );
5872 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5875 osrfHash* field_def = NULL;
5876 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5877 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5879 // Skip virtual fields, and the primary key
5880 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5883 const char* field_name = osrfHashIteratorKey( field_itr );
5884 if( ! strcmp( field_name, pkey ) )
5887 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5889 int value_is_numeric = 0; // boolean
5891 if( field_object && field_object->classname ) {
5892 value = oilsFMGetString(
5894 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5896 } else if( field_object && JSON_BOOL == field_object->type ) {
5897 if( jsonBoolIsTrue( field_object ) )
5898 value = strdup( "t" );
5900 value = strdup( "f" );
5902 value = jsonObjectToSimpleString( field_object );
5903 if( field_object && JSON_NUMBER == field_object->type )
5904 value_is_numeric = 1;
5907 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5908 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5910 if( !field_object || field_object->type == JSON_NULL ) {
5911 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5912 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5916 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5917 buffer_fadd( sql, " %s = NULL", field_name );
5920 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5924 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5926 const char* numtype = get_datatype( field_def );
5927 if( !strncmp( numtype, "INT", 3 ) ) {
5928 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5929 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5930 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5932 // Must really be intended as a string, so quote it
5933 if( dbi_conn_quote_string( dbhandle, &value )) {
5934 buffer_fadd( sql, " %s = %s", field_name, value );
5936 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5937 modulename, value );
5938 osrfAppSessionStatus(
5940 OSRF_STATUS_INTERNALSERVERERROR,
5941 "osrfMethodException",
5943 "Error quoting string -- please see the error log for more details"
5947 osrfHashIteratorFree( field_itr );
5949 osrfAppRespondComplete( ctx, NULL );
5954 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5957 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5961 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5962 buffer_fadd( sql, " %s = %s", field_name, value );
5964 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5965 osrfAppSessionStatus(
5967 OSRF_STATUS_INTERNALSERVERERROR,
5968 "osrfMethodException",
5970 "Error quoting string -- please see the error log for more details"
5974 osrfHashIteratorFree( field_itr );
5976 osrfAppRespondComplete( ctx, NULL );
5985 osrfHashIteratorFree( field_itr );
5987 jsonObject* obj = jsonNewObject( id );
5989 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5990 dbi_conn_quote_string( dbhandle, &id );
5992 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5994 char* query = buffer_release( sql );
5995 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5997 dbi_result result = dbi_conn_query( dbhandle, query );
6002 jsonObjectFree( obj );
6003 obj = jsonNewObject( NULL );
6005 int errnum = dbi_conn_error( dbhandle, &msg );
6008 "%s ERROR updating %s object with %s = %s: %d %s",
6010 osrfHashGet( meta, "fieldmapper" ),
6014 msg ? msg : "(No description available)"
6016 osrfAppSessionStatus(
6018 OSRF_STATUS_INTERNALSERVERERROR,
6019 "osrfMethodException",
6021 "Error in updating a row -- please see the error log for more details"
6023 if( !oilsIsDBConnected( dbhandle ))
6024 osrfAppSessionPanic( ctx->session );
6027 dbi_result_free( result );
6030 osrfAppRespondComplete( ctx, obj );
6031 jsonObjectFree( obj );
6035 int doDelete( osrfMethodContext* ctx ) {
6036 if( osrfMethodVerifyContext( ctx )) {
6037 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6042 timeout_needs_resetting = 1;
6044 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6046 if( getXactId( ctx ) == NULL ) {
6047 osrfAppSessionStatus(
6049 OSRF_STATUS_BADREQUEST,
6050 "osrfMethodException",
6052 "No active transaction -- required for DELETE"
6054 osrfAppRespondComplete( ctx, NULL );
6058 // The following test is harmless but redundant. If a class is
6059 // readonly, we don't register a delete method for it.
6060 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6061 osrfAppSessionStatus(
6063 OSRF_STATUS_BADREQUEST,
6064 "osrfMethodException",
6066 "Cannot DELETE readonly class"
6068 osrfAppRespondComplete( ctx, NULL );
6072 dbhandle = writehandle;
6074 char* pkey = osrfHashGet( meta, "primarykey" );
6081 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6082 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6083 osrfAppRespondComplete( ctx, NULL );
6087 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6089 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL, 1 )) {
6090 osrfAppRespondComplete( ctx, NULL );
6093 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6098 "%s deleting %s object with %s = %s",
6100 osrfHashGet( meta, "fieldmapper" ),
6105 jsonObject* obj = jsonNewObject( id );
6107 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6108 dbi_conn_quote_string( writehandle, &id );
6110 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6111 osrfHashGet( meta, "tablename" ), pkey, id );
6116 jsonObjectFree( obj );
6117 obj = jsonNewObject( NULL );
6119 int errnum = dbi_conn_error( writehandle, &msg );
6122 "%s ERROR deleting %s object with %s = %s: %d %s",
6124 osrfHashGet( meta, "fieldmapper" ),
6128 msg ? msg : "(No description available)"
6130 osrfAppSessionStatus(
6132 OSRF_STATUS_INTERNALSERVERERROR,
6133 "osrfMethodException",
6135 "Error in deleting a row -- please see the error log for more details"
6137 if( !oilsIsDBConnected( writehandle ))
6138 osrfAppSessionPanic( ctx->session );
6140 dbi_result_free( result );
6144 osrfAppRespondComplete( ctx, obj );
6145 jsonObjectFree( obj );
6150 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6151 @param result An iterator for a result set; we only look at the current row.
6152 @param @meta Pointer to the class metadata for the core class.
6153 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6155 If a column is not defined in the IDL, or if it has no array_position defined for it in
6156 the IDL, or if it is defined as virtual, ignore it.
6158 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6159 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6160 array_position in the IDL.
6162 A field defined in the IDL but not represented in the returned row will leave a hole
6163 in the JSON_ARRAY. In effect it will be treated as a null value.
6165 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6166 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6167 classname corresponding to the @a meta argument.
6169 The calling code is responsible for freeing the the resulting jsonObject by calling
6172 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6173 if( !( result && meta )) return NULL;
6175 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6176 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6177 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6179 osrfHash* fields = osrfHashGet( meta, "fields" );
6181 int columnIndex = 1;
6182 const char* columnName;
6184 /* cycle through the columns in the row returned from the database */
6185 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6187 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6189 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6191 /* determine the field type and storage attributes */
6192 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6193 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6195 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6196 // or if it has no sequence number there, or if it's virtual, skip it.
6197 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6200 if( str_is_true( osrfHashGet( _f, "virtual" )))
6201 continue; // skip this column: IDL says it's virtual
6203 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6204 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6205 continue; // since we assign sequence numbers dynamically as we load the IDL.
6207 fmIndex = atoi( pos );
6208 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6210 continue; // This field is not defined in the IDL
6213 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6214 // sequence number from the IDL (which is likely to be different from the sequence
6215 // of columns in the SELECT clause).
6216 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6217 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6222 case DBI_TYPE_INTEGER :
6224 if( attr & DBI_INTEGER_SIZE8 )
6225 jsonObjectSetIndex( object, fmIndex,
6226 jsonNewNumberObject(
6227 dbi_result_get_longlong_idx( result, columnIndex )));
6229 jsonObjectSetIndex( object, fmIndex,
6230 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6234 case DBI_TYPE_DECIMAL :
6235 jsonObjectSetIndex( object, fmIndex,
6236 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6239 case DBI_TYPE_STRING :
6244 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6249 case DBI_TYPE_DATETIME : {
6251 char dt_string[ 256 ] = "";
6254 // Fetch the date column as a time_t
6255 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6257 // Translate the time_t to a human-readable string
6258 if( !( attr & DBI_DATETIME_DATE )) {
6259 gmtime_r( &_tmp_dt, &gmdt );
6260 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6261 } else if( !( attr & DBI_DATETIME_TIME )) {
6262 localtime_r( &_tmp_dt, &gmdt );
6263 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6265 localtime_r( &_tmp_dt, &gmdt );
6266 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6269 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6273 case DBI_TYPE_BINARY :
6274 osrfLogError( OSRF_LOG_MARK,
6275 "Can't do binary at column %s : index %d", columnName, columnIndex );
6284 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6285 if( !result ) return NULL;
6287 jsonObject* object = jsonNewObject( NULL );
6290 char dt_string[ 256 ];
6294 int columnIndex = 1;
6296 unsigned short type;
6297 const char* columnName;
6299 /* cycle through the column list */
6300 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6302 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6304 fmIndex = -1; // reset the position
6306 /* determine the field type and storage attributes */
6307 type = dbi_result_get_field_type_idx( result, columnIndex );
6308 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6310 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6311 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6316 case DBI_TYPE_INTEGER :
6318 if( attr & DBI_INTEGER_SIZE8 )
6319 jsonObjectSetKey( object, columnName,
6320 jsonNewNumberObject( dbi_result_get_longlong_idx(
6321 result, columnIndex )) );
6323 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6324 dbi_result_get_int_idx( result, columnIndex )) );
6327 case DBI_TYPE_DECIMAL :
6328 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6329 dbi_result_get_double_idx( result, columnIndex )) );
6332 case DBI_TYPE_STRING :
6333 jsonObjectSetKey( object, columnName,
6334 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6337 case DBI_TYPE_DATETIME :
6339 memset( dt_string, '\0', sizeof( dt_string ));
6340 memset( &gmdt, '\0', sizeof( gmdt ));
6342 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6344 if( !( attr & DBI_DATETIME_DATE )) {
6345 gmtime_r( &_tmp_dt, &gmdt );
6346 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6347 } else if( !( attr & DBI_DATETIME_TIME )) {
6348 localtime_r( &_tmp_dt, &gmdt );
6349 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6351 localtime_r( &_tmp_dt, &gmdt );
6352 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6355 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6358 case DBI_TYPE_BINARY :
6359 osrfLogError( OSRF_LOG_MARK,
6360 "Can't do binary at column %s : index %d", columnName, columnIndex );
6364 } // end while loop traversing result
6369 // Interpret a string as true or false
6370 int str_is_true( const char* str ) {
6371 if( NULL == str || strcasecmp( str, "true" ) )
6377 // Interpret a jsonObject as true or false
6378 static int obj_is_true( const jsonObject* obj ) {
6381 else switch( obj->type )
6389 if( strcasecmp( obj->value.s, "true" ) )
6393 case JSON_NUMBER : // Support 1/0 for perl's sake
6394 if( jsonObjectGetNumber( obj ) == 1.0 )
6403 // Translate a numeric code into a text string identifying a type of
6404 // jsonObject. To be used for building error messages.
6405 static const char* json_type( int code ) {
6411 return "JSON_ARRAY";
6413 return "JSON_STRING";
6415 return "JSON_NUMBER";
6421 return "(unrecognized)";
6425 // Extract the "primitive" attribute from an IDL field definition.
6426 // If we haven't initialized the app, then we must be running in
6427 // some kind of testbed. In that case, default to "string".
6428 static const char* get_primitive( osrfHash* field ) {
6429 const char* s = osrfHashGet( field, "primitive" );
6431 if( child_initialized )
6434 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6436 osrfHashGet( field, "name" )
6444 // Extract the "datatype" attribute from an IDL field definition.
6445 // If we haven't initialized the app, then we must be running in
6446 // some kind of testbed. In that case, default to to NUMERIC,
6447 // since we look at the datatype only for numbers.
6448 static const char* get_datatype( osrfHash* field ) {
6449 const char* s = osrfHashGet( field, "datatype" );
6451 if( child_initialized )
6454 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6456 osrfHashGet( field, "name" )
6465 @brief Determine whether a string is potentially a valid SQL identifier.
6466 @param s The identifier to be tested.
6467 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6469 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6470 need to follow all the rules exactly, such as requiring that the first character not
6473 We allow leading and trailing white space. In between, we do not allow punctuation
6474 (except for underscores and dollar signs), control characters, or embedded white space.
6476 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6477 for the foreseeable future such quoted identifiers are not likely to be an issue.
6479 int is_identifier( const char* s) {
6483 // Skip leading white space
6484 while( isspace( (unsigned char) *s ) )
6488 return 0; // Nothing but white space? Not okay.
6490 // Check each character until we reach white space or
6491 // end-of-string. Letters, digits, underscores, and
6492 // dollar signs are okay. With the exception of periods
6493 // (as in schema.identifier), control characters and other
6494 // punctuation characters are not okay. Anything else
6495 // is okay -- it could for example be part of a multibyte
6496 // UTF8 character such as a letter with diacritical marks,
6497 // and those are allowed.
6499 if( isalnum( (unsigned char) *s )
6503 ; // Fine; keep going
6504 else if( ispunct( (unsigned char) *s )
6505 || iscntrl( (unsigned char) *s ) )
6508 } while( *s && ! isspace( (unsigned char) *s ) );
6510 // If we found any white space in the above loop,
6511 // the rest had better be all white space.
6513 while( isspace( (unsigned char) *s ) )
6517 return 0; // White space was embedded within non-white space
6523 @brief Determine whether to accept a character string as a comparison operator.
6524 @param op The candidate comparison operator.
6525 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6527 We don't validate the operator for real. We just make sure that it doesn't contain
6528 any semicolons or white space (with special exceptions for a few specific operators).
6529 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6530 space but it's still not a valid operator, then the database will complain.
6532 Another approach would be to compare the string against a short list of approved operators.
6533 We don't do that because we want to allow custom operators like ">100*", which at this
6534 writing would be difficult or impossible to express otherwise in a JSON query.
6536 int is_good_operator( const char* op ) {
6537 if( !op ) return 0; // Sanity check
6541 if( isspace( (unsigned char) *s ) ) {
6542 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6543 // and IS NOT DISTINCT FROM.
6544 if( !strcasecmp( op, "similar to" ) )
6546 else if( !strcasecmp( op, "is distinct from" ) )
6548 else if( !strcasecmp( op, "is not distinct from" ) )
6553 else if( ';' == *s )
6561 @name Query Frame Management
6563 The following machinery supports a stack of query frames for use by SELECT().
6565 A query frame caches information about one level of a SELECT query. When we enter
6566 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6568 The query frame stores information about the core class, and about any joined classes
6571 The main purpose is to map table aliases to classes and tables, so that a query can
6572 join to the same table more than once. A secondary goal is to reduce the number of
6573 lookups in the IDL by caching the results.
6577 #define STATIC_CLASS_INFO_COUNT 3
6579 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6582 @brief Allocate a ClassInfo as raw memory.
6583 @return Pointer to the newly allocated ClassInfo.
6585 Except for the in_use flag, which is used only by the allocation and deallocation
6586 logic, we don't initialize the ClassInfo here.
6588 static ClassInfo* allocate_class_info( void ) {
6589 // In order to reduce the number of mallocs and frees, we return a static
6590 // instance of ClassInfo, if we can find one that we're not already using.
6591 // We rely on the fact that the compiler will implicitly initialize the
6592 // static instances so that in_use == 0.
6595 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6596 if( ! static_class_info[ i ].in_use ) {
6597 static_class_info[ i ].in_use = 1;
6598 return static_class_info + i;
6602 // The static ones are all in use. Malloc one.
6604 return safe_malloc( sizeof( ClassInfo ) );
6608 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6609 @param info Pointer to the ClassInfo to be cleared.
6611 static void clear_class_info( ClassInfo* info ) {
6616 // Free any malloc'd strings
6618 if( info->alias != info->alias_store )
6619 free( info->alias );
6621 if( info->class_name != info->class_name_store )
6622 free( info->class_name );
6624 free( info->source_def );
6626 info->alias = info->class_name = info->source_def = NULL;
6631 @brief Free a ClassInfo and everything it owns.
6632 @param info Pointer to the ClassInfo to be freed.
6634 static void free_class_info( ClassInfo* info ) {
6639 clear_class_info( info );
6641 // If it's one of the static instances, just mark it as not in use
6644 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6645 if( info == static_class_info + i ) {
6646 static_class_info[ i ].in_use = 0;
6651 // Otherwise it must have been malloc'd, so free it
6657 @brief Populate an already-allocated ClassInfo.
6658 @param info Pointer to the ClassInfo to be populated.
6659 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6661 @param class Name of the class.
6662 @return Zero if successful, or 1 if not.
6664 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6665 the relevant portions of the IDL for the specified class.
6667 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6670 osrfLogError( OSRF_LOG_MARK,
6671 "%s ERROR: No ClassInfo available to populate", modulename );
6672 info->alias = info->class_name = info->source_def = NULL;
6673 info->class_def = info->fields = info->links = NULL;
6678 osrfLogError( OSRF_LOG_MARK,
6679 "%s ERROR: No class name provided for lookup", modulename );
6680 info->alias = info->class_name = info->source_def = NULL;
6681 info->class_def = info->fields = info->links = NULL;
6685 // Alias defaults to class name if not supplied
6686 if( ! alias || ! alias[ 0 ] )
6689 // Look up class info in the IDL
6690 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6692 osrfLogError( OSRF_LOG_MARK,
6693 "%s ERROR: Class %s not defined in IDL", modulename, class );
6694 info->alias = info->class_name = info->source_def = NULL;
6695 info->class_def = info->fields = info->links = NULL;
6697 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6698 osrfLogError( OSRF_LOG_MARK,
6699 "%s ERROR: Class %s is defined as virtual", modulename, class );
6700 info->alias = info->class_name = info->source_def = NULL;
6701 info->class_def = info->fields = info->links = NULL;
6705 osrfHash* links = osrfHashGet( class_def, "links" );
6707 osrfLogError( OSRF_LOG_MARK,
6708 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6709 info->alias = info->class_name = info->source_def = NULL;
6710 info->class_def = info->fields = info->links = NULL;
6714 osrfHash* fields = osrfHashGet( class_def, "fields" );
6716 osrfLogError( OSRF_LOG_MARK,
6717 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6718 info->alias = info->class_name = info->source_def = NULL;
6719 info->class_def = info->fields = info->links = NULL;
6723 char* source_def = oilsGetRelation( class_def );
6727 // We got everything we need, so populate the ClassInfo
6728 if( strlen( alias ) > ALIAS_STORE_SIZE )
6729 info->alias = strdup( alias );
6731 strcpy( info->alias_store, alias );
6732 info->alias = info->alias_store;
6735 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6736 info->class_name = strdup( class );
6738 strcpy( info->class_name_store, class );
6739 info->class_name = info->class_name_store;
6742 info->source_def = source_def;
6744 info->class_def = class_def;
6745 info->links = links;
6746 info->fields = fields;
6751 #define STATIC_FRAME_COUNT 3
6753 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6756 @brief Allocate a QueryFrame as raw memory.
6757 @return Pointer to the newly allocated QueryFrame.
6759 Except for the in_use flag, which is used only by the allocation and deallocation
6760 logic, we don't initialize the QueryFrame here.
6762 static QueryFrame* allocate_frame( void ) {
6763 // In order to reduce the number of mallocs and frees, we return a static
6764 // instance of QueryFrame, if we can find one that we're not already using.
6765 // We rely on the fact that the compiler will implicitly initialize the
6766 // static instances so that in_use == 0.
6769 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6770 if( ! static_frame[ i ].in_use ) {
6771 static_frame[ i ].in_use = 1;
6772 return static_frame + i;
6776 // The static ones are all in use. Malloc one.
6778 return safe_malloc( sizeof( QueryFrame ) );
6782 @brief Free a QueryFrame, and all the memory it owns.
6783 @param frame Pointer to the QueryFrame to be freed.
6785 static void free_query_frame( QueryFrame* frame ) {
6790 clear_class_info( &frame->core );
6792 // Free the join list
6794 ClassInfo* info = frame->join_list;
6797 free_class_info( info );
6801 frame->join_list = NULL;
6804 // If the frame is a static instance, just mark it as unused
6806 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6807 if( frame == static_frame + i ) {
6808 static_frame[ i ].in_use = 0;
6813 // Otherwise it must have been malloc'd, so free it
6819 @brief Search a given QueryFrame for a specified alias.
6820 @param frame Pointer to the QueryFrame to be searched.
6821 @param target The alias for which to search.
6822 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6824 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6825 if( ! frame || ! target ) {
6829 ClassInfo* found_class = NULL;
6831 if( !strcmp( target, frame->core.alias ) )
6832 return &(frame->core);
6834 ClassInfo* curr_class = frame->join_list;
6835 while( curr_class ) {
6836 if( strcmp( target, curr_class->alias ) )
6837 curr_class = curr_class->next;
6839 found_class = curr_class;
6849 @brief Push a new (blank) QueryFrame onto the stack.
6851 static void push_query_frame( void ) {
6852 QueryFrame* frame = allocate_frame();
6853 frame->join_list = NULL;
6854 frame->next = curr_query;
6856 // Initialize the ClassInfo for the core class
6857 ClassInfo* core = &frame->core;
6858 core->alias = core->class_name = core->source_def = NULL;
6859 core->class_def = core->fields = core->links = NULL;
6865 @brief Pop a QueryFrame off the stack and destroy it.
6867 static void pop_query_frame( void ) {
6872 QueryFrame* popped = curr_query;
6873 curr_query = popped->next;
6875 free_query_frame( popped );
6879 @brief Populate the ClassInfo for the core class.
6880 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6881 class name as an alias.
6882 @param class_name Name of the core class.
6883 @return Zero if successful, or 1 if not.
6885 Populate the ClassInfo of the core class with copies of the alias and class name, and
6886 with pointers to the relevant portions of the IDL for the core class.
6888 static int add_query_core( const char* alias, const char* class_name ) {
6891 if( ! curr_query ) {
6892 osrfLogError( OSRF_LOG_MARK,
6893 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6895 } else if( curr_query->core.alias ) {
6896 osrfLogError( OSRF_LOG_MARK,
6897 "%s ERROR: Core class %s already populated as %s",
6898 modulename, curr_query->core.class_name, curr_query->core.alias );
6902 build_class_info( &curr_query->core, alias, class_name );
6903 if( curr_query->core.alias )
6906 osrfLogError( OSRF_LOG_MARK,
6907 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6913 @brief Search the current QueryFrame for a specified alias.
6914 @param target The alias for which to search.
6915 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6917 static inline ClassInfo* search_alias( const char* target ) {
6918 return search_alias_in_frame( curr_query, target );
6922 @brief Search all levels of query for a specified alias, starting with the current query.
6923 @param target The alias for which to search.
6924 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6926 static ClassInfo* search_all_alias( const char* target ) {
6927 ClassInfo* found_class = NULL;
6928 QueryFrame* curr_frame = curr_query;
6930 while( curr_frame ) {
6931 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6934 curr_frame = curr_frame->next;
6941 @brief Add a class to the list of classes joined to the current query.
6942 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6943 the class name as an alias.
6944 @param classname The name of the class to be added.
6945 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6947 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6949 if( ! classname || ! *classname ) { // sanity check
6950 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6957 const ClassInfo* conflict = search_alias( alias );
6959 osrfLogError( OSRF_LOG_MARK,
6960 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6961 modulename, alias, conflict->class_name );
6965 ClassInfo* info = allocate_class_info();
6967 if( build_class_info( info, alias, classname ) ) {
6968 free_class_info( info );
6972 // Add the new ClassInfo to the join list of the current QueryFrame
6973 info->next = curr_query->join_list;
6974 curr_query->join_list = info;
6980 @brief Destroy all nodes on the query stack.
6982 static void clear_query_stack( void ) {