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 if( !where_clause ) {
1187 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1191 // Get the class metadata
1192 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1193 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1197 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1199 osrfAppRespondComplete( ctx, NULL );
1203 // Return each row to the client (except that some may be suppressed by PCRUD)
1204 jsonObject* cur = 0;
1205 unsigned long res_idx = 0;
1206 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1207 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur, obj->size ))
1209 osrfAppRespond( ctx, cur );
1211 jsonObjectFree( obj );
1213 osrfAppRespondComplete( ctx, NULL );
1218 @brief Implement the "id_list" method.
1219 @param ctx Pointer to the method context.
1220 @param err Pointer through which to return an error code.
1221 @return Zero if successful, or -1 if not.
1224 - authkey (PCRUD only)
1225 - WHERE clause, as jsonObject
1226 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1228 Return to client: The primary key values for all rows of the relevant class that
1229 satisfy a specified WHERE clause.
1231 This method relies on the assumption that every class has a primary key consisting of
1234 int doIdList( osrfMethodContext* ctx ) {
1235 if( osrfMethodVerifyContext( ctx )) {
1236 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1241 timeout_needs_resetting = 1;
1243 jsonObject* where_clause;
1244 jsonObject* rest_of_query;
1246 // We use the where clause without change. But we need to massage the rest of the
1247 // query, so we work with a copy of it instead of modifying the original.
1249 if( enforce_pcrud ) {
1250 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1251 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1253 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1254 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1257 if( !where_clause ) {
1258 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1262 // Eliminate certain SQL clauses, if present.
1263 if( rest_of_query ) {
1264 jsonObjectRemoveKey( rest_of_query, "select" );
1265 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1266 jsonObjectRemoveKey( rest_of_query, "flesh" );
1267 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1269 rest_of_query = jsonNewObjectType( JSON_HASH );
1272 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1274 // Get the class metadata
1275 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1276 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1278 // Build a SELECT list containing just the primary key,
1279 // i.e. like { "classname":["keyname"] }
1280 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1282 // Load array with name of primary key
1283 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1284 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1285 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1287 jsonObjectSetKey( rest_of_query, "select", select_clause );
1292 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1294 jsonObjectFree( rest_of_query );
1296 osrfAppRespondComplete( ctx, NULL );
1300 // Return each primary key value to the client
1302 unsigned long res_idx = 0;
1303 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1304 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur, obj->size ))
1305 continue; // Suppress due to lack of permission
1307 osrfAppRespond( ctx,
1308 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1311 jsonObjectFree( obj );
1312 osrfAppRespondComplete( ctx, NULL );
1317 @brief Verify that we have a valid class reference.
1318 @param ctx Pointer to the method context.
1319 @param param Pointer to the method parameters.
1320 @return 1 if the class reference is valid, or zero if it isn't.
1322 The class of the method params must match the class to which the method id devoted.
1323 For PCRUD there are additional restrictions.
1325 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1327 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1328 osrfHash* class = osrfHashGet( method_meta, "class" );
1330 // Compare the method's class to the parameters' class
1331 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1333 // Oops -- they don't match. Complain.
1334 growing_buffer* msg = buffer_init( 128 );
1337 "%s: %s method for type %s was passed a %s",
1339 osrfHashGet( method_meta, "methodtype" ),
1340 osrfHashGet( class, "classname" ),
1341 param->classname ? param->classname : "(null)"
1344 char* m = buffer_release( msg );
1345 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1353 return verifyObjectPCRUD( ctx, param, 1 );
1359 @brief (PCRUD only) Verify that the user is properly logged in.
1360 @param ctx Pointer to the method context.
1361 @return If the user is logged in, a pointer to the user object from the authentication
1362 server; otherwise NULL.
1364 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1366 // Get the authkey (the first method parameter)
1367 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1369 // See if we have the same authkey, and a user object,
1370 // locally cached from a previous call
1371 const char* cached_authkey = getAuthkey( ctx );
1372 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1373 const jsonObject* cached_user = getUserLogin( ctx );
1378 // We have no matching authentication data in the cache. Authenticate from scratch.
1379 jsonObject* auth_object = jsonNewObject( auth );
1381 // Fetch the user object from the authentication server
1382 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1384 jsonObjectFree( auth_object );
1386 if( !user->classname || strcmp(user->classname, "au" )) {
1388 growing_buffer* msg = buffer_init( 128 );
1391 "%s: permacrud received a bad auth token: %s",
1396 char* m = buffer_release( msg );
1397 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1401 jsonObjectFree( user );
1405 setUserLogin( ctx, user );
1406 setAuthkey( ctx, auth );
1408 // Allow ourselves up to a second before we have to reset the login timeout.
1409 // It would be nice to use some fraction of the timeout interval enforced by the
1410 // authentication server, but that value is not readily available at this point.
1411 // Instead, we use a conservative default interval.
1412 time_next_reset = time( NULL ) + 1;
1418 @brief For PCRUD: Determine whether the current user may access the current row.
1419 @param ctx Pointer to the method context.
1420 @param obj Pointer to the row being potentially accessed.
1421 @return 1 if access is permitted, or 0 if it isn't.
1423 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1425 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj, const int rs_size ) {
1427 dbhandle = writehandle;
1429 // Figure out what class and method are involved
1430 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1431 osrfHash* class = osrfHashGet( method_metadata, "class" );
1432 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1434 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1435 // contexts we will do another lookup of the current row, even if we already have a
1436 // previously fetched row image, because the row image in hand may not include the
1437 // foreign key(s) that we need.
1439 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1440 // but they aren't implemented yet.
1443 if( *method_type == 's' || *method_type == 'i' ) {
1444 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1446 } else if( *method_type == 'u' || *method_type == 'd' ) {
1447 fetch = 1; // MUST go to the db for the object for update and delete
1450 // Get the appropriate permacrud entry from the IDL, depending on method type
1451 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1453 // No permacrud for this method type on this class
1455 growing_buffer* msg = buffer_init( 128 );
1458 "%s: %s on class %s has no permacrud IDL entry",
1460 osrfHashGet( method_metadata, "methodtype" ),
1461 osrfHashGet( class, "classname" )
1464 char* m = buffer_release( msg );
1465 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1466 "osrfMethodException", ctx->request, m );
1473 // Get the user id, and make sure the user is logged in
1474 const jsonObject* user = verifyUserPCRUD( ctx );
1476 return 0; // Not logged in? No access.
1478 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1480 // Get a list of permissions from the permacrud entry.
1481 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1482 if( permission->size == 0 ) {
1483 osrfLogDebug( OSRF_LOG_MARK, "No permissions required for this action, passing through" );
1487 // Build a list of org units that own the row. This is fairly convoluted because there
1488 // are several different ways that an org unit may own the row, as defined by the
1491 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1492 // identifying an owning org_unit..
1493 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1495 // Foreign context adds a layer of indirection. The row points to some other row that
1496 // an org unit may own. The "jump" attribute, if present, adds another layer of
1498 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1500 // The following string array stores the list of org units. (We don't have a thingie
1501 // for storing lists of integers, so we fake it with a list of strings.)
1502 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1505 const char* pkey_value = NULL;
1506 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1507 // If the global_required attribute is present and true, then the only owning
1508 // org unit is the root org unit, i.e. the one with no parent.
1509 osrfLogDebug( OSRF_LOG_MARK,
1510 "global-level permissions required, fetching top of the org tree" );
1512 // check for perm at top of org tree
1513 const char* org_tree_root_id = org_tree_root( ctx );
1514 if( org_tree_root_id ) {
1515 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1516 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1518 osrfStringArrayFree( context_org_array );
1523 // If the global_required attribute is absent or false, then we look for
1524 // local and/or foreign context. In order to find the relevant foreign
1525 // keys, we must either read the relevant row from the database, or look at
1526 // the image of the row that we already have in memory.
1528 // Even if we have an image of the row in memory, that image may not include the
1529 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1530 // of the row to make sure that we have what we need.
1532 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1533 "fetching context org ids" );
1534 const char* pkey = osrfHashGet( class, "primarykey" );
1535 jsonObject *param = NULL;
1538 // There is no primary key, so we can't do a fresh lookup. Use the row
1539 // image that we already have. If it doesn't have everything we need, too bad.
1541 param = jsonObjectClone( obj );
1542 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1543 } else if( obj->classname ) {
1544 pkey_value = oilsFMGetStringConst( obj, pkey );
1546 param = jsonObjectClone( obj );
1547 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1550 pkey_value = jsonObjectGetString( obj );
1552 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1553 "of %s and retrieving from the database", pkey_value );
1557 // Fetch the row so that we can look at the foreign key(s)
1558 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1559 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1560 jsonObjectFree( _tmp_params );
1562 param = jsonObjectExtractIndex( _list, 0 );
1563 jsonObjectFree( _list );
1567 // The row doesn't exist. Complain, and deny access.
1568 osrfLogDebug( OSRF_LOG_MARK,
1569 "Object not found in the database with primary key %s of %s",
1572 growing_buffer* msg = buffer_init( 128 );
1575 "%s: no object found with primary key %s of %s",
1581 char* m = buffer_release( msg );
1582 osrfAppSessionStatus(
1584 OSRF_STATUS_INTERNALSERVERERROR,
1585 "osrfMethodException",
1594 if( local_context && local_context->size > 0 ) {
1595 // The IDL provides a list of column names for the foreign keys denoting
1596 // local context, i.e. columns identifying owing org units directly. Look up
1597 // the value of each one, and if it isn't null, add it to the list of org units.
1598 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1599 local_context->size );
1601 const char* lcontext = NULL;
1602 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1603 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1604 if( fkey_value ) { // if not null
1605 osrfStringArrayAdd( context_org_array, fkey_value );
1608 "adding class-local field %s (value: %s) to the context org list",
1610 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1616 if( foreign_context ) {
1617 unsigned long class_count = osrfHashGetCount( foreign_context );
1618 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1620 if( class_count > 0 ) {
1622 // The IDL provides a list of foreign key columns pointing to rows that
1623 // an org unit may own. Follow each link, identify the owning org unit,
1624 // and add it to the list.
1625 osrfHash* fcontext = NULL;
1626 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1627 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1628 // For each class to which a foreign key points:
1629 const char* class_name = osrfHashIteratorKey( class_itr );
1630 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1634 "%d foreign context fields(s) specified for class %s",
1635 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1639 // Get the name of the key field in the foreign table
1640 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1642 // Get the value of the foreign key pointing to the foreign table
1643 char* foreign_pkey_value =
1644 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1645 if( !foreign_pkey_value )
1646 continue; // Foreign key value is null; skip it
1648 // Look up the row to which the foreign key points
1649 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1650 jsonObject* _list = doFieldmapperSearch(
1651 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1653 jsonObject* _fparam = NULL;
1654 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1655 _fparam = jsonObjectExtractIndex( _list, 0 );
1657 jsonObjectFree( _tmp_params );
1658 jsonObjectFree( _list );
1660 // At this point _fparam either points to the row identified by the
1661 // foreign key, or it's NULL (no such row found).
1663 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1665 const char* bad_class = NULL; // For noting failed lookups
1667 bad_class = class_name; // Referenced row not found
1668 else if( jump_list ) {
1669 // Follow a chain of rows, linked by foreign keys, to find an owner
1670 const char* flink = NULL;
1672 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1673 // For each entry in the jump list. Each entry (i.e. flink) is
1674 // the name of a foreign key column in the current row.
1676 // From the IDL, get the linkage information for the next jump
1677 osrfHash* foreign_link_hash =
1678 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1680 // Get the class metadata for the class
1681 // to which the foreign key points
1682 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1683 osrfHashGet( foreign_link_hash, "class" ));
1685 // Get the name of the referenced key of that class
1686 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1688 // Get the value of the foreign key pointing to that class
1689 free( foreign_pkey_value );
1690 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1691 if( !foreign_pkey_value )
1692 break; // Foreign key is null; quit looking
1694 // Build a WHERE clause for the lookup
1695 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1698 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1699 _tmp_params, NULL, &err );
1701 // Get the resulting row
1702 jsonObjectFree( _fparam );
1703 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1704 _fparam = jsonObjectExtractIndex( _list, 0 );
1706 // Referenced row not found
1708 bad_class = osrfHashGet( foreign_link_hash, "class" );
1711 jsonObjectFree( _tmp_params );
1712 jsonObjectFree( _list );
1718 // We had a foreign key pointing to such-and-such a row, but then
1719 // we couldn't fetch that row. The data in the database are in an
1720 // inconsistent state; the database itself may even be corrupted.
1721 growing_buffer* msg = buffer_init( 128 );
1724 "%s: no object of class %s found with primary key %s of %s",
1728 foreign_pkey_value ? foreign_pkey_value : "(null)"
1731 char* m = buffer_release( msg );
1732 osrfAppSessionStatus(
1734 OSRF_STATUS_INTERNALSERVERERROR,
1735 "osrfMethodException",
1741 osrfHashIteratorFree( class_itr );
1742 free( foreign_pkey_value );
1743 jsonObjectFree( param );
1748 free( foreign_pkey_value );
1751 // Examine each context column of the foreign row,
1752 // and add its value to the list of org units.
1754 const char* foreign_field = NULL;
1755 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1756 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1757 osrfStringArrayAdd( context_org_array,
1758 oilsFMGetStringConst( _fparam, foreign_field ));
1759 osrfLogDebug( OSRF_LOG_MARK,
1760 "adding foreign class %s field %s (value: %s) "
1761 "to the context org list",
1764 osrfStringArrayGetString(
1765 context_org_array, context_org_array->size - 1 )
1769 jsonObjectFree( _fparam );
1773 osrfHashIteratorFree( class_itr );
1777 jsonObjectFree( param );
1780 const char* context_org = NULL;
1781 const char* perm = NULL;
1784 // For every combination of permission and context org unit: call a stored procedure
1785 // to determine if the user has this permission in the context of this org unit.
1786 // If the answer is yes at any point, then we're done, and the user has permission.
1787 // In other words permissions are additive.
1789 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1792 osrfStringArray* pcache = NULL;
1793 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
1794 pcache = getPermLocationCache(ctx, perm);
1797 pcache = osrfNewStringArray(0);
1799 result = dbi_conn_queryf(
1801 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
1809 "Received a result for permission [%s] for user %d",
1814 if( dbi_result_first_row( result )) {
1816 jsonObject* return_val = oilsMakeJSONFromResult( result );
1817 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
1818 jsonObjectFree( return_val );
1819 } while( dbi_result_next_row( result ));
1821 setPermLocationCache(ctx, perm, pcache);
1824 dbi_result_free( result );
1830 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1832 if (rs_size > perm_at_threshold) {
1833 if (osrfStringArrayContains( pcache, context_org )) {
1842 "Checking object permission [%s] for user %d "
1843 "on object %s (class %s) at org %d",
1847 osrfHashGet( class, "classname" ),
1851 result = dbi_conn_queryf(
1853 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1856 osrfHashGet( class, "classname" ),
1864 "Received a result for object permission [%s] "
1865 "for user %d on object %s (class %s) at org %d",
1869 osrfHashGet( class, "classname" ),
1873 if( dbi_result_first_row( result )) {
1874 jsonObject* return_val = oilsMakeJSONFromResult( result );
1875 const char* has_perm = jsonObjectGetString(
1876 jsonObjectGetKeyConst( return_val, "has_perm" ));
1880 "Status of object permission [%s] for user %d "
1881 "on object %s (class %s) at org %d is %s",
1885 osrfHashGet(class, "classname"),
1890 if( *has_perm == 't' )
1892 jsonObjectFree( return_val );
1895 dbi_result_free( result );
1900 int errnum = dbi_conn_error( writehandle, &msg );
1901 osrfLogWarning( OSRF_LOG_MARK,
1902 "Unable to call check object permissions: %d, %s",
1903 errnum, msg ? msg : "(No description available)" );
1904 if( !oilsIsDBConnected( writehandle ))
1905 osrfAppSessionPanic( ctx->session );
1909 if (rs_size > perm_at_threshold) break;
1911 osrfLogDebug( OSRF_LOG_MARK,
1912 "Checking non-object permission [%s] for user %d at org %d",
1913 perm, userid, atoi(context_org) );
1914 result = dbi_conn_queryf(
1916 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1923 osrfLogDebug( OSRF_LOG_MARK,
1924 "Received a result for permission [%s] for user %d at org %d",
1925 perm, userid, atoi( context_org ));
1926 if( dbi_result_first_row( result )) {
1927 jsonObject* return_val = oilsMakeJSONFromResult( result );
1928 const char* has_perm = jsonObjectGetString(
1929 jsonObjectGetKeyConst( return_val, "has_perm" ));
1930 osrfLogDebug( OSRF_LOG_MARK,
1931 "Status of permission [%s] for user %d at org %d is [%s]",
1932 perm, userid, atoi( context_org ), has_perm );
1933 if( *has_perm == 't' )
1935 jsonObjectFree( return_val );
1938 dbi_result_free( result );
1943 int errnum = dbi_conn_error( writehandle, &msg );
1944 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
1945 errnum, msg ? msg : "(No description available)" );
1946 if( !oilsIsDBConnected( writehandle ))
1947 osrfAppSessionPanic( ctx->session );
1956 osrfStringArrayFree( context_org_array );
1962 @brief Look up the root of the org_unit tree.
1963 @param ctx Pointer to the method context.
1964 @return The id of the root org unit, as a character string.
1966 Query actor.org_unit where parent_ou is null, and return the id as a string.
1968 This function assumes that there is only one root org unit, i.e. that we
1969 have a single tree, not a forest.
1971 The calling code is responsible for freeing the returned string.
1973 static const char* org_tree_root( osrfMethodContext* ctx ) {
1975 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1976 static time_t last_lookup_time = 0;
1977 time_t current_time = time( NULL );
1979 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1980 // We successfully looked this up less than an hour ago.
1981 // It's not likely to have changed since then.
1982 return strdup( cached_root_id );
1984 last_lookup_time = current_time;
1987 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1988 jsonObject* result = doFieldmapperSearch(
1989 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1990 jsonObjectFree( where_clause );
1992 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1995 jsonObjectFree( result );
1997 growing_buffer* msg = buffer_init( 128 );
1998 OSRF_BUFFER_ADD( msg, modulename );
1999 OSRF_BUFFER_ADD( msg,
2000 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2002 char* m = buffer_release( msg );
2003 osrfAppSessionStatus( ctx->session,
2004 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2007 cached_root_id[ 0 ] = '\0';
2011 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2012 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2014 strcpy( cached_root_id, root_org_unit_id );
2015 jsonObjectFree( result );
2016 return cached_root_id;
2020 @brief Create a JSON_HASH with a single key/value pair.
2021 @param key The key of the key/value pair.
2022 @param value the value of the key/value pair.
2023 @return Pointer to a newly created jsonObject of type JSON_HASH.
2025 The value of the key/value is either a string or (if @a value is NULL) a null.
2027 static jsonObject* single_hash( const char* key, const char* value ) {
2029 if( ! key ) key = "";
2031 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2032 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2037 int doCreate( osrfMethodContext* ctx ) {
2038 if(osrfMethodVerifyContext( ctx )) {
2039 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2044 timeout_needs_resetting = 1;
2046 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2047 jsonObject* target = NULL;
2048 jsonObject* options = NULL;
2050 if( enforce_pcrud ) {
2051 target = jsonObjectGetIndex( ctx->params, 1 );
2052 options = jsonObjectGetIndex( ctx->params, 2 );
2054 target = jsonObjectGetIndex( ctx->params, 0 );
2055 options = jsonObjectGetIndex( ctx->params, 1 );
2058 if( !verifyObjectClass( ctx, target )) {
2059 osrfAppRespondComplete( ctx, NULL );
2063 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2065 const char* trans_id = getXactId( ctx );
2067 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2069 osrfAppSessionStatus(
2071 OSRF_STATUS_BADREQUEST,
2072 "osrfMethodException",
2074 "No active transaction -- required for CREATE"
2076 osrfAppRespondComplete( ctx, NULL );
2080 // The following test is harmless but redundant. If a class is
2081 // readonly, we don't register a create method for it.
2082 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2083 osrfAppSessionStatus(
2085 OSRF_STATUS_BADREQUEST,
2086 "osrfMethodException",
2088 "Cannot INSERT readonly class"
2090 osrfAppRespondComplete( ctx, NULL );
2094 // Set the last_xact_id
2095 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2097 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2098 trans_id, target->classname, index);
2099 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2102 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2104 dbhandle = writehandle;
2106 osrfHash* fields = osrfHashGet( meta, "fields" );
2107 char* pkey = osrfHashGet( meta, "primarykey" );
2108 char* seq = osrfHashGet( meta, "sequence" );
2110 growing_buffer* table_buf = buffer_init( 128 );
2111 growing_buffer* col_buf = buffer_init( 128 );
2112 growing_buffer* val_buf = buffer_init( 128 );
2114 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2115 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2116 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2117 buffer_add( val_buf,"VALUES (" );
2121 osrfHash* field = NULL;
2122 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2123 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2125 const char* field_name = osrfHashIteratorKey( field_itr );
2127 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2130 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2133 if( field_object && field_object->classname ) {
2134 value = oilsFMGetString(
2136 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2138 } else if( field_object && JSON_BOOL == field_object->type ) {
2139 if( jsonBoolIsTrue( field_object ) )
2140 value = strdup( "t" );
2142 value = strdup( "f" );
2144 value = jsonObjectToSimpleString( field_object );
2150 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2151 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2154 buffer_add( col_buf, field_name );
2156 if( !field_object || field_object->type == JSON_NULL ) {
2157 buffer_add( val_buf, "DEFAULT" );
2159 } else if( !strcmp( get_primitive( field ), "number" )) {
2160 const char* numtype = get_datatype( field );
2161 if( !strcmp( numtype, "INT8" )) {
2162 buffer_fadd( val_buf, "%lld", atoll( value ));
2164 } else if( !strcmp( numtype, "INT" )) {
2165 buffer_fadd( val_buf, "%d", atoi( value ));
2167 } else if( !strcmp( numtype, "NUMERIC" )) {
2168 buffer_fadd( val_buf, "%f", atof( value ));
2171 if( dbi_conn_quote_string( writehandle, &value )) {
2172 OSRF_BUFFER_ADD( val_buf, value );
2175 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2176 osrfAppSessionStatus(
2178 OSRF_STATUS_INTERNALSERVERERROR,
2179 "osrfMethodException",
2181 "Error quoting string -- please see the error log for more details"
2184 buffer_free( table_buf );
2185 buffer_free( col_buf );
2186 buffer_free( val_buf );
2187 osrfAppRespondComplete( ctx, NULL );
2195 osrfHashIteratorFree( field_itr );
2197 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2198 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2200 char* table_str = buffer_release( table_buf );
2201 char* col_str = buffer_release( col_buf );
2202 char* val_str = buffer_release( val_buf );
2203 growing_buffer* sql = buffer_init( 128 );
2204 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2209 char* query = buffer_release( sql );
2211 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2213 jsonObject* obj = NULL;
2216 dbi_result result = dbi_conn_query( writehandle, query );
2218 obj = jsonNewObject( NULL );
2220 int errnum = dbi_conn_error( writehandle, &msg );
2223 "%s ERROR inserting %s object using query [%s]: %d %s",
2225 osrfHashGet(meta, "fieldmapper"),
2228 msg ? msg : "(No description available)"
2230 osrfAppSessionStatus(
2232 OSRF_STATUS_INTERNALSERVERERROR,
2233 "osrfMethodException",
2235 "INSERT error -- please see the error log for more details"
2237 if( !oilsIsDBConnected( writehandle ))
2238 osrfAppSessionPanic( ctx->session );
2241 dbi_result_free( result );
2243 char* id = oilsFMGetString( target, pkey );
2245 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2246 growing_buffer* _id = buffer_init( 10 );
2247 buffer_fadd( _id, "%lld", new_id );
2248 id = buffer_release( _id );
2251 // Find quietness specification, if present
2252 const char* quiet_str = NULL;
2254 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2256 quiet_str = jsonObjectGetString( quiet_obj );
2259 if( str_is_true( quiet_str )) { // if quietness is specified
2260 obj = jsonNewObject( id );
2264 // Fetch the row that we just inserted, so that we can return it to the client
2265 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2266 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2269 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2273 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2275 jsonObjectFree( list );
2276 jsonObjectFree( where_clause );
2283 osrfAppRespondComplete( ctx, obj );
2284 jsonObjectFree( obj );
2289 @brief Implement the retrieve method.
2290 @param ctx Pointer to the method context.
2291 @param err Pointer through which to return an error code.
2292 @return If successful, a pointer to the result to be returned to the client;
2295 From the method's class, fetch a row with a specified value in the primary key. This
2296 method relies on the database design convention that a primary key consists of a single
2300 - authkey (PCRUD only)
2301 - value of the primary key for the desired row, for building the WHERE clause
2302 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2304 Return to client: One row from the query.
2306 int doRetrieve( osrfMethodContext* ctx ) {
2307 if(osrfMethodVerifyContext( ctx )) {
2308 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2313 timeout_needs_resetting = 1;
2318 if( enforce_pcrud ) {
2323 // Get the class metadata
2324 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2326 // Get the value of the primary key, from a method parameter
2327 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2331 "%s retrieving %s object with primary key value of %s",
2333 osrfHashGet( class_def, "fieldmapper" ),
2334 jsonObjectGetString( id_obj )
2337 // Build a WHERE clause based on the key value
2338 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2341 osrfHashGet( class_def, "primarykey" ), // name of key column
2342 jsonObjectClone( id_obj ) // value of key column
2345 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2349 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2351 jsonObjectFree( where_clause );
2353 osrfAppRespondComplete( ctx, NULL );
2357 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2358 jsonObjectFree( list );
2360 if( enforce_pcrud ) {
2361 if(!verifyObjectPCRUD( ctx, obj, 1 )) {
2362 jsonObjectFree( obj );
2364 growing_buffer* msg = buffer_init( 128 );
2365 OSRF_BUFFER_ADD( msg, modulename );
2366 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2368 char* m = buffer_release( msg );
2369 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2373 osrfAppRespondComplete( ctx, NULL );
2378 osrfAppRespondComplete( ctx, obj );
2379 jsonObjectFree( obj );
2384 @brief Translate a numeric value to a string representation for the database.
2385 @param field Pointer to the IDL field definition.
2386 @param value Pointer to a jsonObject holding the value of a field.
2387 @return Pointer to a newly allocated string.
2389 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2390 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2391 or (what is worse) valid SQL that is wrong.
2393 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2395 The calling code is responsible for freeing the resulting string by calling free().
2397 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2398 growing_buffer* val_buf = buffer_init( 32 );
2399 const char* numtype = get_datatype( field );
2401 // For historical reasons the following contains cruft that could be cleaned up.
2402 if( !strncmp( numtype, "INT", 3 ) ) {
2403 if( value->type == JSON_NUMBER )
2404 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2405 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2407 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2410 } else if( !strcmp( numtype, "NUMERIC" )) {
2411 if( value->type == JSON_NUMBER )
2412 buffer_fadd( val_buf, jsonObjectGetString( value ));
2414 buffer_fadd( val_buf, jsonObjectGetString( value ));
2418 // Presumably this was really intended to be a string, so quote it
2419 char* str = jsonObjectToSimpleString( value );
2420 if( dbi_conn_quote_string( dbhandle, &str )) {
2421 OSRF_BUFFER_ADD( val_buf, str );
2424 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2426 buffer_free( val_buf );
2431 return buffer_release( val_buf );
2434 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2435 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2436 growing_buffer* sql_buf = buffer_init( 32 );
2442 osrfHashGet( field, "name" )
2446 buffer_add( sql_buf, "IN (" );
2447 } else if( !strcasecmp( op,"not in" )) {
2448 buffer_add( sql_buf, "NOT IN (" );
2450 buffer_add( sql_buf, "IN (" );
2453 if( node->type == JSON_HASH ) {
2454 // subquery predicate
2455 char* subpred = buildQuery( ctx, node, SUBSELECT );
2457 buffer_free( sql_buf );
2461 buffer_add( sql_buf, subpred );
2464 } else if( node->type == JSON_ARRAY ) {
2465 // literal value list
2466 int in_item_index = 0;
2467 int in_item_first = 1;
2468 const jsonObject* in_item;
2469 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2474 buffer_add( sql_buf, ", " );
2477 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2478 osrfLogError( OSRF_LOG_MARK,
2479 "%s: Expected string or number within IN list; found %s",
2480 modulename, json_type( in_item->type ) );
2481 buffer_free( sql_buf );
2485 // Append the literal value -- quoted if not a number
2486 if( JSON_NUMBER == in_item->type ) {
2487 char* val = jsonNumberToDBString( field, in_item );
2488 OSRF_BUFFER_ADD( sql_buf, val );
2491 } else if( !strcmp( get_primitive( field ), "number" )) {
2492 char* val = jsonNumberToDBString( field, in_item );
2493 OSRF_BUFFER_ADD( sql_buf, val );
2497 char* key_string = jsonObjectToSimpleString( in_item );
2498 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2499 OSRF_BUFFER_ADD( sql_buf, key_string );
2502 osrfLogError( OSRF_LOG_MARK,
2503 "%s: Error quoting key string [%s]", modulename, key_string );
2505 buffer_free( sql_buf );
2511 if( in_item_first ) {
2512 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2513 buffer_free( sql_buf );
2517 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2518 modulename, json_type( node->type ));
2519 buffer_free( sql_buf );
2523 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2525 return buffer_release( sql_buf );
2528 // Receive a JSON_ARRAY representing a function call. The first
2529 // entry in the array is the function name. The rest are parameters.
2530 static char* searchValueTransform( const jsonObject* array ) {
2532 if( array->size < 1 ) {
2533 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2537 // Get the function name
2538 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2539 if( func_item->type != JSON_STRING ) {
2540 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2541 modulename, json_type( func_item->type ));
2545 growing_buffer* sql_buf = buffer_init( 32 );
2547 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2548 OSRF_BUFFER_ADD( sql_buf, "( " );
2550 // Get the parameters
2551 int func_item_index = 1; // We already grabbed the zeroth entry
2552 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2554 // Add a separator comma, if we need one
2555 if( func_item_index > 2 )
2556 buffer_add( sql_buf, ", " );
2558 // Add the current parameter
2559 if( func_item->type == JSON_NULL ) {
2560 buffer_add( sql_buf, "NULL" );
2562 char* val = jsonObjectToSimpleString( func_item );
2563 if( dbi_conn_quote_string( dbhandle, &val )) {
2564 OSRF_BUFFER_ADD( sql_buf, val );
2567 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2569 buffer_free( sql_buf );
2576 buffer_add( sql_buf, " )" );
2578 return buffer_release( sql_buf );
2581 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2582 const jsonObject* node, const char* op ) {
2584 if( ! is_good_operator( op ) ) {
2585 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2589 char* val = searchValueTransform( node );
2593 growing_buffer* sql_buf = buffer_init( 32 );
2598 osrfHashGet( field, "name" ),
2605 return buffer_release( sql_buf );
2608 // class_alias is a class name or other table alias
2609 // field is a field definition as stored in the IDL
2610 // node comes from the method parameter, and may represent an entry in the SELECT list
2611 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2612 const jsonObject* node ) {
2613 growing_buffer* sql_buf = buffer_init( 32 );
2615 const char* field_transform = jsonObjectGetString(
2616 jsonObjectGetKeyConst( node, "transform" ) );
2617 const char* transform_subcolumn = jsonObjectGetString(
2618 jsonObjectGetKeyConst( node, "result_field" ) );
2620 if( transform_subcolumn ) {
2621 if( ! is_identifier( transform_subcolumn ) ) {
2622 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2623 modulename, transform_subcolumn );
2624 buffer_free( sql_buf );
2627 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2630 if( field_transform ) {
2632 if( ! is_identifier( field_transform ) ) {
2633 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2634 modulename, field_transform );
2635 buffer_free( sql_buf );
2639 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2640 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2641 field_transform, class_alias, osrfHashGet( field, "name" ));
2643 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2644 field_transform, class_alias, osrfHashGet( field, "name" ));
2647 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2650 if( array->type != JSON_ARRAY ) {
2651 osrfLogError( OSRF_LOG_MARK,
2652 "%s: Expected JSON_ARRAY for function params; found %s",
2653 modulename, json_type( array->type ) );
2654 buffer_free( sql_buf );
2657 int func_item_index = 0;
2658 jsonObject* func_item;
2659 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2661 char* val = jsonObjectToSimpleString( func_item );
2664 buffer_add( sql_buf, ",NULL" );
2665 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2666 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2667 OSRF_BUFFER_ADD( sql_buf, val );
2669 osrfLogError( OSRF_LOG_MARK,
2670 "%s: Error quoting key string [%s]", modulename, val );
2672 buffer_free( sql_buf );
2679 buffer_add( sql_buf, " )" );
2682 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2685 if( transform_subcolumn )
2686 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2688 return buffer_release( sql_buf );
2691 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2692 const jsonObject* node, const char* op ) {
2694 if( ! is_good_operator( op ) ) {
2695 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2699 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2700 if( ! field_transform )
2703 int extra_parens = 0; // boolean
2705 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2707 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2709 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2711 free( field_transform );
2715 } else if( value_obj->type == JSON_ARRAY ) {
2716 value = searchValueTransform( value_obj );
2718 osrfLogError( OSRF_LOG_MARK,
2719 "%s: Error building value transform for field transform", modulename );
2720 free( field_transform );
2723 } else if( value_obj->type == JSON_HASH ) {
2724 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2726 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2728 free( field_transform );
2732 } else if( value_obj->type == JSON_NUMBER ) {
2733 value = jsonNumberToDBString( field, value_obj );
2734 } else if( value_obj->type == JSON_NULL ) {
2735 osrfLogError( OSRF_LOG_MARK,
2736 "%s: Error building predicate for field transform: null value", modulename );
2737 free( field_transform );
2739 } else if( value_obj->type == JSON_BOOL ) {
2740 osrfLogError( OSRF_LOG_MARK,
2741 "%s: Error building predicate for field transform: boolean value", modulename );
2742 free( field_transform );
2745 if( !strcmp( get_primitive( field ), "number") ) {
2746 value = jsonNumberToDBString( field, value_obj );
2748 value = jsonObjectToSimpleString( value_obj );
2749 if( !dbi_conn_quote_string( dbhandle, &value )) {
2750 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2751 modulename, value );
2753 free( field_transform );
2759 const char* left_parens = "";
2760 const char* right_parens = "";
2762 if( extra_parens ) {
2767 growing_buffer* sql_buf = buffer_init( 32 );
2771 "%s%s %s %s %s %s%s",
2782 free( field_transform );
2784 return buffer_release( sql_buf );
2787 static char* searchSimplePredicate( const char* op, const char* class_alias,
2788 osrfHash* field, const jsonObject* node ) {
2790 if( ! is_good_operator( op ) ) {
2791 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2797 // Get the value to which we are comparing the specified column
2798 if( node->type != JSON_NULL ) {
2799 if( node->type == JSON_NUMBER ) {
2800 val = jsonNumberToDBString( field, node );
2801 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2802 val = jsonNumberToDBString( field, node );
2804 val = jsonObjectToSimpleString( node );
2809 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2810 // Value is not numeric; enclose it in quotes
2811 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2812 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2819 // Compare to a null value
2820 val = strdup( "NULL" );
2821 if( strcmp( op, "=" ))
2827 growing_buffer* sql_buf = buffer_init( 32 );
2828 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2829 char* pred = buffer_release( sql_buf );
2836 static char* searchBETWEENPredicate( const char* class_alias,
2837 osrfHash* field, const jsonObject* node ) {
2839 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2840 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2842 if( NULL == y_node ) {
2843 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2846 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2847 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2854 if( !strcmp( get_primitive( field ), "number") ) {
2855 x_string = jsonNumberToDBString( field, x_node );
2856 y_string = jsonNumberToDBString( field, y_node );
2859 x_string = jsonObjectToSimpleString( x_node );
2860 y_string = jsonObjectToSimpleString( y_node );
2861 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2862 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2863 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2864 modulename, x_string, y_string );
2871 growing_buffer* sql_buf = buffer_init( 32 );
2872 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2873 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2877 return buffer_release( sql_buf );
2880 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2881 jsonObject* node, osrfMethodContext* ctx ) {
2884 if( node->type == JSON_ARRAY ) { // equality IN search
2885 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2886 } else if( node->type == JSON_HASH ) { // other search
2887 jsonIterator* pred_itr = jsonNewIterator( node );
2888 if( !jsonIteratorHasNext( pred_itr ) ) {
2889 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2890 modulename, osrfHashGet(field, "name" ));
2892 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2894 // Verify that there are no additional predicates
2895 if( jsonIteratorHasNext( pred_itr ) ) {
2896 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2897 modulename, osrfHashGet(field, "name" ));
2898 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2899 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2900 else if( !(strcasecmp( pred_itr->key,"in" ))
2901 || !(strcasecmp( pred_itr->key,"not in" )) )
2902 pred = searchINPredicate(
2903 class_info->alias, field, pred_node, pred_itr->key, ctx );
2904 else if( pred_node->type == JSON_ARRAY )
2905 pred = searchFunctionPredicate(
2906 class_info->alias, field, pred_node, pred_itr->key );
2907 else if( pred_node->type == JSON_HASH )
2908 pred = searchFieldTransformPredicate(
2909 class_info, field, pred_node, pred_itr->key );
2911 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2913 jsonIteratorFree( pred_itr );
2915 } else if( node->type == JSON_NULL ) { // IS NULL search
2916 growing_buffer* _p = buffer_init( 64 );
2919 "\"%s\".%s IS NULL",
2920 class_info->class_name,
2921 osrfHashGet( field, "name" )
2923 pred = buffer_release( _p );
2924 } else { // equality search
2925 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2944 field : call_number,
2960 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2962 const jsonObject* working_hash;
2963 jsonObject* freeable_hash = NULL;
2965 if( join_hash->type == JSON_HASH ) {
2966 working_hash = join_hash;
2967 } else if( join_hash->type == JSON_STRING ) {
2968 // turn it into a JSON_HASH by creating a wrapper
2969 // around a copy of the original
2970 const char* _tmp = jsonObjectGetString( join_hash );
2971 freeable_hash = jsonNewObjectType( JSON_HASH );
2972 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2973 working_hash = freeable_hash;
2977 "%s: JOIN failed; expected JSON object type not found",
2983 growing_buffer* join_buf = buffer_init( 128 );
2984 const char* leftclass = left_info->class_name;
2986 jsonObject* snode = NULL;
2987 jsonIterator* search_itr = jsonNewIterator( working_hash );
2989 while ( (snode = jsonIteratorNext( search_itr )) ) {
2990 const char* right_alias = search_itr->key;
2992 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2994 class = right_alias;
2996 const ClassInfo* right_info = add_joined_class( right_alias, class );
3000 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3004 jsonIteratorFree( search_itr );
3005 buffer_free( join_buf );
3007 jsonObjectFree( freeable_hash );
3010 osrfHash* links = right_info->links;
3011 const char* table = right_info->source_def;
3013 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3014 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3016 if( field && !fkey ) {
3017 // Look up the corresponding join column in the IDL.
3018 // The link must be defined in the child table,
3019 // and point to the right parent table.
3020 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3021 const char* reltype = NULL;
3022 const char* other_class = NULL;
3023 reltype = osrfHashGet( idl_link, "reltype" );
3024 if( reltype && strcmp( reltype, "has_many" ) )
3025 other_class = osrfHashGet( idl_link, "class" );
3026 if( other_class && !strcmp( other_class, leftclass ) )
3027 fkey = osrfHashGet( idl_link, "key" );
3031 "%s: JOIN failed. No link defined from %s.%s to %s",
3037 buffer_free( join_buf );
3039 jsonObjectFree( freeable_hash );
3040 jsonIteratorFree( search_itr );
3044 } else if( !field && fkey ) {
3045 // Look up the corresponding join column in the IDL.
3046 // The link must be defined in the child table,
3047 // and point to the right parent table.
3048 osrfHash* left_links = left_info->links;
3049 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3050 const char* reltype = NULL;
3051 const char* other_class = NULL;
3052 reltype = osrfHashGet( idl_link, "reltype" );
3053 if( reltype && strcmp( reltype, "has_many" ) )
3054 other_class = osrfHashGet( idl_link, "class" );
3055 if( other_class && !strcmp( other_class, class ) )
3056 field = osrfHashGet( idl_link, "key" );
3060 "%s: JOIN failed. No link defined from %s.%s to %s",
3066 buffer_free( join_buf );
3068 jsonObjectFree( freeable_hash );
3069 jsonIteratorFree( search_itr );
3073 } else if( !field && !fkey ) {
3074 osrfHash* left_links = left_info->links;
3076 // For each link defined for the left class:
3077 // see if the link references the joined class
3078 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3079 osrfHash* curr_link = NULL;
3080 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3081 const char* other_class = osrfHashGet( curr_link, "class" );
3082 if( other_class && !strcmp( other_class, class ) ) {
3084 // In the IDL, the parent class doesn't always know then names of the child
3085 // columns that are pointing to it, so don't use that end of the link
3086 const char* reltype = osrfHashGet( curr_link, "reltype" );
3087 if( reltype && strcmp( reltype, "has_many" ) ) {
3088 // Found a link between the classes
3089 fkey = osrfHashIteratorKey( itr );
3090 field = osrfHashGet( curr_link, "key" );
3095 osrfHashIteratorFree( itr );
3097 if( !field || !fkey ) {
3098 // Do another such search, with the classes reversed
3100 // For each link defined for the joined class:
3101 // see if the link references the left class
3102 osrfHashIterator* itr = osrfNewHashIterator( links );
3103 osrfHash* curr_link = NULL;
3104 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3105 const char* other_class = osrfHashGet( curr_link, "class" );
3106 if( other_class && !strcmp( other_class, leftclass ) ) {
3108 // In the IDL, the parent class doesn't know then names of the child
3109 // columns that are pointing to it, so don't use that end of the link
3110 const char* reltype = osrfHashGet( curr_link, "reltype" );
3111 if( reltype && strcmp( reltype, "has_many" ) ) {
3112 // Found a link between the classes
3113 field = osrfHashIteratorKey( itr );
3114 fkey = osrfHashGet( curr_link, "key" );
3119 osrfHashIteratorFree( itr );
3122 if( !field || !fkey ) {
3125 "%s: JOIN failed. No link defined between %s and %s",
3130 buffer_free( join_buf );
3132 jsonObjectFree( freeable_hash );
3133 jsonIteratorFree( search_itr );
3138 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3140 if( !strcasecmp( type,"left" )) {
3141 buffer_add( join_buf, " LEFT JOIN" );
3142 } else if( !strcasecmp( type,"right" )) {
3143 buffer_add( join_buf, " RIGHT JOIN" );
3144 } else if( !strcasecmp( type,"full" )) {
3145 buffer_add( join_buf, " FULL JOIN" );
3147 buffer_add( join_buf, " INNER JOIN" );
3150 buffer_add( join_buf, " INNER JOIN" );
3153 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3154 table, right_alias, right_alias, field, left_info->alias, fkey );
3156 // Add any other join conditions as specified by "filter"
3157 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3159 const char* filter_op = jsonObjectGetString(
3160 jsonObjectGetKeyConst( snode, "filter_op" ) );
3161 if( filter_op && !strcasecmp( "or",filter_op )) {
3162 buffer_add( join_buf, " OR " );
3164 buffer_add( join_buf, " AND " );
3167 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3169 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3170 OSRF_BUFFER_ADD( join_buf, jpred );
3175 "%s: JOIN failed. Invalid conditional expression.",
3178 jsonIteratorFree( search_itr );
3179 buffer_free( join_buf );
3181 jsonObjectFree( freeable_hash );
3186 buffer_add( join_buf, " ) " );
3188 // Recursively add a nested join, if one is present
3189 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3191 char* jpred = searchJOIN( join_filter, right_info );
3193 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3194 OSRF_BUFFER_ADD( join_buf, jpred );
3197 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3198 jsonIteratorFree( search_itr );
3199 buffer_free( join_buf );
3201 jsonObjectFree( freeable_hash );
3208 jsonObjectFree( freeable_hash );
3209 jsonIteratorFree( search_itr );
3211 return buffer_release( join_buf );
3216 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3217 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3218 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3220 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3222 search_hash is the JSON expression of the conditions.
3223 meta is the class definition from the IDL, for the relevant table.
3224 opjoin_type indicates whether multiple conditions, if present, should be
3225 connected by AND or OR.
3226 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3227 to pass it to other functions -- and all they do with it is to use the session
3228 and request members to send error messages back to the client.
3232 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3233 int opjoin_type, osrfMethodContext* ctx ) {
3237 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3238 "opjoin_type = %d, ctx addr = %p",
3241 class_info->class_def,
3246 growing_buffer* sql_buf = buffer_init( 128 );
3248 jsonObject* node = NULL;
3251 if( search_hash->type == JSON_ARRAY ) {
3252 if( 0 == search_hash->size ) {
3255 "%s: Invalid predicate structure: empty JSON array",
3258 buffer_free( sql_buf );
3262 unsigned long i = 0;
3263 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3267 if( opjoin_type == OR_OP_JOIN )
3268 buffer_add( sql_buf, " OR " );
3270 buffer_add( sql_buf, " AND " );
3273 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3275 buffer_free( sql_buf );
3279 buffer_fadd( sql_buf, "( %s )", subpred );
3283 } else if( search_hash->type == JSON_HASH ) {
3284 osrfLogDebug( OSRF_LOG_MARK,
3285 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3286 jsonIterator* search_itr = jsonNewIterator( search_hash );
3287 if( !jsonIteratorHasNext( search_itr ) ) {
3290 "%s: Invalid predicate structure: empty JSON object",
3293 jsonIteratorFree( search_itr );
3294 buffer_free( sql_buf );
3298 while( (node = jsonIteratorNext( search_itr )) ) {
3303 if( opjoin_type == OR_OP_JOIN )
3304 buffer_add( sql_buf, " OR " );
3306 buffer_add( sql_buf, " AND " );
3309 if( '+' == search_itr->key[ 0 ] ) {
3311 // This plus sign prefixes a class name or other table alias;
3312 // make sure the table alias is in scope
3313 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3314 if( ! alias_info ) {
3317 "%s: Invalid table alias \"%s\" in WHERE clause",
3321 jsonIteratorFree( search_itr );
3322 buffer_free( sql_buf );
3326 if( node->type == JSON_STRING ) {
3327 // It's the name of a column; make sure it belongs to the class
3328 const char* fieldname = jsonObjectGetString( node );
3329 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3332 "%s: Invalid column name \"%s\" in WHERE clause "
3333 "for table alias \"%s\"",
3338 jsonIteratorFree( search_itr );
3339 buffer_free( sql_buf );
3343 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3345 // It's something more complicated
3346 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3348 jsonIteratorFree( search_itr );
3349 buffer_free( sql_buf );
3353 buffer_fadd( sql_buf, "( %s )", subpred );
3356 } else if( '-' == search_itr->key[ 0 ] ) {
3357 if( !strcasecmp( "-or", search_itr->key )) {
3358 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3360 jsonIteratorFree( search_itr );
3361 buffer_free( sql_buf );
3365 buffer_fadd( sql_buf, "( %s )", subpred );
3367 } else if( !strcasecmp( "-and", 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, "( %s )", subpred );
3377 } else if( !strcasecmp("-not",search_itr->key) ) {
3378 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3380 jsonIteratorFree( search_itr );
3381 buffer_free( sql_buf );
3385 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3387 } else if( !strcasecmp( "-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, "EXISTS ( %s )", subpred );
3397 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3398 char* subpred = buildQuery( ctx, node, SUBSELECT );
3400 jsonIteratorFree( search_itr );
3401 buffer_free( sql_buf );
3405 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3407 } else { // Invalid "minus" operator
3410 "%s: Invalid operator \"%s\" in WHERE clause",
3414 jsonIteratorFree( search_itr );
3415 buffer_free( sql_buf );
3421 const char* class = class_info->class_name;
3422 osrfHash* fields = class_info->fields;
3423 osrfHash* field = osrfHashGet( fields, search_itr->key );
3426 const char* table = class_info->source_def;
3429 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3432 table ? table : "?",
3435 jsonIteratorFree( search_itr );
3436 buffer_free( sql_buf );
3440 char* subpred = searchPredicate( class_info, field, node, ctx );
3442 buffer_free( sql_buf );
3443 jsonIteratorFree( search_itr );
3447 buffer_add( sql_buf, subpred );
3451 jsonIteratorFree( search_itr );
3454 // ERROR ... only hash and array allowed at this level
3455 char* predicate_string = jsonObjectToJSON( search_hash );
3458 "%s: Invalid predicate structure: %s",
3462 buffer_free( sql_buf );
3463 free( predicate_string );
3467 return buffer_release( sql_buf );
3470 /* Build a JSON_ARRAY of field names for a given table alias
3472 static jsonObject* defaultSelectList( const char* table_alias ) {
3477 ClassInfo* class_info = search_all_alias( table_alias );
3478 if( ! class_info ) {
3481 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3488 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3489 osrfHash* field_def = NULL;
3490 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3491 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3492 const char* field_name = osrfHashIteratorKey( field_itr );
3493 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3494 jsonObjectPush( array, jsonNewObject( field_name ) );
3497 osrfHashIteratorFree( field_itr );
3502 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3503 // The jsonObject must be a JSON_HASH with an single entry for "union",
3504 // "intersect", or "except". The data associated with this key must be an
3505 // array of hashes, each hash being a query.
3506 // Also allowed but currently ignored: entries for "order_by" and "alias".
3507 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3509 if( ! combo || combo->type != JSON_HASH )
3510 return NULL; // should be impossible; validated by caller
3512 const jsonObject* query_array = NULL; // array of subordinate queries
3513 const char* op = NULL; // name of operator, e.g. UNION
3514 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3515 int op_count = 0; // for detecting conflicting operators
3516 int excepting = 0; // boolean
3517 int all = 0; // boolean
3518 jsonObject* order_obj = NULL;
3520 // Identify the elements in the hash
3521 jsonIterator* query_itr = jsonNewIterator( combo );
3522 jsonObject* curr_obj = NULL;
3523 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3524 if( ! strcmp( "union", query_itr->key ) ) {
3527 query_array = curr_obj;
3528 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3531 query_array = curr_obj;
3532 } else if( ! strcmp( "except", query_itr->key ) ) {
3536 query_array = curr_obj;
3537 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3540 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3543 order_obj = curr_obj;
3544 } else if( ! strcmp( "alias", query_itr->key ) ) {
3545 if( curr_obj->type != JSON_STRING ) {
3546 jsonIteratorFree( query_itr );
3549 alias = jsonObjectGetString( curr_obj );
3550 } else if( ! strcmp( "all", query_itr->key ) ) {
3551 if( obj_is_true( curr_obj ) )
3555 osrfAppSessionStatus(
3557 OSRF_STATUS_INTERNALSERVERERROR,
3558 "osrfMethodException",
3560 "Malformed query; unexpected entry in query object"
3564 "%s: Unexpected entry for \"%s\" in%squery",
3569 jsonIteratorFree( query_itr );
3573 jsonIteratorFree( query_itr );
3575 // More sanity checks
3576 if( ! query_array ) {
3578 osrfAppSessionStatus(
3580 OSRF_STATUS_INTERNALSERVERERROR,
3581 "osrfMethodException",
3583 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3587 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3590 return NULL; // should be impossible...
3591 } else if( op_count > 1 ) {
3593 osrfAppSessionStatus(
3595 OSRF_STATUS_INTERNALSERVERERROR,
3596 "osrfMethodException",
3598 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3602 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3606 } if( query_array->type != JSON_ARRAY ) {
3608 osrfAppSessionStatus(
3610 OSRF_STATUS_INTERNALSERVERERROR,
3611 "osrfMethodException",
3613 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3617 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3620 json_type( query_array->type )
3623 } if( query_array->size < 2 ) {
3625 osrfAppSessionStatus(
3627 OSRF_STATUS_INTERNALSERVERERROR,
3628 "osrfMethodException",
3630 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3634 "%s:%srequires multiple queries as operands",
3639 } else if( excepting && query_array->size > 2 ) {
3641 osrfAppSessionStatus(
3643 OSRF_STATUS_INTERNALSERVERERROR,
3644 "osrfMethodException",
3646 "EXCEPT operator has too many queries as operands"
3650 "%s:EXCEPT operator has too many queries as operands",
3654 } else if( order_obj && ! alias ) {
3656 osrfAppSessionStatus(
3658 OSRF_STATUS_INTERNALSERVERERROR,
3659 "osrfMethodException",
3661 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3665 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3671 // So far so good. Now build the SQL.
3672 growing_buffer* sql = buffer_init( 256 );
3674 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3675 // Add a layer of parentheses
3676 if( flags & SUBCOMBO )
3677 OSRF_BUFFER_ADD( sql, "( " );
3679 // Traverse the query array. Each entry should be a hash.
3680 int first = 1; // boolean
3682 jsonObject* query = NULL;
3683 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3684 if( query->type != JSON_HASH ) {
3686 osrfAppSessionStatus(
3688 OSRF_STATUS_INTERNALSERVERERROR,
3689 "osrfMethodException",
3691 "Malformed query under UNION, INTERSECT or EXCEPT"
3695 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3698 json_type( query->type )
3707 OSRF_BUFFER_ADD( sql, op );
3709 OSRF_BUFFER_ADD( sql, "ALL " );
3712 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3716 "%s: Error building query under%s",
3724 OSRF_BUFFER_ADD( sql, query_str );
3727 if( flags & SUBCOMBO )
3728 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3730 if( !(flags & SUBSELECT) )
3731 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3733 return buffer_release( sql );
3736 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3737 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3738 // or "except" to indicate the type of query.
3739 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3743 osrfAppSessionStatus(
3745 OSRF_STATUS_INTERNALSERVERERROR,
3746 "osrfMethodException",
3748 "Malformed query; no query object"
3750 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3752 } else if( query->type != JSON_HASH ) {
3754 osrfAppSessionStatus(
3756 OSRF_STATUS_INTERNALSERVERERROR,
3757 "osrfMethodException",
3759 "Malformed query object"
3763 "%s: Query object is %s instead of JSON_HASH",
3765 json_type( query->type )
3770 // Determine what kind of query it purports to be, and dispatch accordingly.
3771 if( jsonObjectGetKeyConst( query, "union" ) ||
3772 jsonObjectGetKeyConst( query, "intersect" ) ||
3773 jsonObjectGetKeyConst( query, "except" )) {
3774 return doCombo( ctx, query, flags );
3776 // It is presumably a SELECT query
3778 // Push a node onto the stack for the current query. Every level of
3779 // subquery gets its own QueryFrame on the Stack.
3782 // Build an SQL SELECT statement
3785 jsonObjectGetKey( query, "select" ),
3786 jsonObjectGetKeyConst( query, "from" ),
3787 jsonObjectGetKeyConst( query, "where" ),
3788 jsonObjectGetKeyConst( query, "having" ),
3789 jsonObjectGetKeyConst( query, "order_by" ),
3790 jsonObjectGetKeyConst( query, "limit" ),
3791 jsonObjectGetKeyConst( query, "offset" ),
3800 /* method context */ osrfMethodContext* ctx,
3802 /* SELECT */ jsonObject* selhash,
3803 /* FROM */ const jsonObject* join_hash,
3804 /* WHERE */ const jsonObject* search_hash,
3805 /* HAVING */ const jsonObject* having_hash,
3806 /* ORDER BY */ const jsonObject* order_hash,
3807 /* LIMIT */ const jsonObject* limit,
3808 /* OFFSET */ const jsonObject* offset,
3809 /* flags */ int flags
3811 const char* locale = osrf_message_get_last_locale();
3813 // general tmp objects
3814 const jsonObject* tmp_const;
3815 jsonObject* selclass = NULL;
3816 jsonObject* snode = NULL;
3817 jsonObject* onode = NULL;
3819 char* string = NULL;
3820 int from_function = 0;
3825 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3827 // punt if there's no FROM clause
3828 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3831 "%s: FROM clause is missing or empty",
3835 osrfAppSessionStatus(
3837 OSRF_STATUS_INTERNALSERVERERROR,
3838 "osrfMethodException",
3840 "FROM clause is missing or empty in JSON query"
3845 // the core search class
3846 const char* core_class = NULL;
3848 // get the core class -- the only key of the top level FROM clause, or a string
3849 if( join_hash->type == JSON_HASH ) {
3850 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3851 snode = jsonIteratorNext( tmp_itr );
3853 // Populate the current QueryFrame with information
3854 // about the core class
3855 if( add_query_core( NULL, tmp_itr->key ) ) {
3857 osrfAppSessionStatus(
3859 OSRF_STATUS_INTERNALSERVERERROR,
3860 "osrfMethodException",
3862 "Unable to look up core class"
3866 core_class = curr_query->core.class_name;
3869 jsonObject* extra = jsonIteratorNext( tmp_itr );
3871 jsonIteratorFree( tmp_itr );
3874 // There shouldn't be more than one entry in join_hash
3878 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3882 osrfAppSessionStatus(
3884 OSRF_STATUS_INTERNALSERVERERROR,
3885 "osrfMethodException",
3887 "Malformed FROM clause in JSON query"
3889 return NULL; // Malformed join_hash; extra entry
3891 } else if( join_hash->type == JSON_ARRAY ) {
3892 // We're selecting from a function, not from a table
3894 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3897 } else if( join_hash->type == JSON_STRING ) {
3898 // Populate the current QueryFrame with information
3899 // about the core class
3900 core_class = jsonObjectGetString( join_hash );
3902 if( add_query_core( NULL, core_class ) ) {
3904 osrfAppSessionStatus(
3906 OSRF_STATUS_INTERNALSERVERERROR,
3907 "osrfMethodException",
3909 "Unable to look up core class"
3917 "%s: FROM clause is unexpected JSON type: %s",
3919 json_type( join_hash->type )
3922 osrfAppSessionStatus(
3924 OSRF_STATUS_INTERNALSERVERERROR,
3925 "osrfMethodException",
3927 "Ill-formed FROM clause in JSON query"
3932 // Build the join clause, if any, while filling out the list
3933 // of joined classes in the current QueryFrame.
3934 char* join_clause = NULL;
3935 if( join_hash && ! from_function ) {
3937 join_clause = searchJOIN( join_hash, &curr_query->core );
3938 if( ! join_clause ) {
3940 osrfAppSessionStatus(
3942 OSRF_STATUS_INTERNALSERVERERROR,
3943 "osrfMethodException",
3945 "Unable to construct JOIN clause(s)"
3951 // For in case we don't get a select list
3952 jsonObject* defaultselhash = NULL;
3954 // if there is no select list, build a default select list ...
3955 if( !selhash && !from_function ) {
3956 jsonObject* default_list = defaultSelectList( core_class );
3957 if( ! default_list ) {
3959 osrfAppSessionStatus(
3961 OSRF_STATUS_INTERNALSERVERERROR,
3962 "osrfMethodException",
3964 "Unable to build default SELECT clause in JSON query"
3966 free( join_clause );
3971 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3972 jsonObjectSetKey( selhash, core_class, default_list );
3975 // The SELECT clause can be encoded only by a hash
3976 if( !from_function && selhash->type != JSON_HASH ) {
3979 "%s: Expected JSON_HASH for SELECT clause; found %s",
3981 json_type( selhash->type )
3985 osrfAppSessionStatus(
3987 OSRF_STATUS_INTERNALSERVERERROR,
3988 "osrfMethodException",
3990 "Malformed SELECT clause in JSON query"
3992 free( join_clause );
3996 // If you see a null or wild card specifier for the core class, or an
3997 // empty array, replace it with a default SELECT list
3998 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4000 int default_needed = 0; // boolean
4001 if( JSON_STRING == tmp_const->type
4002 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4004 else if( JSON_NULL == tmp_const->type )
4007 if( default_needed ) {
4008 // Build a default SELECT list
4009 jsonObject* default_list = defaultSelectList( core_class );
4010 if( ! default_list ) {
4012 osrfAppSessionStatus(
4014 OSRF_STATUS_INTERNALSERVERERROR,
4015 "osrfMethodException",
4017 "Can't build default SELECT clause in JSON query"
4019 free( join_clause );
4024 jsonObjectSetKey( selhash, core_class, default_list );
4028 // temp buffers for the SELECT list and GROUP BY clause
4029 growing_buffer* select_buf = buffer_init( 128 );
4030 growing_buffer* group_buf = buffer_init( 128 );
4032 int aggregate_found = 0; // boolean
4034 // Build a select list
4035 if( from_function ) // From a function we select everything
4036 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4039 // Build the SELECT list as SQL
4043 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4044 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4046 const char* cname = selclass_itr->key;
4048 // Make sure the target relation is in the FROM clause.
4050 // At this point join_hash is a step down from the join_hash we
4051 // received as a parameter. If the original was a JSON_STRING,
4052 // then json_hash is now NULL. If the original was a JSON_HASH,
4053 // then json_hash is now the first (and only) entry in it,
4054 // denoting the core class. We've already excluded the
4055 // possibility that the original was a JSON_ARRAY, because in
4056 // that case from_function would be non-NULL, and we wouldn't
4059 // If the current table alias isn't in scope, bail out
4060 ClassInfo* class_info = search_alias( cname );
4061 if( ! class_info ) {
4064 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4069 osrfAppSessionStatus(
4071 OSRF_STATUS_INTERNALSERVERERROR,
4072 "osrfMethodException",
4074 "Selected class not in FROM clause in JSON query"
4076 jsonIteratorFree( selclass_itr );
4077 buffer_free( select_buf );
4078 buffer_free( group_buf );
4079 if( defaultselhash )
4080 jsonObjectFree( defaultselhash );
4081 free( join_clause );
4085 if( selclass->type != JSON_ARRAY ) {
4088 "%s: Malformed SELECT list for class \"%s\"; not an array",
4093 osrfAppSessionStatus(
4095 OSRF_STATUS_INTERNALSERVERERROR,
4096 "osrfMethodException",
4098 "Selected class not in FROM clause in JSON query"
4101 jsonIteratorFree( selclass_itr );
4102 buffer_free( select_buf );
4103 buffer_free( group_buf );
4104 if( defaultselhash )
4105 jsonObjectFree( defaultselhash );
4106 free( join_clause );
4110 // Look up some attributes of the current class
4111 osrfHash* idlClass = class_info->class_def;
4112 osrfHash* class_field_set = class_info->fields;
4113 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4114 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4116 if( 0 == selclass->size ) {
4119 "%s: No columns selected from \"%s\"",
4125 // stitch together the column list for the current table alias...
4126 unsigned long field_idx = 0;
4127 jsonObject* selfield = NULL;
4128 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4130 // If we need a separator comma, add one
4134 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4137 // if the field specification is a string, add it to the list
4138 if( selfield->type == JSON_STRING ) {
4140 // Look up the field in the IDL
4141 const char* col_name = jsonObjectGetString( selfield );
4142 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4144 // No such field in current class
4147 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4153 osrfAppSessionStatus(
4155 OSRF_STATUS_INTERNALSERVERERROR,
4156 "osrfMethodException",
4158 "Selected column not defined in JSON query"
4160 jsonIteratorFree( selclass_itr );
4161 buffer_free( select_buf );
4162 buffer_free( group_buf );
4163 if( defaultselhash )
4164 jsonObjectFree( defaultselhash );
4165 free( join_clause );
4167 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4168 // Virtual field not allowed
4171 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4177 osrfAppSessionStatus(
4179 OSRF_STATUS_INTERNALSERVERERROR,
4180 "osrfMethodException",
4182 "Selected column may not be virtual in JSON query"
4184 jsonIteratorFree( selclass_itr );
4185 buffer_free( select_buf );
4186 buffer_free( group_buf );
4187 if( defaultselhash )
4188 jsonObjectFree( defaultselhash );
4189 free( join_clause );
4195 if( flags & DISABLE_I18N )
4198 i18n = osrfHashGet( field_def, "i18n" );
4200 if( str_is_true( i18n ) ) {
4201 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4202 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4203 class_tname, cname, col_name, class_pkey,
4204 cname, class_pkey, locale, col_name );
4206 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4207 cname, col_name, col_name );
4210 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4211 cname, col_name, col_name );
4214 // ... but it could be an object, in which case we check for a Field Transform
4215 } else if( selfield->type == JSON_HASH ) {
4217 const char* col_name = jsonObjectGetString(
4218 jsonObjectGetKeyConst( selfield, "column" ) );
4220 // Get the field definition from the IDL
4221 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4223 // No such field in current class
4226 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4232 osrfAppSessionStatus(
4234 OSRF_STATUS_INTERNALSERVERERROR,
4235 "osrfMethodException",
4237 "Selected column is not defined in JSON query"
4239 jsonIteratorFree( selclass_itr );
4240 buffer_free( select_buf );
4241 buffer_free( group_buf );
4242 if( defaultselhash )
4243 jsonObjectFree( defaultselhash );
4244 free( join_clause );
4246 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4247 // No such field in current class
4250 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4256 osrfAppSessionStatus(
4258 OSRF_STATUS_INTERNALSERVERERROR,
4259 "osrfMethodException",
4261 "Selected column is virtual in JSON query"
4263 jsonIteratorFree( selclass_itr );
4264 buffer_free( select_buf );
4265 buffer_free( group_buf );
4266 if( defaultselhash )
4267 jsonObjectFree( defaultselhash );
4268 free( join_clause );
4272 // Decide what to use as a column alias
4274 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4275 _alias = jsonObjectGetString( tmp_const );
4276 } else { // Use field name as the alias
4280 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4281 char* transform_str = searchFieldTransform(
4282 class_info->alias, field_def, selfield );
4283 if( transform_str ) {
4284 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4285 free( transform_str );
4288 osrfAppSessionStatus(
4290 OSRF_STATUS_INTERNALSERVERERROR,
4291 "osrfMethodException",
4293 "Unable to generate transform function in JSON query"
4295 jsonIteratorFree( selclass_itr );
4296 buffer_free( select_buf );
4297 buffer_free( group_buf );
4298 if( defaultselhash )
4299 jsonObjectFree( defaultselhash );
4300 free( join_clause );
4307 if( flags & DISABLE_I18N )
4310 i18n = osrfHashGet( field_def, "i18n" );
4312 if( str_is_true( i18n ) ) {
4313 buffer_fadd( select_buf,
4314 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4315 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4316 class_tname, cname, col_name, class_pkey, cname,
4317 class_pkey, locale, _alias );
4319 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4320 cname, col_name, _alias );
4323 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4324 cname, col_name, _alias );
4331 "%s: Selected item is unexpected JSON type: %s",
4333 json_type( selfield->type )
4336 osrfAppSessionStatus(
4338 OSRF_STATUS_INTERNALSERVERERROR,
4339 "osrfMethodException",
4341 "Ill-formed SELECT item in JSON query"
4343 jsonIteratorFree( selclass_itr );
4344 buffer_free( select_buf );
4345 buffer_free( group_buf );
4346 if( defaultselhash )
4347 jsonObjectFree( defaultselhash );
4348 free( join_clause );
4352 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4353 if( obj_is_true( agg_obj ) )
4354 aggregate_found = 1;
4356 // Append a comma (except for the first one)
4357 // and add the column to a GROUP BY clause
4361 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4363 buffer_fadd( group_buf, " %d", sel_pos );
4367 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4369 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( elfield, "aggregate");
4370 if ( ! obj_is_true( aggregate_obj ) ) {
4374 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4377 buffer_fadd(group_buf, " %d", sel_pos);
4380 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4384 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4387 _column = searchFieldTransform(class_info->alias, field, selfield);
4388 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4389 OSRF_BUFFER_ADD(group_buf, _column);
4390 _column = searchFieldTransform(class_info->alias, field, selfield);
4397 } // end while -- iterating across SELECT columns
4399 } // end while -- iterating across classes
4401 jsonIteratorFree( selclass_itr );
4404 char* col_list = buffer_release( select_buf );
4406 // Make sure the SELECT list isn't empty. This can happen, for example,
4407 // if we try to build a default SELECT clause from a non-core table.
4410 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4412 osrfAppSessionStatus(
4414 OSRF_STATUS_INTERNALSERVERERROR,
4415 "osrfMethodException",
4417 "SELECT list is empty"
4420 buffer_free( group_buf );
4421 if( defaultselhash )
4422 jsonObjectFree( defaultselhash );
4423 free( join_clause );
4429 table = searchValueTransform( join_hash );
4431 table = strdup( curr_query->core.source_def );
4435 osrfAppSessionStatus(
4437 OSRF_STATUS_INTERNALSERVERERROR,
4438 "osrfMethodException",
4440 "Unable to identify table for core class"
4443 buffer_free( group_buf );
4444 if( defaultselhash )
4445 jsonObjectFree( defaultselhash );
4446 free( join_clause );
4450 // Put it all together
4451 growing_buffer* sql_buf = buffer_init( 128 );
4452 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4456 // Append the join clause, if any
4458 buffer_add(sql_buf, join_clause );
4459 free( join_clause );
4462 char* order_by_list = NULL;
4463 char* having_buf = NULL;
4465 if( !from_function ) {
4467 // Build a WHERE clause, if there is one
4469 buffer_add( sql_buf, " WHERE " );
4471 // and it's on the WHERE clause
4472 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4475 osrfAppSessionStatus(
4477 OSRF_STATUS_INTERNALSERVERERROR,
4478 "osrfMethodException",
4480 "Severe query error in WHERE predicate -- see error log for more details"
4483 buffer_free( group_buf );
4484 buffer_free( sql_buf );
4485 if( defaultselhash )
4486 jsonObjectFree( defaultselhash );
4490 buffer_add( sql_buf, pred );
4494 // Build a HAVING clause, if there is one
4497 // and it's on the the WHERE clause
4498 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4500 if( ! having_buf ) {
4502 osrfAppSessionStatus(
4504 OSRF_STATUS_INTERNALSERVERERROR,
4505 "osrfMethodException",
4507 "Severe query error in HAVING predicate -- see error log for more details"
4510 buffer_free( group_buf );
4511 buffer_free( sql_buf );
4512 if( defaultselhash )
4513 jsonObjectFree( defaultselhash );
4518 // Build an ORDER BY clause, if there is one
4519 if( NULL == order_hash )
4520 ; // No ORDER BY? do nothing
4521 else if( JSON_ARRAY == order_hash->type ) {
4522 order_by_list = buildOrderByFromArray( ctx, order_hash );
4523 if( !order_by_list ) {
4525 buffer_free( group_buf );
4526 buffer_free( sql_buf );
4527 if( defaultselhash )
4528 jsonObjectFree( defaultselhash );
4531 } else if( JSON_HASH == order_hash->type ) {
4532 // This hash is keyed on class alias. Each class has either
4533 // an array of field names or a hash keyed on field name.
4534 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4535 jsonIterator* class_itr = jsonNewIterator( order_hash );
4536 while( (snode = jsonIteratorNext( class_itr )) ) {
4538 ClassInfo* order_class_info = search_alias( class_itr->key );
4539 if( ! order_class_info ) {
4540 osrfLogError( OSRF_LOG_MARK,
4541 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4542 modulename, class_itr->key );
4544 osrfAppSessionStatus(
4546 OSRF_STATUS_INTERNALSERVERERROR,
4547 "osrfMethodException",
4549 "Invalid class referenced in ORDER BY clause -- "
4550 "see error log for more details"
4552 jsonIteratorFree( class_itr );
4553 buffer_free( order_buf );
4555 buffer_free( group_buf );
4556 buffer_free( sql_buf );
4557 if( defaultselhash )
4558 jsonObjectFree( defaultselhash );
4562 osrfHash* field_list_def = order_class_info->fields;
4564 if( snode->type == JSON_HASH ) {
4566 // Hash is keyed on field names from the current class. For each field
4567 // there is another layer of hash to define the sorting details, if any,
4568 // or a string to indicate direction of sorting.
4569 jsonIterator* order_itr = jsonNewIterator( snode );
4570 while( (onode = jsonIteratorNext( order_itr )) ) {
4572 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4574 osrfLogError( OSRF_LOG_MARK,
4575 "%s: Invalid field \"%s\" in ORDER BY clause",
4576 modulename, order_itr->key );
4578 osrfAppSessionStatus(
4580 OSRF_STATUS_INTERNALSERVERERROR,
4581 "osrfMethodException",
4583 "Invalid field in ORDER BY clause -- "
4584 "see error log for more details"
4586 jsonIteratorFree( order_itr );
4587 jsonIteratorFree( class_itr );
4588 buffer_free( order_buf );
4590 buffer_free( group_buf );
4591 buffer_free( sql_buf );
4592 if( defaultselhash )
4593 jsonObjectFree( defaultselhash );
4595 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4596 osrfLogError( OSRF_LOG_MARK,
4597 "%s: Virtual field \"%s\" in ORDER BY clause",
4598 modulename, order_itr->key );
4600 osrfAppSessionStatus(
4602 OSRF_STATUS_INTERNALSERVERERROR,
4603 "osrfMethodException",
4605 "Virtual field in ORDER BY clause -- "
4606 "see error log for more details"
4608 jsonIteratorFree( order_itr );
4609 jsonIteratorFree( class_itr );
4610 buffer_free( order_buf );
4612 buffer_free( group_buf );
4613 buffer_free( sql_buf );
4614 if( defaultselhash )
4615 jsonObjectFree( defaultselhash );
4619 const char* direction = NULL;
4620 if( onode->type == JSON_HASH ) {
4621 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4622 string = searchFieldTransform(
4624 osrfHashGet( field_list_def, order_itr->key ),
4628 if( ctx ) osrfAppSessionStatus(
4630 OSRF_STATUS_INTERNALSERVERERROR,
4631 "osrfMethodException",
4633 "Severe query error in ORDER BY clause -- "
4634 "see error log for more details"
4636 jsonIteratorFree( order_itr );
4637 jsonIteratorFree( class_itr );
4639 buffer_free( group_buf );
4640 buffer_free( order_buf);
4641 buffer_free( sql_buf );
4642 if( defaultselhash )
4643 jsonObjectFree( defaultselhash );
4647 growing_buffer* field_buf = buffer_init( 16 );
4648 buffer_fadd( field_buf, "\"%s\".%s",
4649 class_itr->key, order_itr->key );
4650 string = buffer_release( field_buf );
4653 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4654 const char* dir = jsonObjectGetString( tmp_const );
4655 if(!strncasecmp( dir, "d", 1 )) {
4656 direction = " DESC";
4662 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4663 osrfLogError( OSRF_LOG_MARK,
4664 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4665 modulename, json_type( onode->type ) );
4667 osrfAppSessionStatus(
4669 OSRF_STATUS_INTERNALSERVERERROR,
4670 "osrfMethodException",
4672 "Malformed ORDER BY clause -- see error log for more details"
4674 jsonIteratorFree( order_itr );
4675 jsonIteratorFree( class_itr );
4677 buffer_free( group_buf );
4678 buffer_free( order_buf );
4679 buffer_free( sql_buf );
4680 if( defaultselhash )
4681 jsonObjectFree( defaultselhash );
4685 string = strdup( order_itr->key );
4686 const char* dir = jsonObjectGetString( onode );
4687 if( !strncasecmp( dir, "d", 1 )) {
4688 direction = " DESC";
4695 OSRF_BUFFER_ADD( order_buf, ", " );
4697 order_buf = buffer_init( 128 );
4699 OSRF_BUFFER_ADD( order_buf, string );
4703 OSRF_BUFFER_ADD( order_buf, direction );
4707 jsonIteratorFree( order_itr );
4709 } else if( snode->type == JSON_ARRAY ) {
4711 // Array is a list of fields from the current class
4712 unsigned long order_idx = 0;
4713 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4715 const char* _f = jsonObjectGetString( onode );
4717 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4719 osrfLogError( OSRF_LOG_MARK,
4720 "%s: Invalid field \"%s\" in ORDER BY clause",
4723 osrfAppSessionStatus(
4725 OSRF_STATUS_INTERNALSERVERERROR,
4726 "osrfMethodException",
4728 "Invalid field in ORDER BY clause -- "
4729 "see error log for more details"
4731 jsonIteratorFree( class_itr );
4732 buffer_free( order_buf );
4734 buffer_free( group_buf );
4735 buffer_free( sql_buf );
4736 if( defaultselhash )
4737 jsonObjectFree( defaultselhash );
4739 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4740 osrfLogError( OSRF_LOG_MARK,
4741 "%s: Virtual field \"%s\" in ORDER BY clause",
4744 osrfAppSessionStatus(
4746 OSRF_STATUS_INTERNALSERVERERROR,
4747 "osrfMethodException",
4749 "Virtual field in ORDER BY clause -- "
4750 "see error log for more details"
4752 jsonIteratorFree( class_itr );
4753 buffer_free( order_buf );
4755 buffer_free( group_buf );
4756 buffer_free( sql_buf );
4757 if( defaultselhash )
4758 jsonObjectFree( defaultselhash );
4763 OSRF_BUFFER_ADD( order_buf, ", " );
4765 order_buf = buffer_init( 128 );
4767 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4771 // IT'S THE OOOOOOOOOOOLD STYLE!
4773 osrfLogError( OSRF_LOG_MARK,
4774 "%s: Possible SQL injection attempt; direct order by is not allowed",
4777 osrfAppSessionStatus(
4779 OSRF_STATUS_INTERNALSERVERERROR,
4780 "osrfMethodException",
4782 "Severe query error -- see error log for more details"
4787 buffer_free( group_buf );
4788 buffer_free( order_buf );
4789 buffer_free( sql_buf );
4790 if( defaultselhash )
4791 jsonObjectFree( defaultselhash );
4792 jsonIteratorFree( class_itr );
4796 jsonIteratorFree( class_itr );
4798 order_by_list = buffer_release( order_buf );
4800 osrfLogError( OSRF_LOG_MARK,
4801 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4802 modulename, json_type( order_hash->type ) );
4804 osrfAppSessionStatus(
4806 OSRF_STATUS_INTERNALSERVERERROR,
4807 "osrfMethodException",
4809 "Malformed ORDER BY clause -- see error log for more details"
4812 buffer_free( group_buf );
4813 buffer_free( sql_buf );
4814 if( defaultselhash )
4815 jsonObjectFree( defaultselhash );
4820 string = buffer_release( group_buf );
4822 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4823 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4824 OSRF_BUFFER_ADD( sql_buf, string );
4829 if( having_buf && *having_buf ) {
4830 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4831 OSRF_BUFFER_ADD( sql_buf, having_buf );
4835 if( order_by_list ) {
4837 if( *order_by_list ) {
4838 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4839 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4842 free( order_by_list );
4846 const char* str = jsonObjectGetString( limit );
4847 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4851 const char* str = jsonObjectGetString( offset );
4852 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4855 if( !(flags & SUBSELECT) )
4856 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4858 if( defaultselhash )
4859 jsonObjectFree( defaultselhash );
4861 return buffer_release( sql_buf );
4863 } // end of SELECT()
4866 @brief Build a list of ORDER BY expressions.
4867 @param ctx Pointer to the method context.
4868 @param order_array Pointer to a JSON_ARRAY of field specifications.
4869 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
4870 Each expression may be either a column reference or a function call whose first parameter
4871 is a column reference.
4873 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
4874 It may optionally include entries for "direction" and/or "transform".
4876 The calling code is responsible for freeing the returned string.
4878 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
4879 if( ! order_array ) {
4880 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
4883 osrfAppSessionStatus(
4885 OSRF_STATUS_INTERNALSERVERERROR,
4886 "osrfMethodException",
4888 "Logic error: ORDER BY clause expected, not found; "
4889 "see error log for more details"
4892 } else if( order_array->type != JSON_ARRAY ) {
4893 osrfLogError( OSRF_LOG_MARK,
4894 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
4896 osrfAppSessionStatus(
4898 OSRF_STATUS_INTERNALSERVERERROR,
4899 "osrfMethodException",
4901 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
4905 growing_buffer* order_buf = buffer_init( 128 );
4906 int first = 1; // boolean
4908 jsonObject* order_spec;
4909 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
4911 if( JSON_HASH != order_spec->type ) {
4912 osrfLogError( OSRF_LOG_MARK,
4913 "%s: Malformed field specification in ORDER BY clause; "
4914 "expected JSON_HASH, found %s",
4915 modulename, json_type( order_spec->type ) );
4917 osrfAppSessionStatus(
4919 OSRF_STATUS_INTERNALSERVERERROR,
4920 "osrfMethodException",
4922 "Malformed ORDER BY clause -- see error log for more details"
4924 buffer_free( order_buf );
4928 const char* class_alias =
4929 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
4931 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
4933 if( !field || !class_alias ) {
4934 osrfLogError( OSRF_LOG_MARK,
4935 "%s: Missing class or field name in field specification of ORDER BY clause",
4938 osrfAppSessionStatus(
4940 OSRF_STATUS_INTERNALSERVERERROR,
4941 "osrfMethodException",
4943 "Malformed ORDER BY clause -- see error log for more details"
4945 buffer_free( order_buf );
4949 const ClassInfo* order_class_info = search_alias( class_alias );
4950 if( ! order_class_info ) {
4951 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4952 "not in FROM clause, skipping it", modulename, class_alias );
4956 // Add a separating comma, except at the beginning
4960 OSRF_BUFFER_ADD( order_buf, ", " );
4962 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4964 osrfLogError( OSRF_LOG_MARK,
4965 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4966 modulename, class_alias, field );
4968 osrfAppSessionStatus(
4970 OSRF_STATUS_INTERNALSERVERERROR,
4971 "osrfMethodException",
4973 "Invalid field referenced in ORDER BY clause -- "
4974 "see error log for more details"
4978 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4979 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4980 modulename, field );
4982 osrfAppSessionStatus(
4984 OSRF_STATUS_INTERNALSERVERERROR,
4985 "osrfMethodException",
4987 "Virtual field in ORDER BY clause -- see error log for more details"
4989 buffer_free( order_buf );
4993 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
4994 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
4995 if( ! transform_str ) {
4997 osrfAppSessionStatus(
4999 OSRF_STATUS_INTERNALSERVERERROR,
5000 "osrfMethodException",
5002 "Severe query error in ORDER BY clause -- "
5003 "see error log for more details"
5005 buffer_free( order_buf );
5009 OSRF_BUFFER_ADD( order_buf, transform_str );
5010 free( transform_str );
5013 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5015 const char* direction =
5016 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5018 if( direction[ 0 ] || 'D' == direction[ 0 ] )
5019 OSRF_BUFFER_ADD( order_buf, " DESC" );
5021 OSRF_BUFFER_ADD( order_buf, " ASC" );
5025 return buffer_release( order_buf );
5029 @brief Build a SELECT statement.
5030 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5031 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5032 @param meta Pointer to the class metadata for the core class.
5033 @param ctx Pointer to the method context.
5034 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5036 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5037 "order_by", "limit", and "offset".
5039 The SELECT statements built here are distinct from those built for the json_query method.
5041 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5042 osrfHash* meta, osrfMethodContext* ctx ) {
5044 const char* locale = osrf_message_get_last_locale();
5046 osrfHash* fields = osrfHashGet( meta, "fields" );
5047 const char* core_class = osrfHashGet( meta, "classname" );
5049 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5051 jsonObject* selhash = NULL;
5052 jsonObject* defaultselhash = NULL;
5054 growing_buffer* sql_buf = buffer_init( 128 );
5055 growing_buffer* select_buf = buffer_init( 128 );
5057 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5058 defaultselhash = jsonNewObjectType( JSON_HASH );
5059 selhash = defaultselhash;
5062 // If there's no SELECT list for the core class, build one
5063 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5064 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5066 // Add every non-virtual field to the field list
5067 osrfHash* field_def = NULL;
5068 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5069 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5070 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5071 const char* field = osrfHashIteratorKey( field_itr );
5072 jsonObjectPush( field_list, jsonNewObject( field ) );
5075 osrfHashIteratorFree( field_itr );
5076 jsonObjectSetKey( selhash, core_class, field_list );
5079 // Build a list of columns for the SELECT clause
5081 const jsonObject* snode = NULL;
5082 jsonIterator* class_itr = jsonNewIterator( selhash );
5083 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5085 // If the class isn't in the IDL, ignore it
5086 const char* cname = class_itr->key;
5087 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5091 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5092 if( strcmp( core_class, class_itr->key )) {
5096 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5097 if( !found->size ) {
5098 jsonObjectFree( found );
5102 jsonObjectFree( found );
5105 const jsonObject* node = NULL;
5106 jsonIterator* select_itr = jsonNewIterator( snode );
5107 while( (node = jsonIteratorNext( select_itr )) ) {
5108 const char* item_str = jsonObjectGetString( node );
5109 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5110 char* fname = osrfHashGet( field, "name" );
5118 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5123 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5124 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5127 i18n = osrfHashGet( field, "i18n" );
5129 if( str_is_true( i18n ) ) {
5130 char* pkey = osrfHashGet( idlClass, "primarykey" );
5131 char* tname = osrfHashGet( idlClass, "tablename" );
5133 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5134 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5135 tname, cname, fname, pkey, cname, pkey, locale, fname );
5137 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5140 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5144 jsonIteratorFree( select_itr );
5147 jsonIteratorFree( class_itr );
5149 char* col_list = buffer_release( select_buf );
5150 char* table = oilsGetRelation( meta );
5152 table = strdup( "(null)" );
5154 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5158 // Clear the query stack (as a fail-safe precaution against possible
5159 // leftover garbage); then push the first query frame onto the stack.
5160 clear_query_stack();
5162 if( add_query_core( NULL, core_class ) ) {
5164 osrfAppSessionStatus(
5166 OSRF_STATUS_INTERNALSERVERERROR,
5167 "osrfMethodException",
5169 "Unable to build query frame for core class"
5171 buffer_free( sql_buf );
5172 if( defaultselhash )
5173 jsonObjectFree( defaultselhash );
5177 // Add the JOIN clauses, if any
5179 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5180 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5181 OSRF_BUFFER_ADD( sql_buf, join_clause );
5182 free( join_clause );
5185 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5186 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5188 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5190 // Add the conditions in the WHERE clause
5191 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5193 osrfAppSessionStatus(
5195 OSRF_STATUS_INTERNALSERVERERROR,
5196 "osrfMethodException",
5198 "Severe query error -- see error log for more details"
5200 buffer_free( sql_buf );
5201 if( defaultselhash )
5202 jsonObjectFree( defaultselhash );
5203 clear_query_stack();
5206 buffer_add( sql_buf, pred );
5210 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5211 if( rest_of_query ) {
5212 const jsonObject* order_by = NULL;
5213 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5215 char* order_by_list = NULL;
5217 if( JSON_ARRAY == order_by->type ) {
5218 order_by_list = buildOrderByFromArray( ctx, order_by );
5219 if( !order_by_list ) {
5220 buffer_free( sql_buf );
5221 if( defaultselhash )
5222 jsonObjectFree( defaultselhash );
5223 clear_query_stack();
5226 } else if( JSON_HASH == order_by->type ) {
5227 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5228 // and build a list of ORDER BY expressions.
5229 growing_buffer* order_buf = buffer_init( 128 );
5231 jsonIterator* class_itr = jsonNewIterator( order_by );
5232 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5234 ClassInfo* order_class_info = search_alias( class_itr->key );
5235 if( ! order_class_info )
5236 continue; // class not referenced by FROM clause? Ignore it.
5238 if( JSON_HASH == snode->type ) {
5240 // If the data for the current class is a JSON_HASH, then it is
5241 // keyed on field name.
5243 const jsonObject* onode = NULL;
5244 jsonIterator* order_itr = jsonNewIterator( snode );
5245 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5247 osrfHash* field_def = osrfHashGet(
5248 order_class_info->fields, order_itr->key );
5250 continue; // Field not defined in IDL? Ignore it.
5251 if( str_is_true( osrfHashGet( field_def, "virtual")))
5252 continue; // Field is virtual? Ignore it.
5254 char* field_str = NULL;
5255 char* direction = NULL;
5256 if( onode->type == JSON_HASH ) {
5257 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5258 field_str = searchFieldTransform(
5259 class_itr->key, field_def, onode );
5261 osrfAppSessionStatus(
5263 OSRF_STATUS_INTERNALSERVERERROR,
5264 "osrfMethodException",
5266 "Severe query error in ORDER BY clause -- "
5267 "see error log for more details"
5269 jsonIteratorFree( order_itr );
5270 jsonIteratorFree( class_itr );
5271 buffer_free( order_buf );
5272 buffer_free( sql_buf );
5273 if( defaultselhash )
5274 jsonObjectFree( defaultselhash );
5275 clear_query_stack();
5279 growing_buffer* field_buf = buffer_init( 16 );
5280 buffer_fadd( field_buf, "\"%s\".%s",
5281 class_itr->key, order_itr->key );
5282 field_str = buffer_release( field_buf );
5285 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5286 const char* dir = jsonObjectGetString( order_by );
5287 if(!strncasecmp( dir, "d", 1 )) {
5288 direction = " DESC";
5292 field_str = strdup( order_itr->key );
5293 const char* dir = jsonObjectGetString( onode );
5294 if( !strncasecmp( dir, "d", 1 )) {
5295 direction = " DESC";
5304 buffer_add( order_buf, ", " );
5307 buffer_add( order_buf, field_str );
5311 buffer_add( order_buf, direction );
5313 } // end while; looping over ORDER BY expressions
5315 jsonIteratorFree( order_itr );
5317 } else if( JSON_STRING == snode->type ) {
5318 // We expect a comma-separated list of sort fields.
5319 const char* str = jsonObjectGetString( snode );
5320 if( strchr( str, ';' )) {
5321 // No semicolons allowed. It is theoretically possible for a
5322 // legitimate semicolon to occur within quotes, but it's not likely
5323 // to occur in practice in the context of an ORDER BY list.
5324 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5325 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5327 osrfAppSessionStatus(
5329 OSRF_STATUS_INTERNALSERVERERROR,
5330 "osrfMethodException",
5332 "Possible attempt at SOL injection -- "
5333 "semicolon found in ORDER BY list"
5336 jsonIteratorFree( class_itr );
5337 buffer_free( order_buf );
5338 buffer_free( sql_buf );
5339 if( defaultselhash )
5340 jsonObjectFree( defaultselhash );
5341 clear_query_stack();
5344 buffer_add( order_buf, str );
5348 } // end while; looping over order_by classes
5350 jsonIteratorFree( class_itr );
5351 order_by_list = buffer_release( order_buf );
5354 osrfLogWarning( OSRF_LOG_MARK,
5355 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5356 "no ORDER BY generated" );
5359 if( order_by_list && *order_by_list ) {
5360 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5361 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5364 free( order_by_list );
5367 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5369 const char* str = jsonObjectGetString( limit );
5377 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5379 const char* str = jsonObjectGetString( offset );
5388 if( defaultselhash )
5389 jsonObjectFree( defaultselhash );
5390 clear_query_stack();
5392 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5393 return buffer_release( sql_buf );
5396 int doJSONSearch ( osrfMethodContext* ctx ) {
5397 if(osrfMethodVerifyContext( ctx )) {
5398 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5402 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5406 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5410 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5411 flags |= SELECT_DISTINCT;
5413 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5414 flags |= DISABLE_I18N;
5416 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5417 clear_query_stack(); // a possibly needless precaution
5418 char* sql = buildQuery( ctx, hash, flags );
5419 clear_query_stack();
5426 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5429 dbhandle = writehandle;
5431 dbi_result result = dbi_conn_query( dbhandle, sql );
5434 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5436 if( dbi_result_first_row( result )) {
5437 /* JSONify the result */
5438 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5441 jsonObject* return_val = oilsMakeJSONFromResult( result );
5442 osrfAppRespond( ctx, return_val );
5443 jsonObjectFree( return_val );
5444 } while( dbi_result_next_row( result ));
5447 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5450 osrfAppRespondComplete( ctx, NULL );
5452 /* clean up the query */
5453 dbi_result_free( result );
5458 int errnum = dbi_conn_error( dbhandle, &msg );
5459 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5460 modulename, sql, errnum, msg ? msg : "(No description available)" );
5461 osrfAppSessionStatus(
5463 OSRF_STATUS_INTERNALSERVERERROR,
5464 "osrfMethodException",
5466 "Severe query error -- see error log for more details"
5468 if( !oilsIsDBConnected( dbhandle ))
5469 osrfAppSessionPanic( ctx->session );
5476 // The last parameter, err, is used to report an error condition by updating an int owned by
5477 // the calling code.
5479 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5480 // It is the responsibility of the calling code to initialize *err before the
5481 // call, so that it will be able to make sense of the result.
5483 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5484 // redundant anyway.
5485 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5486 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5489 dbhandle = writehandle;
5491 char* core_class = osrfHashGet( class_meta, "classname" );
5492 char* pkey = osrfHashGet( class_meta, "primarykey" );
5494 const jsonObject* _tmp;
5496 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5498 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5503 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5505 dbi_result result = dbi_conn_query( dbhandle, sql );
5506 if( NULL == result ) {
5508 int errnum = dbi_conn_error( dbhandle, &msg );
5509 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5510 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5511 msg ? msg : "(No description available)" );
5512 if( !oilsIsDBConnected( dbhandle ))
5513 osrfAppSessionPanic( ctx->session );
5514 osrfAppSessionStatus(
5516 OSRF_STATUS_INTERNALSERVERERROR,
5517 "osrfMethodException",
5519 "Severe query error -- see error log for more details"
5526 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5529 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5530 jsonObject* row_obj = NULL;
5532 if( dbi_result_first_row( result )) {
5534 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5535 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5536 // eliminate the duplicates.
5537 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5538 osrfHash* dedup = osrfNewHash();
5540 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5541 char* pkey_val = oilsFMGetString( row_obj, pkey );
5542 if( osrfHashGet( dedup, pkey_val ) ) {
5543 jsonObjectFree( row_obj );
5546 osrfHashSet( dedup, pkey_val, pkey_val );
5547 jsonObjectPush( res_list, row_obj );
5549 } while( dbi_result_next_row( result ));
5550 osrfHashFree( dedup );
5553 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5557 /* clean up the query */
5558 dbi_result_free( result );
5561 // If we're asked to flesh, and there's anything to flesh, then flesh it
5562 // (but not for PCRUD, lest the user to bypass permissions by fleshing
5563 // something that he has no permission to look at).
5564 if( res_list->size && query_hash && ! enforce_pcrud ) {
5565 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5567 // Get the flesh depth
5568 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5569 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5570 flesh_depth = max_flesh_depth;
5572 // We need a non-zero flesh depth, and a list of fields to flesh
5573 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5574 if( temp_blob && flesh_depth > 0 ) {
5576 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5577 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5579 osrfStringArray* link_fields = NULL;
5580 osrfHash* links = osrfHashGet( class_meta, "links" );
5582 // Make an osrfStringArray of the names of fields to be fleshed
5583 if( flesh_fields ) {
5584 if( flesh_fields->size == 1 ) {
5585 const char* _t = jsonObjectGetString(
5586 jsonObjectGetIndex( flesh_fields, 0 ) );
5587 if( !strcmp( _t, "*" ))
5588 link_fields = osrfHashKeys( links );
5591 if( !link_fields ) {
5593 link_fields = osrfNewStringArray( 1 );
5594 jsonIterator* _i = jsonNewIterator( flesh_fields );
5595 while ((_f = jsonIteratorNext( _i ))) {
5596 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5598 jsonIteratorFree( _i );
5602 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5604 // Iterate over the JSON_ARRAY of rows
5606 unsigned long res_idx = 0;
5607 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5610 const char* link_field;
5612 // Iterate over the list of fleshable fields
5613 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5615 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5617 osrfHash* kid_link = osrfHashGet( links, link_field );
5619 continue; // Not a link field; skip it
5621 osrfHash* field = osrfHashGet( fields, link_field );
5623 continue; // Not a field at all; skip it (IDL is ill-formed)
5625 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5626 osrfHashGet( kid_link, "class" ));
5628 continue; // The class it links to doesn't exist; skip it
5630 const char* reltype = osrfHashGet( kid_link, "reltype" );
5632 continue; // No reltype; skip it (IDL is ill-formed)
5634 osrfHash* value_field = field;
5636 if( !strcmp( reltype, "has_many" )
5637 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5638 value_field = osrfHashGet(
5639 fields, osrfHashGet( class_meta, "primarykey" ) );
5642 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5644 if( link_map->size > 0 ) {
5645 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5648 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5653 osrfHashGet( kid_link, "class" ),
5660 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5661 osrfHashGet( kid_link, "field" ),
5662 osrfHashGet( kid_link, "class" ),
5663 osrfHashGet( kid_link, "key" ),
5664 osrfHashGet( kid_link, "reltype" )
5667 const char* search_key = jsonObjectGetString(
5668 jsonObjectGetIndex( cur,
5669 atoi( osrfHashGet( value_field, "array_position" ) )
5674 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5678 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5680 // construct WHERE clause
5681 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5684 osrfHashGet( kid_link, "key" ),
5685 jsonNewObject( search_key )
5688 // construct the rest of the query, mostly
5689 // by copying pieces of the previous level of query
5690 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5691 jsonObjectSetKey( rest_of_query, "flesh",
5692 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5696 jsonObjectSetKey( rest_of_query, "flesh_fields",
5697 jsonObjectClone( flesh_blob ));
5699 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5700 jsonObjectSetKey( rest_of_query, "order_by",
5701 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5705 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5706 jsonObjectSetKey( rest_of_query, "select",
5707 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5711 // do the query, recursively, to expand the fleshable field
5712 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5713 where_clause, rest_of_query, err );
5715 jsonObjectFree( where_clause );
5716 jsonObjectFree( rest_of_query );
5719 osrfStringArrayFree( link_fields );
5720 jsonObjectFree( res_list );
5721 jsonObjectFree( flesh_blob );
5725 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5726 osrfHashGet( kid_link, "class" ), kids->size );
5728 // Traverse the result set
5729 jsonObject* X = NULL;
5730 if( link_map->size > 0 && kids->size > 0 ) {
5732 kids = jsonNewObjectType( JSON_ARRAY );
5734 jsonObject* _k_node;
5735 unsigned long res_idx = 0;
5736 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5742 (unsigned long) atoi(
5748 osrfHashGet( kid_link, "class" )
5752 osrfStringArrayGetString( link_map, 0 )
5760 } // end while loop traversing X
5763 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5764 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5765 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5766 osrfHashGet( kid_link, "field" ));
5769 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5770 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5774 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5776 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5777 osrfHashGet( kid_link, "field" ) );
5780 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5781 jsonObjectClone( kids )
5786 jsonObjectFree( kids );
5790 jsonObjectFree( kids );
5792 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5793 osrfHashGet( kid_link, "field" ) );
5794 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5796 } // end while loop traversing list of fleshable fields
5797 } // end while loop traversing res_list
5798 jsonObjectFree( flesh_blob );
5799 osrfStringArrayFree( link_fields );
5808 int doUpdate( osrfMethodContext* ctx ) {
5809 if( osrfMethodVerifyContext( ctx )) {
5810 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5815 timeout_needs_resetting = 1;
5817 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5819 jsonObject* target = NULL;
5821 target = jsonObjectGetIndex( ctx->params, 1 );
5823 target = jsonObjectGetIndex( ctx->params, 0 );
5825 if(!verifyObjectClass( ctx, target )) {
5826 osrfAppRespondComplete( ctx, NULL );
5830 if( getXactId( ctx ) == NULL ) {
5831 osrfAppSessionStatus(
5833 OSRF_STATUS_BADREQUEST,
5834 "osrfMethodException",
5836 "No active transaction -- required for UPDATE"
5838 osrfAppRespondComplete( ctx, NULL );
5842 // The following test is harmless but redundant. If a class is
5843 // readonly, we don't register an update method for it.
5844 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5845 osrfAppSessionStatus(
5847 OSRF_STATUS_BADREQUEST,
5848 "osrfMethodException",
5850 "Cannot UPDATE readonly class"
5852 osrfAppRespondComplete( ctx, NULL );
5856 const char* trans_id = getXactId( ctx );
5858 // Set the last_xact_id
5859 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5861 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5862 trans_id, target->classname, index );
5863 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5866 char* pkey = osrfHashGet( meta, "primarykey" );
5867 osrfHash* fields = osrfHashGet( meta, "fields" );
5869 char* id = oilsFMGetString( target, pkey );
5873 "%s updating %s object with %s = %s",
5875 osrfHashGet( meta, "fieldmapper" ),
5880 dbhandle = writehandle;
5881 growing_buffer* sql = buffer_init( 128 );
5882 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5885 osrfHash* field_def = NULL;
5886 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5887 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5889 // Skip virtual fields, and the primary key
5890 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5893 const char* field_name = osrfHashIteratorKey( field_itr );
5894 if( ! strcmp( field_name, pkey ) )
5897 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5899 int value_is_numeric = 0; // boolean
5901 if( field_object && field_object->classname ) {
5902 value = oilsFMGetString(
5904 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5906 } else if( field_object && JSON_BOOL == field_object->type ) {
5907 if( jsonBoolIsTrue( field_object ) )
5908 value = strdup( "t" );
5910 value = strdup( "f" );
5912 value = jsonObjectToSimpleString( field_object );
5913 if( field_object && JSON_NUMBER == field_object->type )
5914 value_is_numeric = 1;
5917 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5918 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5920 if( !field_object || field_object->type == JSON_NULL ) {
5921 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5922 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5926 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5927 buffer_fadd( sql, " %s = NULL", field_name );
5930 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5934 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5936 const char* numtype = get_datatype( field_def );
5937 if( !strncmp( numtype, "INT", 3 ) ) {
5938 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5939 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5940 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5942 // Must really be intended as a string, so quote it
5943 if( dbi_conn_quote_string( dbhandle, &value )) {
5944 buffer_fadd( sql, " %s = %s", field_name, value );
5946 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5947 modulename, value );
5948 osrfAppSessionStatus(
5950 OSRF_STATUS_INTERNALSERVERERROR,
5951 "osrfMethodException",
5953 "Error quoting string -- please see the error log for more details"
5957 osrfHashIteratorFree( field_itr );
5959 osrfAppRespondComplete( ctx, NULL );
5964 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5967 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5971 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5972 buffer_fadd( sql, " %s = %s", field_name, value );
5974 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5975 osrfAppSessionStatus(
5977 OSRF_STATUS_INTERNALSERVERERROR,
5978 "osrfMethodException",
5980 "Error quoting string -- please see the error log for more details"
5984 osrfHashIteratorFree( field_itr );
5986 osrfAppRespondComplete( ctx, NULL );
5995 osrfHashIteratorFree( field_itr );
5997 jsonObject* obj = jsonNewObject( id );
5999 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6000 dbi_conn_quote_string( dbhandle, &id );
6002 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6004 char* query = buffer_release( sql );
6005 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6007 dbi_result result = dbi_conn_query( dbhandle, query );
6012 jsonObjectFree( obj );
6013 obj = jsonNewObject( NULL );
6015 int errnum = dbi_conn_error( dbhandle, &msg );
6018 "%s ERROR updating %s object with %s = %s: %d %s",
6020 osrfHashGet( meta, "fieldmapper" ),
6024 msg ? msg : "(No description available)"
6026 osrfAppSessionStatus(
6028 OSRF_STATUS_INTERNALSERVERERROR,
6029 "osrfMethodException",
6031 "Error in updating a row -- please see the error log for more details"
6033 if( !oilsIsDBConnected( dbhandle ))
6034 osrfAppSessionPanic( ctx->session );
6037 dbi_result_free( result );
6040 osrfAppRespondComplete( ctx, obj );
6041 jsonObjectFree( obj );
6045 int doDelete( osrfMethodContext* ctx ) {
6046 if( osrfMethodVerifyContext( ctx )) {
6047 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6052 timeout_needs_resetting = 1;
6054 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6056 if( getXactId( ctx ) == NULL ) {
6057 osrfAppSessionStatus(
6059 OSRF_STATUS_BADREQUEST,
6060 "osrfMethodException",
6062 "No active transaction -- required for DELETE"
6064 osrfAppRespondComplete( ctx, NULL );
6068 // The following test is harmless but redundant. If a class is
6069 // readonly, we don't register a delete method for it.
6070 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6071 osrfAppSessionStatus(
6073 OSRF_STATUS_BADREQUEST,
6074 "osrfMethodException",
6076 "Cannot DELETE readonly class"
6078 osrfAppRespondComplete( ctx, NULL );
6082 dbhandle = writehandle;
6084 char* pkey = osrfHashGet( meta, "primarykey" );
6091 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6092 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6093 osrfAppRespondComplete( ctx, NULL );
6097 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6099 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL, 1 )) {
6100 osrfAppRespondComplete( ctx, NULL );
6103 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6108 "%s deleting %s object with %s = %s",
6110 osrfHashGet( meta, "fieldmapper" ),
6115 jsonObject* obj = jsonNewObject( id );
6117 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6118 dbi_conn_quote_string( writehandle, &id );
6120 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6121 osrfHashGet( meta, "tablename" ), pkey, id );
6126 jsonObjectFree( obj );
6127 obj = jsonNewObject( NULL );
6129 int errnum = dbi_conn_error( writehandle, &msg );
6132 "%s ERROR deleting %s object with %s = %s: %d %s",
6134 osrfHashGet( meta, "fieldmapper" ),
6138 msg ? msg : "(No description available)"
6140 osrfAppSessionStatus(
6142 OSRF_STATUS_INTERNALSERVERERROR,
6143 "osrfMethodException",
6145 "Error in deleting a row -- please see the error log for more details"
6147 if( !oilsIsDBConnected( writehandle ))
6148 osrfAppSessionPanic( ctx->session );
6150 dbi_result_free( result );
6154 osrfAppRespondComplete( ctx, obj );
6155 jsonObjectFree( obj );
6160 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6161 @param result An iterator for a result set; we only look at the current row.
6162 @param @meta Pointer to the class metadata for the core class.
6163 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6165 If a column is not defined in the IDL, or if it has no array_position defined for it in
6166 the IDL, or if it is defined as virtual, ignore it.
6168 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6169 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6170 array_position in the IDL.
6172 A field defined in the IDL but not represented in the returned row will leave a hole
6173 in the JSON_ARRAY. In effect it will be treated as a null value.
6175 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6176 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6177 classname corresponding to the @a meta argument.
6179 The calling code is responsible for freeing the the resulting jsonObject by calling
6182 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6183 if( !( result && meta )) return NULL;
6185 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6186 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6187 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6189 osrfHash* fields = osrfHashGet( meta, "fields" );
6191 int columnIndex = 1;
6192 const char* columnName;
6194 /* cycle through the columns in the row returned from the database */
6195 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6197 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6199 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6201 /* determine the field type and storage attributes */
6202 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6203 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6205 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6206 // or if it has no sequence number there, or if it's virtual, skip it.
6207 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6210 if( str_is_true( osrfHashGet( _f, "virtual" )))
6211 continue; // skip this column: IDL says it's virtual
6213 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6214 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6215 continue; // since we assign sequence numbers dynamically as we load the IDL.
6217 fmIndex = atoi( pos );
6218 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6220 continue; // This field is not defined in the IDL
6223 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6224 // sequence number from the IDL (which is likely to be different from the sequence
6225 // of columns in the SELECT clause).
6226 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6227 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6232 case DBI_TYPE_INTEGER :
6234 if( attr & DBI_INTEGER_SIZE8 )
6235 jsonObjectSetIndex( object, fmIndex,
6236 jsonNewNumberObject(
6237 dbi_result_get_longlong_idx( result, columnIndex )));
6239 jsonObjectSetIndex( object, fmIndex,
6240 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6244 case DBI_TYPE_DECIMAL :
6245 jsonObjectSetIndex( object, fmIndex,
6246 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6249 case DBI_TYPE_STRING :
6254 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6259 case DBI_TYPE_DATETIME : {
6261 char dt_string[ 256 ] = "";
6264 // Fetch the date column as a time_t
6265 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6267 // Translate the time_t to a human-readable string
6268 if( !( attr & DBI_DATETIME_DATE )) {
6269 gmtime_r( &_tmp_dt, &gmdt );
6270 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6271 } else if( !( attr & DBI_DATETIME_TIME )) {
6272 localtime_r( &_tmp_dt, &gmdt );
6273 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6275 localtime_r( &_tmp_dt, &gmdt );
6276 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6279 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6283 case DBI_TYPE_BINARY :
6284 osrfLogError( OSRF_LOG_MARK,
6285 "Can't do binary at column %s : index %d", columnName, columnIndex );
6294 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6295 if( !result ) return NULL;
6297 jsonObject* object = jsonNewObject( NULL );
6300 char dt_string[ 256 ];
6304 int columnIndex = 1;
6306 unsigned short type;
6307 const char* columnName;
6309 /* cycle through the column list */
6310 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6312 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6314 fmIndex = -1; // reset the position
6316 /* determine the field type and storage attributes */
6317 type = dbi_result_get_field_type_idx( result, columnIndex );
6318 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6320 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6321 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6326 case DBI_TYPE_INTEGER :
6328 if( attr & DBI_INTEGER_SIZE8 )
6329 jsonObjectSetKey( object, columnName,
6330 jsonNewNumberObject( dbi_result_get_longlong_idx(
6331 result, columnIndex )) );
6333 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6334 dbi_result_get_int_idx( result, columnIndex )) );
6337 case DBI_TYPE_DECIMAL :
6338 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6339 dbi_result_get_double_idx( result, columnIndex )) );
6342 case DBI_TYPE_STRING :
6343 jsonObjectSetKey( object, columnName,
6344 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6347 case DBI_TYPE_DATETIME :
6349 memset( dt_string, '\0', sizeof( dt_string ));
6350 memset( &gmdt, '\0', sizeof( gmdt ));
6352 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6354 if( !( attr & DBI_DATETIME_DATE )) {
6355 gmtime_r( &_tmp_dt, &gmdt );
6356 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6357 } else if( !( attr & DBI_DATETIME_TIME )) {
6358 localtime_r( &_tmp_dt, &gmdt );
6359 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6361 localtime_r( &_tmp_dt, &gmdt );
6362 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6365 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6368 case DBI_TYPE_BINARY :
6369 osrfLogError( OSRF_LOG_MARK,
6370 "Can't do binary at column %s : index %d", columnName, columnIndex );
6374 } // end while loop traversing result
6379 // Interpret a string as true or false
6380 int str_is_true( const char* str ) {
6381 if( NULL == str || strcasecmp( str, "true" ) )
6387 // Interpret a jsonObject as true or false
6388 static int obj_is_true( const jsonObject* obj ) {
6391 else switch( obj->type )
6399 if( strcasecmp( obj->value.s, "true" ) )
6403 case JSON_NUMBER : // Support 1/0 for perl's sake
6404 if( jsonObjectGetNumber( obj ) == 1.0 )
6413 // Translate a numeric code into a text string identifying a type of
6414 // jsonObject. To be used for building error messages.
6415 static const char* json_type( int code ) {
6421 return "JSON_ARRAY";
6423 return "JSON_STRING";
6425 return "JSON_NUMBER";
6431 return "(unrecognized)";
6435 // Extract the "primitive" attribute from an IDL field definition.
6436 // If we haven't initialized the app, then we must be running in
6437 // some kind of testbed. In that case, default to "string".
6438 static const char* get_primitive( osrfHash* field ) {
6439 const char* s = osrfHashGet( field, "primitive" );
6441 if( child_initialized )
6444 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6446 osrfHashGet( field, "name" )
6454 // Extract the "datatype" attribute from an IDL field definition.
6455 // If we haven't initialized the app, then we must be running in
6456 // some kind of testbed. In that case, default to to NUMERIC,
6457 // since we look at the datatype only for numbers.
6458 static const char* get_datatype( osrfHash* field ) {
6459 const char* s = osrfHashGet( field, "datatype" );
6461 if( child_initialized )
6464 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6466 osrfHashGet( field, "name" )
6475 @brief Determine whether a string is potentially a valid SQL identifier.
6476 @param s The identifier to be tested.
6477 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6479 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6480 need to follow all the rules exactly, such as requiring that the first character not
6483 We allow leading and trailing white space. In between, we do not allow punctuation
6484 (except for underscores and dollar signs), control characters, or embedded white space.
6486 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6487 for the foreseeable future such quoted identifiers are not likely to be an issue.
6489 int is_identifier( const char* s) {
6493 // Skip leading white space
6494 while( isspace( (unsigned char) *s ) )
6498 return 0; // Nothing but white space? Not okay.
6500 // Check each character until we reach white space or
6501 // end-of-string. Letters, digits, underscores, and
6502 // dollar signs are okay. With the exception of periods
6503 // (as in schema.identifier), control characters and other
6504 // punctuation characters are not okay. Anything else
6505 // is okay -- it could for example be part of a multibyte
6506 // UTF8 character such as a letter with diacritical marks,
6507 // and those are allowed.
6509 if( isalnum( (unsigned char) *s )
6513 ; // Fine; keep going
6514 else if( ispunct( (unsigned char) *s )
6515 || iscntrl( (unsigned char) *s ) )
6518 } while( *s && ! isspace( (unsigned char) *s ) );
6520 // If we found any white space in the above loop,
6521 // the rest had better be all white space.
6523 while( isspace( (unsigned char) *s ) )
6527 return 0; // White space was embedded within non-white space
6533 @brief Determine whether to accept a character string as a comparison operator.
6534 @param op The candidate comparison operator.
6535 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6537 We don't validate the operator for real. We just make sure that it doesn't contain
6538 any semicolons or white space (with special exceptions for a few specific operators).
6539 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6540 space but it's still not a valid operator, then the database will complain.
6542 Another approach would be to compare the string against a short list of approved operators.
6543 We don't do that because we want to allow custom operators like ">100*", which at this
6544 writing would be difficult or impossible to express otherwise in a JSON query.
6546 int is_good_operator( const char* op ) {
6547 if( !op ) return 0; // Sanity check
6551 if( isspace( (unsigned char) *s ) ) {
6552 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6553 // and IS NOT DISTINCT FROM.
6554 if( !strcasecmp( op, "similar to" ) )
6556 else if( !strcasecmp( op, "is distinct from" ) )
6558 else if( !strcasecmp( op, "is not distinct from" ) )
6563 else if( ';' == *s )
6571 @name Query Frame Management
6573 The following machinery supports a stack of query frames for use by SELECT().
6575 A query frame caches information about one level of a SELECT query. When we enter
6576 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6578 The query frame stores information about the core class, and about any joined classes
6581 The main purpose is to map table aliases to classes and tables, so that a query can
6582 join to the same table more than once. A secondary goal is to reduce the number of
6583 lookups in the IDL by caching the results.
6587 #define STATIC_CLASS_INFO_COUNT 3
6589 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6592 @brief Allocate a ClassInfo as raw memory.
6593 @return Pointer to the newly allocated ClassInfo.
6595 Except for the in_use flag, which is used only by the allocation and deallocation
6596 logic, we don't initialize the ClassInfo here.
6598 static ClassInfo* allocate_class_info( void ) {
6599 // In order to reduce the number of mallocs and frees, we return a static
6600 // instance of ClassInfo, if we can find one that we're not already using.
6601 // We rely on the fact that the compiler will implicitly initialize the
6602 // static instances so that in_use == 0.
6605 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6606 if( ! static_class_info[ i ].in_use ) {
6607 static_class_info[ i ].in_use = 1;
6608 return static_class_info + i;
6612 // The static ones are all in use. Malloc one.
6614 return safe_malloc( sizeof( ClassInfo ) );
6618 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6619 @param info Pointer to the ClassInfo to be cleared.
6621 static void clear_class_info( ClassInfo* info ) {
6626 // Free any malloc'd strings
6628 if( info->alias != info->alias_store )
6629 free( info->alias );
6631 if( info->class_name != info->class_name_store )
6632 free( info->class_name );
6634 free( info->source_def );
6636 info->alias = info->class_name = info->source_def = NULL;
6641 @brief Free a ClassInfo and everything it owns.
6642 @param info Pointer to the ClassInfo to be freed.
6644 static void free_class_info( ClassInfo* info ) {
6649 clear_class_info( info );
6651 // If it's one of the static instances, just mark it as not in use
6654 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6655 if( info == static_class_info + i ) {
6656 static_class_info[ i ].in_use = 0;
6661 // Otherwise it must have been malloc'd, so free it
6667 @brief Populate an already-allocated ClassInfo.
6668 @param info Pointer to the ClassInfo to be populated.
6669 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6671 @param class Name of the class.
6672 @return Zero if successful, or 1 if not.
6674 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6675 the relevant portions of the IDL for the specified class.
6677 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6680 osrfLogError( OSRF_LOG_MARK,
6681 "%s ERROR: No ClassInfo available to populate", modulename );
6682 info->alias = info->class_name = info->source_def = NULL;
6683 info->class_def = info->fields = info->links = NULL;
6688 osrfLogError( OSRF_LOG_MARK,
6689 "%s ERROR: No class name provided for lookup", modulename );
6690 info->alias = info->class_name = info->source_def = NULL;
6691 info->class_def = info->fields = info->links = NULL;
6695 // Alias defaults to class name if not supplied
6696 if( ! alias || ! alias[ 0 ] )
6699 // Look up class info in the IDL
6700 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6702 osrfLogError( OSRF_LOG_MARK,
6703 "%s ERROR: Class %s not defined in IDL", modulename, class );
6704 info->alias = info->class_name = info->source_def = NULL;
6705 info->class_def = info->fields = info->links = NULL;
6707 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6708 osrfLogError( OSRF_LOG_MARK,
6709 "%s ERROR: Class %s is defined as virtual", modulename, class );
6710 info->alias = info->class_name = info->source_def = NULL;
6711 info->class_def = info->fields = info->links = NULL;
6715 osrfHash* links = osrfHashGet( class_def, "links" );
6717 osrfLogError( OSRF_LOG_MARK,
6718 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6719 info->alias = info->class_name = info->source_def = NULL;
6720 info->class_def = info->fields = info->links = NULL;
6724 osrfHash* fields = osrfHashGet( class_def, "fields" );
6726 osrfLogError( OSRF_LOG_MARK,
6727 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6728 info->alias = info->class_name = info->source_def = NULL;
6729 info->class_def = info->fields = info->links = NULL;
6733 char* source_def = oilsGetRelation( class_def );
6737 // We got everything we need, so populate the ClassInfo
6738 if( strlen( alias ) > ALIAS_STORE_SIZE )
6739 info->alias = strdup( alias );
6741 strcpy( info->alias_store, alias );
6742 info->alias = info->alias_store;
6745 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6746 info->class_name = strdup( class );
6748 strcpy( info->class_name_store, class );
6749 info->class_name = info->class_name_store;
6752 info->source_def = source_def;
6754 info->class_def = class_def;
6755 info->links = links;
6756 info->fields = fields;
6761 #define STATIC_FRAME_COUNT 3
6763 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6766 @brief Allocate a QueryFrame as raw memory.
6767 @return Pointer to the newly allocated QueryFrame.
6769 Except for the in_use flag, which is used only by the allocation and deallocation
6770 logic, we don't initialize the QueryFrame here.
6772 static QueryFrame* allocate_frame( void ) {
6773 // In order to reduce the number of mallocs and frees, we return a static
6774 // instance of QueryFrame, if we can find one that we're not already using.
6775 // We rely on the fact that the compiler will implicitly initialize the
6776 // static instances so that in_use == 0.
6779 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6780 if( ! static_frame[ i ].in_use ) {
6781 static_frame[ i ].in_use = 1;
6782 return static_frame + i;
6786 // The static ones are all in use. Malloc one.
6788 return safe_malloc( sizeof( QueryFrame ) );
6792 @brief Free a QueryFrame, and all the memory it owns.
6793 @param frame Pointer to the QueryFrame to be freed.
6795 static void free_query_frame( QueryFrame* frame ) {
6800 clear_class_info( &frame->core );
6802 // Free the join list
6804 ClassInfo* info = frame->join_list;
6807 free_class_info( info );
6811 frame->join_list = NULL;
6814 // If the frame is a static instance, just mark it as unused
6816 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6817 if( frame == static_frame + i ) {
6818 static_frame[ i ].in_use = 0;
6823 // Otherwise it must have been malloc'd, so free it
6829 @brief Search a given QueryFrame for a specified alias.
6830 @param frame Pointer to the QueryFrame to be searched.
6831 @param target The alias for which to search.
6832 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6834 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6835 if( ! frame || ! target ) {
6839 ClassInfo* found_class = NULL;
6841 if( !strcmp( target, frame->core.alias ) )
6842 return &(frame->core);
6844 ClassInfo* curr_class = frame->join_list;
6845 while( curr_class ) {
6846 if( strcmp( target, curr_class->alias ) )
6847 curr_class = curr_class->next;
6849 found_class = curr_class;
6859 @brief Push a new (blank) QueryFrame onto the stack.
6861 static void push_query_frame( void ) {
6862 QueryFrame* frame = allocate_frame();
6863 frame->join_list = NULL;
6864 frame->next = curr_query;
6866 // Initialize the ClassInfo for the core class
6867 ClassInfo* core = &frame->core;
6868 core->alias = core->class_name = core->source_def = NULL;
6869 core->class_def = core->fields = core->links = NULL;
6875 @brief Pop a QueryFrame off the stack and destroy it.
6877 static void pop_query_frame( void ) {
6882 QueryFrame* popped = curr_query;
6883 curr_query = popped->next;
6885 free_query_frame( popped );
6889 @brief Populate the ClassInfo for the core class.
6890 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6891 class name as an alias.
6892 @param class_name Name of the core class.
6893 @return Zero if successful, or 1 if not.
6895 Populate the ClassInfo of the core class with copies of the alias and class name, and
6896 with pointers to the relevant portions of the IDL for the core class.
6898 static int add_query_core( const char* alias, const char* class_name ) {
6901 if( ! curr_query ) {
6902 osrfLogError( OSRF_LOG_MARK,
6903 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6905 } else if( curr_query->core.alias ) {
6906 osrfLogError( OSRF_LOG_MARK,
6907 "%s ERROR: Core class %s already populated as %s",
6908 modulename, curr_query->core.class_name, curr_query->core.alias );
6912 build_class_info( &curr_query->core, alias, class_name );
6913 if( curr_query->core.alias )
6916 osrfLogError( OSRF_LOG_MARK,
6917 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6923 @brief Search the current QueryFrame for a specified alias.
6924 @param target The alias for which to search.
6925 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6927 static inline ClassInfo* search_alias( const char* target ) {
6928 return search_alias_in_frame( curr_query, target );
6932 @brief Search all levels of query for a specified alias, starting with the current query.
6933 @param target The alias for which to search.
6934 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6936 static ClassInfo* search_all_alias( const char* target ) {
6937 ClassInfo* found_class = NULL;
6938 QueryFrame* curr_frame = curr_query;
6940 while( curr_frame ) {
6941 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6944 curr_frame = curr_frame->next;
6951 @brief Add a class to the list of classes joined to the current query.
6952 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6953 the class name as an alias.
6954 @param classname The name of the class to be added.
6955 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6957 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6959 if( ! classname || ! *classname ) { // sanity check
6960 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6967 const ClassInfo* conflict = search_alias( alias );
6969 osrfLogError( OSRF_LOG_MARK,
6970 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6971 modulename, alias, conflict->class_name );
6975 ClassInfo* info = allocate_class_info();
6977 if( build_class_info( info, alias, classname ) ) {
6978 free_class_info( info );
6982 // Add the new ClassInfo to the join list of the current QueryFrame
6983 info->next = curr_query->join_list;
6984 curr_query->join_list = info;
6990 @brief Destroy all nodes on the query stack.
6992 static void clear_query_stack( void ) {