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 database
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 // no result, skip this entirely
2362 if(NULL != obj && !verifyObjectPCRUD( ctx, obj, 1 )) {
2363 jsonObjectFree( obj );
2365 growing_buffer* msg = buffer_init( 128 );
2366 OSRF_BUFFER_ADD( msg, modulename );
2367 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2369 char* m = buffer_release( msg );
2370 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2374 osrfAppRespondComplete( ctx, NULL );
2379 osrfAppRespondComplete( ctx, obj );
2380 jsonObjectFree( obj );
2385 @brief Translate a numeric value to a string representation for the database.
2386 @param field Pointer to the IDL field definition.
2387 @param value Pointer to a jsonObject holding the value of a field.
2388 @return Pointer to a newly allocated string.
2390 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2391 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2392 or (what is worse) valid SQL that is wrong.
2394 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2396 The calling code is responsible for freeing the resulting string by calling free().
2398 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2399 growing_buffer* val_buf = buffer_init( 32 );
2400 const char* numtype = get_datatype( field );
2402 // For historical reasons the following contains cruft that could be cleaned up.
2403 if( !strncmp( numtype, "INT", 3 ) ) {
2404 if( value->type == JSON_NUMBER )
2405 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2406 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2408 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2411 } else if( !strcmp( numtype, "NUMERIC" )) {
2412 if( value->type == JSON_NUMBER )
2413 buffer_fadd( val_buf, jsonObjectGetString( value ));
2415 buffer_fadd( val_buf, jsonObjectGetString( value ));
2419 // Presumably this was really intended to be a string, so quote it
2420 char* str = jsonObjectToSimpleString( value );
2421 if( dbi_conn_quote_string( dbhandle, &str )) {
2422 OSRF_BUFFER_ADD( val_buf, str );
2425 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2427 buffer_free( val_buf );
2432 return buffer_release( val_buf );
2435 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2436 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2437 growing_buffer* sql_buf = buffer_init( 32 );
2443 osrfHashGet( field, "name" )
2447 buffer_add( sql_buf, "IN (" );
2448 } else if( !strcasecmp( op,"not in" )) {
2449 buffer_add( sql_buf, "NOT IN (" );
2451 buffer_add( sql_buf, "IN (" );
2454 if( node->type == JSON_HASH ) {
2455 // subquery predicate
2456 char* subpred = buildQuery( ctx, node, SUBSELECT );
2458 buffer_free( sql_buf );
2462 buffer_add( sql_buf, subpred );
2465 } else if( node->type == JSON_ARRAY ) {
2466 // literal value list
2467 int in_item_index = 0;
2468 int in_item_first = 1;
2469 const jsonObject* in_item;
2470 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2475 buffer_add( sql_buf, ", " );
2478 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2479 osrfLogError( OSRF_LOG_MARK,
2480 "%s: Expected string or number within IN list; found %s",
2481 modulename, json_type( in_item->type ) );
2482 buffer_free( sql_buf );
2486 // Append the literal value -- quoted if not a number
2487 if( JSON_NUMBER == in_item->type ) {
2488 char* val = jsonNumberToDBString( field, in_item );
2489 OSRF_BUFFER_ADD( sql_buf, val );
2492 } else if( !strcmp( get_primitive( field ), "number" )) {
2493 char* val = jsonNumberToDBString( field, in_item );
2494 OSRF_BUFFER_ADD( sql_buf, val );
2498 char* key_string = jsonObjectToSimpleString( in_item );
2499 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2500 OSRF_BUFFER_ADD( sql_buf, key_string );
2503 osrfLogError( OSRF_LOG_MARK,
2504 "%s: Error quoting key string [%s]", modulename, key_string );
2506 buffer_free( sql_buf );
2512 if( in_item_first ) {
2513 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2514 buffer_free( sql_buf );
2518 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2519 modulename, json_type( node->type ));
2520 buffer_free( sql_buf );
2524 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2526 return buffer_release( sql_buf );
2529 // Receive a JSON_ARRAY representing a function call. The first
2530 // entry in the array is the function name. The rest are parameters.
2531 static char* searchValueTransform( const jsonObject* array ) {
2533 if( array->size < 1 ) {
2534 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2538 // Get the function name
2539 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2540 if( func_item->type != JSON_STRING ) {
2541 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2542 modulename, json_type( func_item->type ));
2546 growing_buffer* sql_buf = buffer_init( 32 );
2548 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2549 OSRF_BUFFER_ADD( sql_buf, "( " );
2551 // Get the parameters
2552 int func_item_index = 1; // We already grabbed the zeroth entry
2553 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2555 // Add a separator comma, if we need one
2556 if( func_item_index > 2 )
2557 buffer_add( sql_buf, ", " );
2559 // Add the current parameter
2560 if( func_item->type == JSON_NULL ) {
2561 buffer_add( sql_buf, "NULL" );
2563 if( func_item->type == JSON_BOOL ) {
2564 if( jsonBoolIsTrue(func_item) ) {
2565 buffer_add( sql_buf, "TRUE" );
2567 buffer_add( sql_buf, "FALSE" );
2570 char* val = jsonObjectToSimpleString( func_item );
2571 if( dbi_conn_quote_string( dbhandle, &val )) {
2572 OSRF_BUFFER_ADD( sql_buf, val );
2575 osrfLogError( OSRF_LOG_MARK,
2576 "%s: Error quoting key string [%s]", modulename, val );
2577 buffer_free( sql_buf );
2585 buffer_add( sql_buf, " )" );
2587 return buffer_release( sql_buf );
2590 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2591 const jsonObject* node, const char* op ) {
2593 if( ! is_good_operator( op ) ) {
2594 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2598 char* val = searchValueTransform( node );
2602 growing_buffer* sql_buf = buffer_init( 32 );
2607 osrfHashGet( field, "name" ),
2614 return buffer_release( sql_buf );
2617 // class_alias is a class name or other table alias
2618 // field is a field definition as stored in the IDL
2619 // node comes from the method parameter, and may represent an entry in the SELECT list
2620 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2621 const jsonObject* node ) {
2622 growing_buffer* sql_buf = buffer_init( 32 );
2624 const char* field_transform = jsonObjectGetString(
2625 jsonObjectGetKeyConst( node, "transform" ) );
2626 const char* transform_subcolumn = jsonObjectGetString(
2627 jsonObjectGetKeyConst( node, "result_field" ) );
2629 if( transform_subcolumn ) {
2630 if( ! is_identifier( transform_subcolumn ) ) {
2631 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2632 modulename, transform_subcolumn );
2633 buffer_free( sql_buf );
2636 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2639 if( field_transform ) {
2641 if( ! is_identifier( field_transform ) ) {
2642 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2643 modulename, field_transform );
2644 buffer_free( sql_buf );
2648 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2649 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2650 field_transform, class_alias, osrfHashGet( field, "name" ));
2652 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2653 field_transform, class_alias, osrfHashGet( field, "name" ));
2656 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2659 if( array->type != JSON_ARRAY ) {
2660 osrfLogError( OSRF_LOG_MARK,
2661 "%s: Expected JSON_ARRAY for function params; found %s",
2662 modulename, json_type( array->type ) );
2663 buffer_free( sql_buf );
2666 int func_item_index = 0;
2667 jsonObject* func_item;
2668 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2670 char* val = jsonObjectToSimpleString( func_item );
2673 buffer_add( sql_buf, ",NULL" );
2674 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2675 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2676 OSRF_BUFFER_ADD( sql_buf, val );
2678 osrfLogError( OSRF_LOG_MARK,
2679 "%s: Error quoting key string [%s]", modulename, val );
2681 buffer_free( sql_buf );
2688 buffer_add( sql_buf, " )" );
2691 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2694 if( transform_subcolumn )
2695 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2697 return buffer_release( sql_buf );
2700 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2701 const jsonObject* node, const char* op ) {
2703 if( ! is_good_operator( op ) ) {
2704 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2708 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2709 if( ! field_transform )
2712 int extra_parens = 0; // boolean
2714 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2716 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2718 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2720 free( field_transform );
2724 } else if( value_obj->type == JSON_ARRAY ) {
2725 value = searchValueTransform( value_obj );
2727 osrfLogError( OSRF_LOG_MARK,
2728 "%s: Error building value transform for field transform", modulename );
2729 free( field_transform );
2732 } else if( value_obj->type == JSON_HASH ) {
2733 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2735 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2737 free( field_transform );
2741 } else if( value_obj->type == JSON_NUMBER ) {
2742 value = jsonNumberToDBString( field, value_obj );
2743 } else if( value_obj->type == JSON_NULL ) {
2744 osrfLogError( OSRF_LOG_MARK,
2745 "%s: Error building predicate for field transform: null value", modulename );
2746 free( field_transform );
2748 } else if( value_obj->type == JSON_BOOL ) {
2749 osrfLogError( OSRF_LOG_MARK,
2750 "%s: Error building predicate for field transform: boolean value", modulename );
2751 free( field_transform );
2754 if( !strcmp( get_primitive( field ), "number") ) {
2755 value = jsonNumberToDBString( field, value_obj );
2757 value = jsonObjectToSimpleString( value_obj );
2758 if( !dbi_conn_quote_string( dbhandle, &value )) {
2759 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2760 modulename, value );
2762 free( field_transform );
2768 const char* left_parens = "";
2769 const char* right_parens = "";
2771 if( extra_parens ) {
2776 const char* right_percent = "";
2777 const char* real_op = op;
2779 if( !strcasecmp( op, "startwith") ) {
2781 right_percent = "|| '%'";
2784 growing_buffer* sql_buf = buffer_init( 32 );
2788 "%s%s %s %s %s%s %s%s",
2800 free( field_transform );
2802 return buffer_release( sql_buf );
2805 static char* searchSimplePredicate( const char* op, const char* class_alias,
2806 osrfHash* field, const jsonObject* node ) {
2808 if( ! is_good_operator( op ) ) {
2809 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2815 // Get the value to which we are comparing the specified column
2816 if( node->type != JSON_NULL ) {
2817 if( node->type == JSON_NUMBER ) {
2818 val = jsonNumberToDBString( field, node );
2819 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2820 val = jsonNumberToDBString( field, node );
2822 val = jsonObjectToSimpleString( node );
2827 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2828 // Value is not numeric; enclose it in quotes
2829 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2830 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2837 // Compare to a null value
2838 val = strdup( "NULL" );
2839 if( strcmp( op, "=" ))
2845 growing_buffer* sql_buf = buffer_init( 32 );
2846 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2847 char* pred = buffer_release( sql_buf );
2854 static char* searchBETWEENPredicate( const char* class_alias,
2855 osrfHash* field, const jsonObject* node ) {
2857 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2858 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2860 if( NULL == y_node ) {
2861 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2864 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2865 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2872 if( !strcmp( get_primitive( field ), "number") ) {
2873 x_string = jsonNumberToDBString( field, x_node );
2874 y_string = jsonNumberToDBString( field, y_node );
2877 x_string = jsonObjectToSimpleString( x_node );
2878 y_string = jsonObjectToSimpleString( y_node );
2879 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2880 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2881 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2882 modulename, x_string, y_string );
2889 growing_buffer* sql_buf = buffer_init( 32 );
2890 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2891 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2895 return buffer_release( sql_buf );
2898 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2899 jsonObject* node, osrfMethodContext* ctx ) {
2902 if( node->type == JSON_ARRAY ) { // equality IN search
2903 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2904 } else if( node->type == JSON_HASH ) { // other search
2905 jsonIterator* pred_itr = jsonNewIterator( node );
2906 if( !jsonIteratorHasNext( pred_itr ) ) {
2907 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2908 modulename, osrfHashGet(field, "name" ));
2910 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2912 // Verify that there are no additional predicates
2913 if( jsonIteratorHasNext( pred_itr ) ) {
2914 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2915 modulename, osrfHashGet(field, "name" ));
2916 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2917 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2918 else if( !(strcasecmp( pred_itr->key,"in" ))
2919 || !(strcasecmp( pred_itr->key,"not in" )) )
2920 pred = searchINPredicate(
2921 class_info->alias, field, pred_node, pred_itr->key, ctx );
2922 else if( pred_node->type == JSON_ARRAY )
2923 pred = searchFunctionPredicate(
2924 class_info->alias, field, pred_node, pred_itr->key );
2925 else if( pred_node->type == JSON_HASH )
2926 pred = searchFieldTransformPredicate(
2927 class_info, field, pred_node, pred_itr->key );
2929 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2931 jsonIteratorFree( pred_itr );
2933 } else if( node->type == JSON_NULL ) { // IS NULL search
2934 growing_buffer* _p = buffer_init( 64 );
2937 "\"%s\".%s IS NULL",
2938 class_info->class_name,
2939 osrfHashGet( field, "name" )
2941 pred = buffer_release( _p );
2942 } else { // equality search
2943 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2962 field : call_number,
2978 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2980 const jsonObject* working_hash;
2981 jsonObject* freeable_hash = NULL;
2983 if( join_hash->type == JSON_HASH ) {
2984 working_hash = join_hash;
2985 } else if( join_hash->type == JSON_STRING ) {
2986 // turn it into a JSON_HASH by creating a wrapper
2987 // around a copy of the original
2988 const char* _tmp = jsonObjectGetString( join_hash );
2989 freeable_hash = jsonNewObjectType( JSON_HASH );
2990 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2991 working_hash = freeable_hash;
2995 "%s: JOIN failed; expected JSON object type not found",
3001 growing_buffer* join_buf = buffer_init( 128 );
3002 const char* leftclass = left_info->class_name;
3004 jsonObject* snode = NULL;
3005 jsonIterator* search_itr = jsonNewIterator( working_hash );
3007 while ( (snode = jsonIteratorNext( search_itr )) ) {
3008 const char* right_alias = search_itr->key;
3010 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3012 class = right_alias;
3014 const ClassInfo* right_info = add_joined_class( right_alias, class );
3018 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3022 jsonIteratorFree( search_itr );
3023 buffer_free( join_buf );
3025 jsonObjectFree( freeable_hash );
3028 osrfHash* links = right_info->links;
3029 const char* table = right_info->source_def;
3031 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3032 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3034 if( field && !fkey ) {
3035 // Look up the corresponding join column in the IDL.
3036 // The link must be defined in the child table,
3037 // and point to the right parent table.
3038 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3039 const char* reltype = NULL;
3040 const char* other_class = NULL;
3041 reltype = osrfHashGet( idl_link, "reltype" );
3042 if( reltype && strcmp( reltype, "has_many" ) )
3043 other_class = osrfHashGet( idl_link, "class" );
3044 if( other_class && !strcmp( other_class, leftclass ) )
3045 fkey = osrfHashGet( idl_link, "key" );
3049 "%s: JOIN failed. No link defined from %s.%s to %s",
3055 buffer_free( join_buf );
3057 jsonObjectFree( freeable_hash );
3058 jsonIteratorFree( search_itr );
3062 } else if( !field && fkey ) {
3063 // Look up the corresponding join column in the IDL.
3064 // The link must be defined in the child table,
3065 // and point to the right parent table.
3066 osrfHash* left_links = left_info->links;
3067 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3068 const char* reltype = NULL;
3069 const char* other_class = NULL;
3070 reltype = osrfHashGet( idl_link, "reltype" );
3071 if( reltype && strcmp( reltype, "has_many" ) )
3072 other_class = osrfHashGet( idl_link, "class" );
3073 if( other_class && !strcmp( other_class, class ) )
3074 field = osrfHashGet( idl_link, "key" );
3078 "%s: JOIN failed. No link defined from %s.%s to %s",
3084 buffer_free( join_buf );
3086 jsonObjectFree( freeable_hash );
3087 jsonIteratorFree( search_itr );
3091 } else if( !field && !fkey ) {
3092 osrfHash* left_links = left_info->links;
3094 // For each link defined for the left class:
3095 // see if the link references the joined class
3096 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3097 osrfHash* curr_link = NULL;
3098 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3099 const char* other_class = osrfHashGet( curr_link, "class" );
3100 if( other_class && !strcmp( other_class, class ) ) {
3102 // In the IDL, the parent class doesn't always know then names of the child
3103 // columns that are pointing to it, so don't use that end of the link
3104 const char* reltype = osrfHashGet( curr_link, "reltype" );
3105 if( reltype && strcmp( reltype, "has_many" ) ) {
3106 // Found a link between the classes
3107 fkey = osrfHashIteratorKey( itr );
3108 field = osrfHashGet( curr_link, "key" );
3113 osrfHashIteratorFree( itr );
3115 if( !field || !fkey ) {
3116 // Do another such search, with the classes reversed
3118 // For each link defined for the joined class:
3119 // see if the link references the left class
3120 osrfHashIterator* itr = osrfNewHashIterator( links );
3121 osrfHash* curr_link = NULL;
3122 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3123 const char* other_class = osrfHashGet( curr_link, "class" );
3124 if( other_class && !strcmp( other_class, leftclass ) ) {
3126 // In the IDL, the parent class doesn't know then names of the child
3127 // columns that are pointing to it, so don't use that end of the link
3128 const char* reltype = osrfHashGet( curr_link, "reltype" );
3129 if( reltype && strcmp( reltype, "has_many" ) ) {
3130 // Found a link between the classes
3131 field = osrfHashIteratorKey( itr );
3132 fkey = osrfHashGet( curr_link, "key" );
3137 osrfHashIteratorFree( itr );
3140 if( !field || !fkey ) {
3143 "%s: JOIN failed. No link defined between %s and %s",
3148 buffer_free( join_buf );
3150 jsonObjectFree( freeable_hash );
3151 jsonIteratorFree( search_itr );
3156 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3158 if( !strcasecmp( type,"left" )) {
3159 buffer_add( join_buf, " LEFT JOIN" );
3160 } else if( !strcasecmp( type,"right" )) {
3161 buffer_add( join_buf, " RIGHT JOIN" );
3162 } else if( !strcasecmp( type,"full" )) {
3163 buffer_add( join_buf, " FULL JOIN" );
3165 buffer_add( join_buf, " INNER JOIN" );
3168 buffer_add( join_buf, " INNER JOIN" );
3171 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3172 table, right_alias, right_alias, field, left_info->alias, fkey );
3174 // Add any other join conditions as specified by "filter"
3175 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3177 const char* filter_op = jsonObjectGetString(
3178 jsonObjectGetKeyConst( snode, "filter_op" ) );
3179 if( filter_op && !strcasecmp( "or",filter_op )) {
3180 buffer_add( join_buf, " OR " );
3182 buffer_add( join_buf, " AND " );
3185 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3187 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3188 OSRF_BUFFER_ADD( join_buf, jpred );
3193 "%s: JOIN failed. Invalid conditional expression.",
3196 jsonIteratorFree( search_itr );
3197 buffer_free( join_buf );
3199 jsonObjectFree( freeable_hash );
3204 buffer_add( join_buf, " ) " );
3206 // Recursively add a nested join, if one is present
3207 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3209 char* jpred = searchJOIN( join_filter, right_info );
3211 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3212 OSRF_BUFFER_ADD( join_buf, jpred );
3215 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3216 jsonIteratorFree( search_itr );
3217 buffer_free( join_buf );
3219 jsonObjectFree( freeable_hash );
3226 jsonObjectFree( freeable_hash );
3227 jsonIteratorFree( search_itr );
3229 return buffer_release( join_buf );
3234 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3235 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3236 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3238 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3240 search_hash is the JSON expression of the conditions.
3241 meta is the class definition from the IDL, for the relevant table.
3242 opjoin_type indicates whether multiple conditions, if present, should be
3243 connected by AND or OR.
3244 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3245 to pass it to other functions -- and all they do with it is to use the session
3246 and request members to send error messages back to the client.
3250 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3251 int opjoin_type, osrfMethodContext* ctx ) {
3255 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3256 "opjoin_type = %d, ctx addr = %p",
3259 class_info->class_def,
3264 growing_buffer* sql_buf = buffer_init( 128 );
3266 jsonObject* node = NULL;
3269 if( search_hash->type == JSON_ARRAY ) {
3270 if( 0 == search_hash->size ) {
3273 "%s: Invalid predicate structure: empty JSON array",
3276 buffer_free( sql_buf );
3280 unsigned long i = 0;
3281 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3285 if( opjoin_type == OR_OP_JOIN )
3286 buffer_add( sql_buf, " OR " );
3288 buffer_add( sql_buf, " AND " );
3291 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3293 buffer_free( sql_buf );
3297 buffer_fadd( sql_buf, "( %s )", subpred );
3301 } else if( search_hash->type == JSON_HASH ) {
3302 osrfLogDebug( OSRF_LOG_MARK,
3303 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3304 jsonIterator* search_itr = jsonNewIterator( search_hash );
3305 if( !jsonIteratorHasNext( search_itr ) ) {
3308 "%s: Invalid predicate structure: empty JSON object",
3311 jsonIteratorFree( search_itr );
3312 buffer_free( sql_buf );
3316 while( (node = jsonIteratorNext( search_itr )) ) {
3321 if( opjoin_type == OR_OP_JOIN )
3322 buffer_add( sql_buf, " OR " );
3324 buffer_add( sql_buf, " AND " );
3327 if( '+' == search_itr->key[ 0 ] ) {
3329 // This plus sign prefixes a class name or other table alias;
3330 // make sure the table alias is in scope
3331 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3332 if( ! alias_info ) {
3335 "%s: Invalid table alias \"%s\" in WHERE clause",
3339 jsonIteratorFree( search_itr );
3340 buffer_free( sql_buf );
3344 if( node->type == JSON_STRING ) {
3345 // It's the name of a column; make sure it belongs to the class
3346 const char* fieldname = jsonObjectGetString( node );
3347 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3350 "%s: Invalid column name \"%s\" in WHERE clause "
3351 "for table alias \"%s\"",
3356 jsonIteratorFree( search_itr );
3357 buffer_free( sql_buf );
3361 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3363 // It's something more complicated
3364 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3366 jsonIteratorFree( search_itr );
3367 buffer_free( sql_buf );
3371 buffer_fadd( sql_buf, "( %s )", subpred );
3374 } else if( '-' == search_itr->key[ 0 ] ) {
3375 if( !strcasecmp( "-or", search_itr->key )) {
3376 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3378 jsonIteratorFree( search_itr );
3379 buffer_free( sql_buf );
3383 buffer_fadd( sql_buf, "( %s )", subpred );
3385 } else if( !strcasecmp( "-and", search_itr->key )) {
3386 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3388 jsonIteratorFree( search_itr );
3389 buffer_free( sql_buf );
3393 buffer_fadd( sql_buf, "( %s )", subpred );
3395 } else if( !strcasecmp("-not",search_itr->key) ) {
3396 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3398 jsonIteratorFree( search_itr );
3399 buffer_free( sql_buf );
3403 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3405 } else if( !strcasecmp( "-exists", search_itr->key )) {
3406 char* subpred = buildQuery( ctx, node, SUBSELECT );
3408 jsonIteratorFree( search_itr );
3409 buffer_free( sql_buf );
3413 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3415 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3416 char* subpred = buildQuery( ctx, node, SUBSELECT );
3418 jsonIteratorFree( search_itr );
3419 buffer_free( sql_buf );
3423 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3425 } else { // Invalid "minus" operator
3428 "%s: Invalid operator \"%s\" in WHERE clause",
3432 jsonIteratorFree( search_itr );
3433 buffer_free( sql_buf );
3439 const char* class = class_info->class_name;
3440 osrfHash* fields = class_info->fields;
3441 osrfHash* field = osrfHashGet( fields, search_itr->key );
3444 const char* table = class_info->source_def;
3447 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3450 table ? table : "?",
3453 jsonIteratorFree( search_itr );
3454 buffer_free( sql_buf );
3458 char* subpred = searchPredicate( class_info, field, node, ctx );
3460 buffer_free( sql_buf );
3461 jsonIteratorFree( search_itr );
3465 buffer_add( sql_buf, subpred );
3469 jsonIteratorFree( search_itr );
3472 // ERROR ... only hash and array allowed at this level
3473 char* predicate_string = jsonObjectToJSON( search_hash );
3476 "%s: Invalid predicate structure: %s",
3480 buffer_free( sql_buf );
3481 free( predicate_string );
3485 return buffer_release( sql_buf );
3488 /* Build a JSON_ARRAY of field names for a given table alias
3490 static jsonObject* defaultSelectList( const char* table_alias ) {
3495 ClassInfo* class_info = search_all_alias( table_alias );
3496 if( ! class_info ) {
3499 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3506 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3507 osrfHash* field_def = NULL;
3508 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3509 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3510 const char* field_name = osrfHashIteratorKey( field_itr );
3511 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3512 jsonObjectPush( array, jsonNewObject( field_name ) );
3515 osrfHashIteratorFree( field_itr );
3520 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3521 // The jsonObject must be a JSON_HASH with an single entry for "union",
3522 // "intersect", or "except". The data associated with this key must be an
3523 // array of hashes, each hash being a query.
3524 // Also allowed but currently ignored: entries for "order_by" and "alias".
3525 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3527 if( ! combo || combo->type != JSON_HASH )
3528 return NULL; // should be impossible; validated by caller
3530 const jsonObject* query_array = NULL; // array of subordinate queries
3531 const char* op = NULL; // name of operator, e.g. UNION
3532 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3533 int op_count = 0; // for detecting conflicting operators
3534 int excepting = 0; // boolean
3535 int all = 0; // boolean
3536 jsonObject* order_obj = NULL;
3538 // Identify the elements in the hash
3539 jsonIterator* query_itr = jsonNewIterator( combo );
3540 jsonObject* curr_obj = NULL;
3541 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3542 if( ! strcmp( "union", query_itr->key ) ) {
3545 query_array = curr_obj;
3546 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3549 query_array = curr_obj;
3550 } else if( ! strcmp( "except", query_itr->key ) ) {
3554 query_array = curr_obj;
3555 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3558 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3561 order_obj = curr_obj;
3562 } else if( ! strcmp( "alias", query_itr->key ) ) {
3563 if( curr_obj->type != JSON_STRING ) {
3564 jsonIteratorFree( query_itr );
3567 alias = jsonObjectGetString( curr_obj );
3568 } else if( ! strcmp( "all", query_itr->key ) ) {
3569 if( obj_is_true( curr_obj ) )
3573 osrfAppSessionStatus(
3575 OSRF_STATUS_INTERNALSERVERERROR,
3576 "osrfMethodException",
3578 "Malformed query; unexpected entry in query object"
3582 "%s: Unexpected entry for \"%s\" in%squery",
3587 jsonIteratorFree( query_itr );
3591 jsonIteratorFree( query_itr );
3593 // More sanity checks
3594 if( ! query_array ) {
3596 osrfAppSessionStatus(
3598 OSRF_STATUS_INTERNALSERVERERROR,
3599 "osrfMethodException",
3601 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3605 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3608 return NULL; // should be impossible...
3609 } else if( op_count > 1 ) {
3611 osrfAppSessionStatus(
3613 OSRF_STATUS_INTERNALSERVERERROR,
3614 "osrfMethodException",
3616 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3620 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3624 } if( query_array->type != JSON_ARRAY ) {
3626 osrfAppSessionStatus(
3628 OSRF_STATUS_INTERNALSERVERERROR,
3629 "osrfMethodException",
3631 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3635 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3638 json_type( query_array->type )
3641 } if( query_array->size < 2 ) {
3643 osrfAppSessionStatus(
3645 OSRF_STATUS_INTERNALSERVERERROR,
3646 "osrfMethodException",
3648 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3652 "%s:%srequires multiple queries as operands",
3657 } else if( excepting && query_array->size > 2 ) {
3659 osrfAppSessionStatus(
3661 OSRF_STATUS_INTERNALSERVERERROR,
3662 "osrfMethodException",
3664 "EXCEPT operator has too many queries as operands"
3668 "%s:EXCEPT operator has too many queries as operands",
3672 } else if( order_obj && ! alias ) {
3674 osrfAppSessionStatus(
3676 OSRF_STATUS_INTERNALSERVERERROR,
3677 "osrfMethodException",
3679 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3683 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3689 // So far so good. Now build the SQL.
3690 growing_buffer* sql = buffer_init( 256 );
3692 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3693 // Add a layer of parentheses
3694 if( flags & SUBCOMBO )
3695 OSRF_BUFFER_ADD( sql, "( " );
3697 // Traverse the query array. Each entry should be a hash.
3698 int first = 1; // boolean
3700 jsonObject* query = NULL;
3701 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3702 if( query->type != JSON_HASH ) {
3704 osrfAppSessionStatus(
3706 OSRF_STATUS_INTERNALSERVERERROR,
3707 "osrfMethodException",
3709 "Malformed query under UNION, INTERSECT or EXCEPT"
3713 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3716 json_type( query->type )
3725 OSRF_BUFFER_ADD( sql, op );
3727 OSRF_BUFFER_ADD( sql, "ALL " );
3730 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3734 "%s: Error building query under%s",
3742 OSRF_BUFFER_ADD( sql, query_str );
3745 if( flags & SUBCOMBO )
3746 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3748 if( !(flags & SUBSELECT) )
3749 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3751 return buffer_release( sql );
3754 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3755 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3756 // or "except" to indicate the type of query.
3757 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3761 osrfAppSessionStatus(
3763 OSRF_STATUS_INTERNALSERVERERROR,
3764 "osrfMethodException",
3766 "Malformed query; no query object"
3768 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3770 } else if( query->type != JSON_HASH ) {
3772 osrfAppSessionStatus(
3774 OSRF_STATUS_INTERNALSERVERERROR,
3775 "osrfMethodException",
3777 "Malformed query object"
3781 "%s: Query object is %s instead of JSON_HASH",
3783 json_type( query->type )
3788 // Determine what kind of query it purports to be, and dispatch accordingly.
3789 if( jsonObjectGetKeyConst( query, "union" ) ||
3790 jsonObjectGetKeyConst( query, "intersect" ) ||
3791 jsonObjectGetKeyConst( query, "except" )) {
3792 return doCombo( ctx, query, flags );
3794 // It is presumably a SELECT query
3796 // Push a node onto the stack for the current query. Every level of
3797 // subquery gets its own QueryFrame on the Stack.
3800 // Build an SQL SELECT statement
3803 jsonObjectGetKey( query, "select" ),
3804 jsonObjectGetKeyConst( query, "from" ),
3805 jsonObjectGetKeyConst( query, "where" ),
3806 jsonObjectGetKeyConst( query, "having" ),
3807 jsonObjectGetKeyConst( query, "order_by" ),
3808 jsonObjectGetKeyConst( query, "limit" ),
3809 jsonObjectGetKeyConst( query, "offset" ),
3818 /* method context */ osrfMethodContext* ctx,
3820 /* SELECT */ jsonObject* selhash,
3821 /* FROM */ const jsonObject* join_hash,
3822 /* WHERE */ const jsonObject* search_hash,
3823 /* HAVING */ const jsonObject* having_hash,
3824 /* ORDER BY */ const jsonObject* order_hash,
3825 /* LIMIT */ const jsonObject* limit,
3826 /* OFFSET */ const jsonObject* offset,
3827 /* flags */ int flags
3829 const char* locale = osrf_message_get_last_locale();
3831 // general tmp objects
3832 const jsonObject* tmp_const;
3833 jsonObject* selclass = NULL;
3834 jsonObject* snode = NULL;
3835 jsonObject* onode = NULL;
3837 char* string = NULL;
3838 int from_function = 0;
3843 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3845 // punt if there's no FROM clause
3846 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3849 "%s: FROM clause is missing or empty",
3853 osrfAppSessionStatus(
3855 OSRF_STATUS_INTERNALSERVERERROR,
3856 "osrfMethodException",
3858 "FROM clause is missing or empty in JSON query"
3863 // the core search class
3864 const char* core_class = NULL;
3866 // get the core class -- the only key of the top level FROM clause, or a string
3867 if( join_hash->type == JSON_HASH ) {
3868 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3869 snode = jsonIteratorNext( tmp_itr );
3871 // Populate the current QueryFrame with information
3872 // about the core class
3873 if( add_query_core( NULL, tmp_itr->key ) ) {
3875 osrfAppSessionStatus(
3877 OSRF_STATUS_INTERNALSERVERERROR,
3878 "osrfMethodException",
3880 "Unable to look up core class"
3884 core_class = curr_query->core.class_name;
3887 jsonObject* extra = jsonIteratorNext( tmp_itr );
3889 jsonIteratorFree( tmp_itr );
3892 // There shouldn't be more than one entry in join_hash
3896 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3900 osrfAppSessionStatus(
3902 OSRF_STATUS_INTERNALSERVERERROR,
3903 "osrfMethodException",
3905 "Malformed FROM clause in JSON query"
3907 return NULL; // Malformed join_hash; extra entry
3909 } else if( join_hash->type == JSON_ARRAY ) {
3910 // We're selecting from a function, not from a table
3912 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3915 } else if( join_hash->type == JSON_STRING ) {
3916 // Populate the current QueryFrame with information
3917 // about the core class
3918 core_class = jsonObjectGetString( join_hash );
3920 if( add_query_core( NULL, core_class ) ) {
3922 osrfAppSessionStatus(
3924 OSRF_STATUS_INTERNALSERVERERROR,
3925 "osrfMethodException",
3927 "Unable to look up core class"
3935 "%s: FROM clause is unexpected JSON type: %s",
3937 json_type( join_hash->type )
3940 osrfAppSessionStatus(
3942 OSRF_STATUS_INTERNALSERVERERROR,
3943 "osrfMethodException",
3945 "Ill-formed FROM clause in JSON query"
3950 // Build the join clause, if any, while filling out the list
3951 // of joined classes in the current QueryFrame.
3952 char* join_clause = NULL;
3953 if( join_hash && ! from_function ) {
3955 join_clause = searchJOIN( join_hash, &curr_query->core );
3956 if( ! join_clause ) {
3958 osrfAppSessionStatus(
3960 OSRF_STATUS_INTERNALSERVERERROR,
3961 "osrfMethodException",
3963 "Unable to construct JOIN clause(s)"
3969 // For in case we don't get a select list
3970 jsonObject* defaultselhash = NULL;
3972 // if there is no select list, build a default select list ...
3973 if( !selhash && !from_function ) {
3974 jsonObject* default_list = defaultSelectList( core_class );
3975 if( ! default_list ) {
3977 osrfAppSessionStatus(
3979 OSRF_STATUS_INTERNALSERVERERROR,
3980 "osrfMethodException",
3982 "Unable to build default SELECT clause in JSON query"
3984 free( join_clause );
3989 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3990 jsonObjectSetKey( selhash, core_class, default_list );
3993 // The SELECT clause can be encoded only by a hash
3994 if( !from_function && selhash->type != JSON_HASH ) {
3997 "%s: Expected JSON_HASH for SELECT clause; found %s",
3999 json_type( selhash->type )
4003 osrfAppSessionStatus(
4005 OSRF_STATUS_INTERNALSERVERERROR,
4006 "osrfMethodException",
4008 "Malformed SELECT clause in JSON query"
4010 free( join_clause );
4014 // If you see a null or wild card specifier for the core class, or an
4015 // empty array, replace it with a default SELECT list
4016 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4018 int default_needed = 0; // boolean
4019 if( JSON_STRING == tmp_const->type
4020 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4022 else if( JSON_NULL == tmp_const->type )
4025 if( default_needed ) {
4026 // Build a default SELECT list
4027 jsonObject* default_list = defaultSelectList( core_class );
4028 if( ! default_list ) {
4030 osrfAppSessionStatus(
4032 OSRF_STATUS_INTERNALSERVERERROR,
4033 "osrfMethodException",
4035 "Can't build default SELECT clause in JSON query"
4037 free( join_clause );
4042 jsonObjectSetKey( selhash, core_class, default_list );
4046 // temp buffers for the SELECT list and GROUP BY clause
4047 growing_buffer* select_buf = buffer_init( 128 );
4048 growing_buffer* group_buf = buffer_init( 128 );
4050 int aggregate_found = 0; // boolean
4052 // Build a select list
4053 if( from_function ) // From a function we select everything
4054 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4057 // Build the SELECT list as SQL
4061 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4062 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4064 const char* cname = selclass_itr->key;
4066 // Make sure the target relation is in the FROM clause.
4068 // At this point join_hash is a step down from the join_hash we
4069 // received as a parameter. If the original was a JSON_STRING,
4070 // then json_hash is now NULL. If the original was a JSON_HASH,
4071 // then json_hash is now the first (and only) entry in it,
4072 // denoting the core class. We've already excluded the
4073 // possibility that the original was a JSON_ARRAY, because in
4074 // that case from_function would be non-NULL, and we wouldn't
4077 // If the current table alias isn't in scope, bail out
4078 ClassInfo* class_info = search_alias( cname );
4079 if( ! class_info ) {
4082 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4087 osrfAppSessionStatus(
4089 OSRF_STATUS_INTERNALSERVERERROR,
4090 "osrfMethodException",
4092 "Selected class not in FROM clause in JSON query"
4094 jsonIteratorFree( selclass_itr );
4095 buffer_free( select_buf );
4096 buffer_free( group_buf );
4097 if( defaultselhash )
4098 jsonObjectFree( defaultselhash );
4099 free( join_clause );
4103 if( selclass->type != JSON_ARRAY ) {
4106 "%s: Malformed SELECT list for class \"%s\"; not an array",
4111 osrfAppSessionStatus(
4113 OSRF_STATUS_INTERNALSERVERERROR,
4114 "osrfMethodException",
4116 "Selected class not in FROM clause in JSON query"
4119 jsonIteratorFree( selclass_itr );
4120 buffer_free( select_buf );
4121 buffer_free( group_buf );
4122 if( defaultselhash )
4123 jsonObjectFree( defaultselhash );
4124 free( join_clause );
4128 // Look up some attributes of the current class
4129 osrfHash* idlClass = class_info->class_def;
4130 osrfHash* class_field_set = class_info->fields;
4131 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4132 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4134 if( 0 == selclass->size ) {
4137 "%s: No columns selected from \"%s\"",
4143 // stitch together the column list for the current table alias...
4144 unsigned long field_idx = 0;
4145 jsonObject* selfield = NULL;
4146 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4148 // If we need a separator comma, add one
4152 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4155 // if the field specification is a string, add it to the list
4156 if( selfield->type == JSON_STRING ) {
4158 // Look up the field in the IDL
4159 const char* col_name = jsonObjectGetString( selfield );
4160 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4162 // No such field in current class
4165 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4171 osrfAppSessionStatus(
4173 OSRF_STATUS_INTERNALSERVERERROR,
4174 "osrfMethodException",
4176 "Selected column not defined in JSON query"
4178 jsonIteratorFree( selclass_itr );
4179 buffer_free( select_buf );
4180 buffer_free( group_buf );
4181 if( defaultselhash )
4182 jsonObjectFree( defaultselhash );
4183 free( join_clause );
4185 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4186 // Virtual field not allowed
4189 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4195 osrfAppSessionStatus(
4197 OSRF_STATUS_INTERNALSERVERERROR,
4198 "osrfMethodException",
4200 "Selected column may not be virtual in JSON query"
4202 jsonIteratorFree( selclass_itr );
4203 buffer_free( select_buf );
4204 buffer_free( group_buf );
4205 if( defaultselhash )
4206 jsonObjectFree( defaultselhash );
4207 free( join_clause );
4213 if( flags & DISABLE_I18N )
4216 i18n = osrfHashGet( field_def, "i18n" );
4218 if( str_is_true( i18n ) ) {
4219 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4220 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4221 class_tname, cname, col_name, class_pkey,
4222 cname, class_pkey, locale, col_name );
4224 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4225 cname, col_name, col_name );
4228 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4229 cname, col_name, col_name );
4232 // ... but it could be an object, in which case we check for a Field Transform
4233 } else if( selfield->type == JSON_HASH ) {
4235 const char* col_name = jsonObjectGetString(
4236 jsonObjectGetKeyConst( selfield, "column" ) );
4238 // Get the field definition from the IDL
4239 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4241 // No such field in current class
4244 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4250 osrfAppSessionStatus(
4252 OSRF_STATUS_INTERNALSERVERERROR,
4253 "osrfMethodException",
4255 "Selected column is not defined in JSON query"
4257 jsonIteratorFree( selclass_itr );
4258 buffer_free( select_buf );
4259 buffer_free( group_buf );
4260 if( defaultselhash )
4261 jsonObjectFree( defaultselhash );
4262 free( join_clause );
4264 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4265 // No such field in current class
4268 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4274 osrfAppSessionStatus(
4276 OSRF_STATUS_INTERNALSERVERERROR,
4277 "osrfMethodException",
4279 "Selected column is virtual in JSON query"
4281 jsonIteratorFree( selclass_itr );
4282 buffer_free( select_buf );
4283 buffer_free( group_buf );
4284 if( defaultselhash )
4285 jsonObjectFree( defaultselhash );
4286 free( join_clause );
4290 // Decide what to use as a column alias
4292 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4293 _alias = jsonObjectGetString( tmp_const );
4294 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4295 _alias = jsonObjectGetString( tmp_const );
4296 } else { // Use field name as the alias
4300 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4301 char* transform_str = searchFieldTransform(
4302 class_info->alias, field_def, selfield );
4303 if( transform_str ) {
4304 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4305 free( transform_str );
4308 osrfAppSessionStatus(
4310 OSRF_STATUS_INTERNALSERVERERROR,
4311 "osrfMethodException",
4313 "Unable to generate transform function in JSON query"
4315 jsonIteratorFree( selclass_itr );
4316 buffer_free( select_buf );
4317 buffer_free( group_buf );
4318 if( defaultselhash )
4319 jsonObjectFree( defaultselhash );
4320 free( join_clause );
4327 if( flags & DISABLE_I18N )
4330 i18n = osrfHashGet( field_def, "i18n" );
4332 if( str_is_true( i18n ) ) {
4333 buffer_fadd( select_buf,
4334 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4335 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4336 class_tname, cname, col_name, class_pkey, cname,
4337 class_pkey, locale, _alias );
4339 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4340 cname, col_name, _alias );
4343 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4344 cname, col_name, _alias );
4351 "%s: Selected item is unexpected JSON type: %s",
4353 json_type( selfield->type )
4356 osrfAppSessionStatus(
4358 OSRF_STATUS_INTERNALSERVERERROR,
4359 "osrfMethodException",
4361 "Ill-formed SELECT item in JSON query"
4363 jsonIteratorFree( selclass_itr );
4364 buffer_free( select_buf );
4365 buffer_free( group_buf );
4366 if( defaultselhash )
4367 jsonObjectFree( defaultselhash );
4368 free( join_clause );
4372 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4373 if( obj_is_true( agg_obj ) )
4374 aggregate_found = 1;
4376 // Append a comma (except for the first one)
4377 // and add the column to a GROUP BY clause
4381 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4383 buffer_fadd( group_buf, " %d", sel_pos );
4387 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4389 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( elfield, "aggregate");
4390 if ( ! obj_is_true( aggregate_obj ) ) {
4394 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4397 buffer_fadd(group_buf, " %d", sel_pos);
4400 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4404 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4407 _column = searchFieldTransform(class_info->alias, field, selfield);
4408 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4409 OSRF_BUFFER_ADD(group_buf, _column);
4410 _column = searchFieldTransform(class_info->alias, field, selfield);
4417 } // end while -- iterating across SELECT columns
4419 } // end while -- iterating across classes
4421 jsonIteratorFree( selclass_itr );
4424 char* col_list = buffer_release( select_buf );
4426 // Make sure the SELECT list isn't empty. This can happen, for example,
4427 // if we try to build a default SELECT clause from a non-core table.
4430 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4432 osrfAppSessionStatus(
4434 OSRF_STATUS_INTERNALSERVERERROR,
4435 "osrfMethodException",
4437 "SELECT list is empty"
4440 buffer_free( group_buf );
4441 if( defaultselhash )
4442 jsonObjectFree( defaultselhash );
4443 free( join_clause );
4449 table = searchValueTransform( join_hash );
4451 table = strdup( curr_query->core.source_def );
4455 osrfAppSessionStatus(
4457 OSRF_STATUS_INTERNALSERVERERROR,
4458 "osrfMethodException",
4460 "Unable to identify table for core class"
4463 buffer_free( group_buf );
4464 if( defaultselhash )
4465 jsonObjectFree( defaultselhash );
4466 free( join_clause );
4470 // Put it all together
4471 growing_buffer* sql_buf = buffer_init( 128 );
4472 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4476 // Append the join clause, if any
4478 buffer_add(sql_buf, join_clause );
4479 free( join_clause );
4482 char* order_by_list = NULL;
4483 char* having_buf = NULL;
4485 if( !from_function ) {
4487 // Build a WHERE clause, if there is one
4489 buffer_add( sql_buf, " WHERE " );
4491 // and it's on the WHERE clause
4492 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4495 osrfAppSessionStatus(
4497 OSRF_STATUS_INTERNALSERVERERROR,
4498 "osrfMethodException",
4500 "Severe query error in WHERE predicate -- see error log for more details"
4503 buffer_free( group_buf );
4504 buffer_free( sql_buf );
4505 if( defaultselhash )
4506 jsonObjectFree( defaultselhash );
4510 buffer_add( sql_buf, pred );
4514 // Build a HAVING clause, if there is one
4517 // and it's on the the WHERE clause
4518 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4520 if( ! having_buf ) {
4522 osrfAppSessionStatus(
4524 OSRF_STATUS_INTERNALSERVERERROR,
4525 "osrfMethodException",
4527 "Severe query error in HAVING predicate -- see error log for more details"
4530 buffer_free( group_buf );
4531 buffer_free( sql_buf );
4532 if( defaultselhash )
4533 jsonObjectFree( defaultselhash );
4538 // Build an ORDER BY clause, if there is one
4539 if( NULL == order_hash )
4540 ; // No ORDER BY? do nothing
4541 else if( JSON_ARRAY == order_hash->type ) {
4542 order_by_list = buildOrderByFromArray( ctx, order_hash );
4543 if( !order_by_list ) {
4545 buffer_free( group_buf );
4546 buffer_free( sql_buf );
4547 if( defaultselhash )
4548 jsonObjectFree( defaultselhash );
4551 } else if( JSON_HASH == order_hash->type ) {
4552 // This hash is keyed on class alias. Each class has either
4553 // an array of field names or a hash keyed on field name.
4554 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4555 jsonIterator* class_itr = jsonNewIterator( order_hash );
4556 while( (snode = jsonIteratorNext( class_itr )) ) {
4558 ClassInfo* order_class_info = search_alias( class_itr->key );
4559 if( ! order_class_info ) {
4560 osrfLogError( OSRF_LOG_MARK,
4561 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4562 modulename, class_itr->key );
4564 osrfAppSessionStatus(
4566 OSRF_STATUS_INTERNALSERVERERROR,
4567 "osrfMethodException",
4569 "Invalid class referenced in ORDER BY clause -- "
4570 "see error log for more details"
4572 jsonIteratorFree( class_itr );
4573 buffer_free( order_buf );
4575 buffer_free( group_buf );
4576 buffer_free( sql_buf );
4577 if( defaultselhash )
4578 jsonObjectFree( defaultselhash );
4582 osrfHash* field_list_def = order_class_info->fields;
4584 if( snode->type == JSON_HASH ) {
4586 // Hash is keyed on field names from the current class. For each field
4587 // there is another layer of hash to define the sorting details, if any,
4588 // or a string to indicate direction of sorting.
4589 jsonIterator* order_itr = jsonNewIterator( snode );
4590 while( (onode = jsonIteratorNext( order_itr )) ) {
4592 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4594 osrfLogError( OSRF_LOG_MARK,
4595 "%s: Invalid field \"%s\" in ORDER BY clause",
4596 modulename, order_itr->key );
4598 osrfAppSessionStatus(
4600 OSRF_STATUS_INTERNALSERVERERROR,
4601 "osrfMethodException",
4603 "Invalid field in ORDER BY clause -- "
4604 "see error log for more details"
4606 jsonIteratorFree( order_itr );
4607 jsonIteratorFree( class_itr );
4608 buffer_free( order_buf );
4610 buffer_free( group_buf );
4611 buffer_free( sql_buf );
4612 if( defaultselhash )
4613 jsonObjectFree( defaultselhash );
4615 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4616 osrfLogError( OSRF_LOG_MARK,
4617 "%s: Virtual field \"%s\" in ORDER BY clause",
4618 modulename, order_itr->key );
4620 osrfAppSessionStatus(
4622 OSRF_STATUS_INTERNALSERVERERROR,
4623 "osrfMethodException",
4625 "Virtual field in ORDER BY clause -- "
4626 "see error log for more details"
4628 jsonIteratorFree( order_itr );
4629 jsonIteratorFree( class_itr );
4630 buffer_free( order_buf );
4632 buffer_free( group_buf );
4633 buffer_free( sql_buf );
4634 if( defaultselhash )
4635 jsonObjectFree( defaultselhash );
4639 const char* direction = NULL;
4640 if( onode->type == JSON_HASH ) {
4641 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4642 string = searchFieldTransform(
4644 osrfHashGet( field_list_def, order_itr->key ),
4648 if( ctx ) osrfAppSessionStatus(
4650 OSRF_STATUS_INTERNALSERVERERROR,
4651 "osrfMethodException",
4653 "Severe query error in ORDER BY clause -- "
4654 "see error log for more details"
4656 jsonIteratorFree( order_itr );
4657 jsonIteratorFree( class_itr );
4659 buffer_free( group_buf );
4660 buffer_free( order_buf);
4661 buffer_free( sql_buf );
4662 if( defaultselhash )
4663 jsonObjectFree( defaultselhash );
4667 growing_buffer* field_buf = buffer_init( 16 );
4668 buffer_fadd( field_buf, "\"%s\".%s",
4669 class_itr->key, order_itr->key );
4670 string = buffer_release( field_buf );
4673 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4674 const char* dir = jsonObjectGetString( tmp_const );
4675 if(!strncasecmp( dir, "d", 1 )) {
4676 direction = " DESC";
4682 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4683 osrfLogError( OSRF_LOG_MARK,
4684 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4685 modulename, json_type( onode->type ) );
4687 osrfAppSessionStatus(
4689 OSRF_STATUS_INTERNALSERVERERROR,
4690 "osrfMethodException",
4692 "Malformed ORDER BY clause -- see error log for more details"
4694 jsonIteratorFree( order_itr );
4695 jsonIteratorFree( class_itr );
4697 buffer_free( group_buf );
4698 buffer_free( order_buf );
4699 buffer_free( sql_buf );
4700 if( defaultselhash )
4701 jsonObjectFree( defaultselhash );
4705 string = strdup( order_itr->key );
4706 const char* dir = jsonObjectGetString( onode );
4707 if( !strncasecmp( dir, "d", 1 )) {
4708 direction = " DESC";
4715 OSRF_BUFFER_ADD( order_buf, ", " );
4717 order_buf = buffer_init( 128 );
4719 OSRF_BUFFER_ADD( order_buf, string );
4723 OSRF_BUFFER_ADD( order_buf, direction );
4727 jsonIteratorFree( order_itr );
4729 } else if( snode->type == JSON_ARRAY ) {
4731 // Array is a list of fields from the current class
4732 unsigned long order_idx = 0;
4733 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4735 const char* _f = jsonObjectGetString( onode );
4737 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4739 osrfLogError( OSRF_LOG_MARK,
4740 "%s: Invalid field \"%s\" in ORDER BY clause",
4743 osrfAppSessionStatus(
4745 OSRF_STATUS_INTERNALSERVERERROR,
4746 "osrfMethodException",
4748 "Invalid field in ORDER BY clause -- "
4749 "see error log for more details"
4751 jsonIteratorFree( class_itr );
4752 buffer_free( order_buf );
4754 buffer_free( group_buf );
4755 buffer_free( sql_buf );
4756 if( defaultselhash )
4757 jsonObjectFree( defaultselhash );
4759 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4760 osrfLogError( OSRF_LOG_MARK,
4761 "%s: Virtual field \"%s\" in ORDER BY clause",
4764 osrfAppSessionStatus(
4766 OSRF_STATUS_INTERNALSERVERERROR,
4767 "osrfMethodException",
4769 "Virtual field in ORDER BY clause -- "
4770 "see error log for more details"
4772 jsonIteratorFree( class_itr );
4773 buffer_free( order_buf );
4775 buffer_free( group_buf );
4776 buffer_free( sql_buf );
4777 if( defaultselhash )
4778 jsonObjectFree( defaultselhash );
4783 OSRF_BUFFER_ADD( order_buf, ", " );
4785 order_buf = buffer_init( 128 );
4787 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4791 // IT'S THE OOOOOOOOOOOLD STYLE!
4793 osrfLogError( OSRF_LOG_MARK,
4794 "%s: Possible SQL injection attempt; direct order by is not allowed",
4797 osrfAppSessionStatus(
4799 OSRF_STATUS_INTERNALSERVERERROR,
4800 "osrfMethodException",
4802 "Severe query error -- see error log for more details"
4807 buffer_free( group_buf );
4808 buffer_free( order_buf );
4809 buffer_free( sql_buf );
4810 if( defaultselhash )
4811 jsonObjectFree( defaultselhash );
4812 jsonIteratorFree( class_itr );
4816 jsonIteratorFree( class_itr );
4818 order_by_list = buffer_release( order_buf );
4820 osrfLogError( OSRF_LOG_MARK,
4821 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4822 modulename, json_type( order_hash->type ) );
4824 osrfAppSessionStatus(
4826 OSRF_STATUS_INTERNALSERVERERROR,
4827 "osrfMethodException",
4829 "Malformed ORDER BY clause -- see error log for more details"
4832 buffer_free( group_buf );
4833 buffer_free( sql_buf );
4834 if( defaultselhash )
4835 jsonObjectFree( defaultselhash );
4840 string = buffer_release( group_buf );
4842 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4843 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4844 OSRF_BUFFER_ADD( sql_buf, string );
4849 if( having_buf && *having_buf ) {
4850 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4851 OSRF_BUFFER_ADD( sql_buf, having_buf );
4855 if( order_by_list ) {
4857 if( *order_by_list ) {
4858 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4859 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4862 free( order_by_list );
4866 const char* str = jsonObjectGetString( limit );
4867 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4871 const char* str = jsonObjectGetString( offset );
4872 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4875 if( !(flags & SUBSELECT) )
4876 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4878 if( defaultselhash )
4879 jsonObjectFree( defaultselhash );
4881 return buffer_release( sql_buf );
4883 } // end of SELECT()
4886 @brief Build a list of ORDER BY expressions.
4887 @param ctx Pointer to the method context.
4888 @param order_array Pointer to a JSON_ARRAY of field specifications.
4889 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
4890 Each expression may be either a column reference or a function call whose first parameter
4891 is a column reference.
4893 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
4894 It may optionally include entries for "direction" and/or "transform".
4896 The calling code is responsible for freeing the returned string.
4898 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
4899 if( ! order_array ) {
4900 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
4903 osrfAppSessionStatus(
4905 OSRF_STATUS_INTERNALSERVERERROR,
4906 "osrfMethodException",
4908 "Logic error: ORDER BY clause expected, not found; "
4909 "see error log for more details"
4912 } else if( order_array->type != JSON_ARRAY ) {
4913 osrfLogError( OSRF_LOG_MARK,
4914 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
4916 osrfAppSessionStatus(
4918 OSRF_STATUS_INTERNALSERVERERROR,
4919 "osrfMethodException",
4921 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
4925 growing_buffer* order_buf = buffer_init( 128 );
4926 int first = 1; // boolean
4928 jsonObject* order_spec;
4929 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
4931 if( JSON_HASH != order_spec->type ) {
4932 osrfLogError( OSRF_LOG_MARK,
4933 "%s: Malformed field specification in ORDER BY clause; "
4934 "expected JSON_HASH, found %s",
4935 modulename, json_type( order_spec->type ) );
4937 osrfAppSessionStatus(
4939 OSRF_STATUS_INTERNALSERVERERROR,
4940 "osrfMethodException",
4942 "Malformed ORDER BY clause -- see error log for more details"
4944 buffer_free( order_buf );
4948 const char* class_alias =
4949 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
4951 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
4953 if( !field || !class_alias ) {
4954 osrfLogError( OSRF_LOG_MARK,
4955 "%s: Missing class or field name in field specification of ORDER BY clause",
4958 osrfAppSessionStatus(
4960 OSRF_STATUS_INTERNALSERVERERROR,
4961 "osrfMethodException",
4963 "Malformed ORDER BY clause -- see error log for more details"
4965 buffer_free( order_buf );
4969 const ClassInfo* order_class_info = search_alias( class_alias );
4970 if( ! order_class_info ) {
4971 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4972 "not in FROM clause, skipping it", modulename, class_alias );
4976 // Add a separating comma, except at the beginning
4980 OSRF_BUFFER_ADD( order_buf, ", " );
4982 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4984 osrfLogError( OSRF_LOG_MARK,
4985 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4986 modulename, class_alias, field );
4988 osrfAppSessionStatus(
4990 OSRF_STATUS_INTERNALSERVERERROR,
4991 "osrfMethodException",
4993 "Invalid field referenced in ORDER BY clause -- "
4994 "see error log for more details"
4998 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4999 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5000 modulename, field );
5002 osrfAppSessionStatus(
5004 OSRF_STATUS_INTERNALSERVERERROR,
5005 "osrfMethodException",
5007 "Virtual field in ORDER BY clause -- see error log for more details"
5009 buffer_free( order_buf );
5013 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5014 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5015 if( ! transform_str ) {
5017 osrfAppSessionStatus(
5019 OSRF_STATUS_INTERNALSERVERERROR,
5020 "osrfMethodException",
5022 "Severe query error in ORDER BY clause -- "
5023 "see error log for more details"
5025 buffer_free( order_buf );
5029 OSRF_BUFFER_ADD( order_buf, transform_str );
5030 free( transform_str );
5033 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5035 const char* direction =
5036 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5038 if( direction[ 0 ] || 'D' == direction[ 0 ] )
5039 OSRF_BUFFER_ADD( order_buf, " DESC" );
5041 OSRF_BUFFER_ADD( order_buf, " ASC" );
5045 return buffer_release( order_buf );
5049 @brief Build a SELECT statement.
5050 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5051 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5052 @param meta Pointer to the class metadata for the core class.
5053 @param ctx Pointer to the method context.
5054 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5056 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5057 "order_by", "limit", and "offset".
5059 The SELECT statements built here are distinct from those built for the json_query method.
5061 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5062 osrfHash* meta, osrfMethodContext* ctx ) {
5064 const char* locale = osrf_message_get_last_locale();
5066 osrfHash* fields = osrfHashGet( meta, "fields" );
5067 const char* core_class = osrfHashGet( meta, "classname" );
5069 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5071 jsonObject* selhash = NULL;
5072 jsonObject* defaultselhash = NULL;
5074 growing_buffer* sql_buf = buffer_init( 128 );
5075 growing_buffer* select_buf = buffer_init( 128 );
5077 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5078 defaultselhash = jsonNewObjectType( JSON_HASH );
5079 selhash = defaultselhash;
5082 // If there's no SELECT list for the core class, build one
5083 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5084 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5086 // Add every non-virtual field to the field list
5087 osrfHash* field_def = NULL;
5088 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5089 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5090 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5091 const char* field = osrfHashIteratorKey( field_itr );
5092 jsonObjectPush( field_list, jsonNewObject( field ) );
5095 osrfHashIteratorFree( field_itr );
5096 jsonObjectSetKey( selhash, core_class, field_list );
5099 // Build a list of columns for the SELECT clause
5101 const jsonObject* snode = NULL;
5102 jsonIterator* class_itr = jsonNewIterator( selhash );
5103 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5105 // If the class isn't in the IDL, ignore it
5106 const char* cname = class_itr->key;
5107 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5111 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5112 if( strcmp( core_class, class_itr->key )) {
5116 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5117 if( !found->size ) {
5118 jsonObjectFree( found );
5122 jsonObjectFree( found );
5125 const jsonObject* node = NULL;
5126 jsonIterator* select_itr = jsonNewIterator( snode );
5127 while( (node = jsonIteratorNext( select_itr )) ) {
5128 const char* item_str = jsonObjectGetString( node );
5129 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5130 char* fname = osrfHashGet( field, "name" );
5138 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5143 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5144 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5147 i18n = osrfHashGet( field, "i18n" );
5149 if( str_is_true( i18n ) ) {
5150 char* pkey = osrfHashGet( idlClass, "primarykey" );
5151 char* tname = osrfHashGet( idlClass, "tablename" );
5153 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5154 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5155 tname, cname, fname, pkey, cname, pkey, locale, fname );
5157 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5160 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5164 jsonIteratorFree( select_itr );
5167 jsonIteratorFree( class_itr );
5169 char* col_list = buffer_release( select_buf );
5170 char* table = oilsGetRelation( meta );
5172 table = strdup( "(null)" );
5174 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5178 // Clear the query stack (as a fail-safe precaution against possible
5179 // leftover garbage); then push the first query frame onto the stack.
5180 clear_query_stack();
5182 if( add_query_core( NULL, core_class ) ) {
5184 osrfAppSessionStatus(
5186 OSRF_STATUS_INTERNALSERVERERROR,
5187 "osrfMethodException",
5189 "Unable to build query frame for core class"
5191 buffer_free( sql_buf );
5192 if( defaultselhash )
5193 jsonObjectFree( defaultselhash );
5197 // Add the JOIN clauses, if any
5199 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5200 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5201 OSRF_BUFFER_ADD( sql_buf, join_clause );
5202 free( join_clause );
5205 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5206 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5208 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5210 // Add the conditions in the WHERE clause
5211 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5213 osrfAppSessionStatus(
5215 OSRF_STATUS_INTERNALSERVERERROR,
5216 "osrfMethodException",
5218 "Severe query error -- see error log for more details"
5220 buffer_free( sql_buf );
5221 if( defaultselhash )
5222 jsonObjectFree( defaultselhash );
5223 clear_query_stack();
5226 buffer_add( sql_buf, pred );
5230 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5231 if( rest_of_query ) {
5232 const jsonObject* order_by = NULL;
5233 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5235 char* order_by_list = NULL;
5237 if( JSON_ARRAY == order_by->type ) {
5238 order_by_list = buildOrderByFromArray( ctx, order_by );
5239 if( !order_by_list ) {
5240 buffer_free( sql_buf );
5241 if( defaultselhash )
5242 jsonObjectFree( defaultselhash );
5243 clear_query_stack();
5246 } else if( JSON_HASH == order_by->type ) {
5247 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5248 // and build a list of ORDER BY expressions.
5249 growing_buffer* order_buf = buffer_init( 128 );
5251 jsonIterator* class_itr = jsonNewIterator( order_by );
5252 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5254 ClassInfo* order_class_info = search_alias( class_itr->key );
5255 if( ! order_class_info )
5256 continue; // class not referenced by FROM clause? Ignore it.
5258 if( JSON_HASH == snode->type ) {
5260 // If the data for the current class is a JSON_HASH, then it is
5261 // keyed on field name.
5263 const jsonObject* onode = NULL;
5264 jsonIterator* order_itr = jsonNewIterator( snode );
5265 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5267 osrfHash* field_def = osrfHashGet(
5268 order_class_info->fields, order_itr->key );
5270 continue; // Field not defined in IDL? Ignore it.
5271 if( str_is_true( osrfHashGet( field_def, "virtual")))
5272 continue; // Field is virtual? Ignore it.
5274 char* field_str = NULL;
5275 char* direction = NULL;
5276 if( onode->type == JSON_HASH ) {
5277 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5278 field_str = searchFieldTransform(
5279 class_itr->key, field_def, onode );
5281 osrfAppSessionStatus(
5283 OSRF_STATUS_INTERNALSERVERERROR,
5284 "osrfMethodException",
5286 "Severe query error in ORDER BY clause -- "
5287 "see error log for more details"
5289 jsonIteratorFree( order_itr );
5290 jsonIteratorFree( class_itr );
5291 buffer_free( order_buf );
5292 buffer_free( sql_buf );
5293 if( defaultselhash )
5294 jsonObjectFree( defaultselhash );
5295 clear_query_stack();
5299 growing_buffer* field_buf = buffer_init( 16 );
5300 buffer_fadd( field_buf, "\"%s\".%s",
5301 class_itr->key, order_itr->key );
5302 field_str = buffer_release( field_buf );
5305 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5306 const char* dir = jsonObjectGetString( order_by );
5307 if(!strncasecmp( dir, "d", 1 )) {
5308 direction = " DESC";
5312 field_str = strdup( order_itr->key );
5313 const char* dir = jsonObjectGetString( onode );
5314 if( !strncasecmp( dir, "d", 1 )) {
5315 direction = " DESC";
5324 buffer_add( order_buf, ", " );
5327 buffer_add( order_buf, field_str );
5331 buffer_add( order_buf, direction );
5333 } // end while; looping over ORDER BY expressions
5335 jsonIteratorFree( order_itr );
5337 } else if( JSON_STRING == snode->type ) {
5338 // We expect a comma-separated list of sort fields.
5339 const char* str = jsonObjectGetString( snode );
5340 if( strchr( str, ';' )) {
5341 // No semicolons allowed. It is theoretically possible for a
5342 // legitimate semicolon to occur within quotes, but it's not likely
5343 // to occur in practice in the context of an ORDER BY list.
5344 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5345 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5347 osrfAppSessionStatus(
5349 OSRF_STATUS_INTERNALSERVERERROR,
5350 "osrfMethodException",
5352 "Possible attempt at SOL injection -- "
5353 "semicolon found in ORDER BY list"
5356 jsonIteratorFree( class_itr );
5357 buffer_free( order_buf );
5358 buffer_free( sql_buf );
5359 if( defaultselhash )
5360 jsonObjectFree( defaultselhash );
5361 clear_query_stack();
5364 buffer_add( order_buf, str );
5368 } // end while; looping over order_by classes
5370 jsonIteratorFree( class_itr );
5371 order_by_list = buffer_release( order_buf );
5374 osrfLogWarning( OSRF_LOG_MARK,
5375 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5376 "no ORDER BY generated" );
5379 if( order_by_list && *order_by_list ) {
5380 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5381 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5384 free( order_by_list );
5387 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5389 const char* str = jsonObjectGetString( limit );
5397 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5399 const char* str = jsonObjectGetString( offset );
5408 if( defaultselhash )
5409 jsonObjectFree( defaultselhash );
5410 clear_query_stack();
5412 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5413 return buffer_release( sql_buf );
5416 int doJSONSearch ( osrfMethodContext* ctx ) {
5417 if(osrfMethodVerifyContext( ctx )) {
5418 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5422 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5426 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5430 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5431 flags |= SELECT_DISTINCT;
5433 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5434 flags |= DISABLE_I18N;
5436 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5437 clear_query_stack(); // a possibly needless precaution
5438 char* sql = buildQuery( ctx, hash, flags );
5439 clear_query_stack();
5446 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5449 dbhandle = writehandle;
5451 dbi_result result = dbi_conn_query( dbhandle, sql );
5454 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5456 if( dbi_result_first_row( result )) {
5457 /* JSONify the result */
5458 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5461 jsonObject* return_val = oilsMakeJSONFromResult( result );
5462 osrfAppRespond( ctx, return_val );
5463 jsonObjectFree( return_val );
5464 } while( dbi_result_next_row( result ));
5467 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5470 osrfAppRespondComplete( ctx, NULL );
5472 /* clean up the query */
5473 dbi_result_free( result );
5478 int errnum = dbi_conn_error( dbhandle, &msg );
5479 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5480 modulename, sql, errnum, msg ? msg : "(No description available)" );
5481 osrfAppSessionStatus(
5483 OSRF_STATUS_INTERNALSERVERERROR,
5484 "osrfMethodException",
5486 "Severe query error -- see error log for more details"
5488 if( !oilsIsDBConnected( dbhandle ))
5489 osrfAppSessionPanic( ctx->session );
5496 // The last parameter, err, is used to report an error condition by updating an int owned by
5497 // the calling code.
5499 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5500 // It is the responsibility of the calling code to initialize *err before the
5501 // call, so that it will be able to make sense of the result.
5503 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5504 // redundant anyway.
5505 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5506 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5509 dbhandle = writehandle;
5511 char* core_class = osrfHashGet( class_meta, "classname" );
5512 char* pkey = osrfHashGet( class_meta, "primarykey" );
5514 const jsonObject* _tmp;
5516 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5518 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5523 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5525 dbi_result result = dbi_conn_query( dbhandle, sql );
5526 if( NULL == result ) {
5528 int errnum = dbi_conn_error( dbhandle, &msg );
5529 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5530 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5531 msg ? msg : "(No description available)" );
5532 if( !oilsIsDBConnected( dbhandle ))
5533 osrfAppSessionPanic( ctx->session );
5534 osrfAppSessionStatus(
5536 OSRF_STATUS_INTERNALSERVERERROR,
5537 "osrfMethodException",
5539 "Severe query error -- see error log for more details"
5546 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5549 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5550 jsonObject* row_obj = NULL;
5552 if( dbi_result_first_row( result )) {
5554 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5555 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5556 // eliminate the duplicates.
5557 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5558 osrfHash* dedup = osrfNewHash();
5560 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5561 char* pkey_val = oilsFMGetString( row_obj, pkey );
5562 if( osrfHashGet( dedup, pkey_val ) ) {
5563 jsonObjectFree( row_obj );
5566 osrfHashSet( dedup, pkey_val, pkey_val );
5567 jsonObjectPush( res_list, row_obj );
5569 } while( dbi_result_next_row( result ));
5570 osrfHashFree( dedup );
5573 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5577 /* clean up the query */
5578 dbi_result_free( result );
5581 // If we're asked to flesh, and there's anything to flesh, then flesh it
5582 // (but not for PCRUD, lest the user to bypass permissions by fleshing
5583 // something that he has no permission to look at).
5584 if( res_list->size && query_hash && ! enforce_pcrud ) {
5585 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5587 // Get the flesh depth
5588 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5589 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5590 flesh_depth = max_flesh_depth;
5592 // We need a non-zero flesh depth, and a list of fields to flesh
5593 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5594 if( temp_blob && flesh_depth > 0 ) {
5596 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5597 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5599 osrfStringArray* link_fields = NULL;
5600 osrfHash* links = osrfHashGet( class_meta, "links" );
5602 // Make an osrfStringArray of the names of fields to be fleshed
5603 if( flesh_fields ) {
5604 if( flesh_fields->size == 1 ) {
5605 const char* _t = jsonObjectGetString(
5606 jsonObjectGetIndex( flesh_fields, 0 ) );
5607 if( !strcmp( _t, "*" ))
5608 link_fields = osrfHashKeys( links );
5611 if( !link_fields ) {
5613 link_fields = osrfNewStringArray( 1 );
5614 jsonIterator* _i = jsonNewIterator( flesh_fields );
5615 while ((_f = jsonIteratorNext( _i ))) {
5616 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5618 jsonIteratorFree( _i );
5622 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5624 // Iterate over the JSON_ARRAY of rows
5626 unsigned long res_idx = 0;
5627 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5630 const char* link_field;
5632 // Iterate over the list of fleshable fields
5633 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5635 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5637 osrfHash* kid_link = osrfHashGet( links, link_field );
5639 continue; // Not a link field; skip it
5641 osrfHash* field = osrfHashGet( fields, link_field );
5643 continue; // Not a field at all; skip it (IDL is ill-formed)
5645 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5646 osrfHashGet( kid_link, "class" ));
5648 continue; // The class it links to doesn't exist; skip it
5650 const char* reltype = osrfHashGet( kid_link, "reltype" );
5652 continue; // No reltype; skip it (IDL is ill-formed)
5654 osrfHash* value_field = field;
5656 if( !strcmp( reltype, "has_many" )
5657 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5658 value_field = osrfHashGet(
5659 fields, osrfHashGet( class_meta, "primarykey" ) );
5662 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5664 if( link_map->size > 0 ) {
5665 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5668 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5673 osrfHashGet( kid_link, "class" ),
5680 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5681 osrfHashGet( kid_link, "field" ),
5682 osrfHashGet( kid_link, "class" ),
5683 osrfHashGet( kid_link, "key" ),
5684 osrfHashGet( kid_link, "reltype" )
5687 const char* search_key = jsonObjectGetString(
5688 jsonObjectGetIndex( cur,
5689 atoi( osrfHashGet( value_field, "array_position" ) )
5694 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5698 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5700 // construct WHERE clause
5701 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5704 osrfHashGet( kid_link, "key" ),
5705 jsonNewObject( search_key )
5708 // construct the rest of the query, mostly
5709 // by copying pieces of the previous level of query
5710 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5711 jsonObjectSetKey( rest_of_query, "flesh",
5712 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5716 jsonObjectSetKey( rest_of_query, "flesh_fields",
5717 jsonObjectClone( flesh_blob ));
5719 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5720 jsonObjectSetKey( rest_of_query, "order_by",
5721 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5725 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5726 jsonObjectSetKey( rest_of_query, "select",
5727 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5731 // do the query, recursively, to expand the fleshable field
5732 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5733 where_clause, rest_of_query, err );
5735 jsonObjectFree( where_clause );
5736 jsonObjectFree( rest_of_query );
5739 osrfStringArrayFree( link_fields );
5740 jsonObjectFree( res_list );
5741 jsonObjectFree( flesh_blob );
5745 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5746 osrfHashGet( kid_link, "class" ), kids->size );
5748 // Traverse the result set
5749 jsonObject* X = NULL;
5750 if( link_map->size > 0 && kids->size > 0 ) {
5752 kids = jsonNewObjectType( JSON_ARRAY );
5754 jsonObject* _k_node;
5755 unsigned long res_idx = 0;
5756 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5762 (unsigned long) atoi(
5768 osrfHashGet( kid_link, "class" )
5772 osrfStringArrayGetString( link_map, 0 )
5780 } // end while loop traversing X
5783 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5784 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5785 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5786 osrfHashGet( kid_link, "field" ));
5789 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5790 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5794 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5796 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5797 osrfHashGet( kid_link, "field" ) );
5800 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5801 jsonObjectClone( kids )
5806 jsonObjectFree( kids );
5810 jsonObjectFree( kids );
5812 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5813 osrfHashGet( kid_link, "field" ) );
5814 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5816 } // end while loop traversing list of fleshable fields
5817 } // end while loop traversing res_list
5818 jsonObjectFree( flesh_blob );
5819 osrfStringArrayFree( link_fields );
5828 int doUpdate( osrfMethodContext* ctx ) {
5829 if( osrfMethodVerifyContext( ctx )) {
5830 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5835 timeout_needs_resetting = 1;
5837 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5839 jsonObject* target = NULL;
5841 target = jsonObjectGetIndex( ctx->params, 1 );
5843 target = jsonObjectGetIndex( ctx->params, 0 );
5845 if(!verifyObjectClass( ctx, target )) {
5846 osrfAppRespondComplete( ctx, NULL );
5850 if( getXactId( ctx ) == NULL ) {
5851 osrfAppSessionStatus(
5853 OSRF_STATUS_BADREQUEST,
5854 "osrfMethodException",
5856 "No active transaction -- required for UPDATE"
5858 osrfAppRespondComplete( ctx, NULL );
5862 // The following test is harmless but redundant. If a class is
5863 // readonly, we don't register an update method for it.
5864 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5865 osrfAppSessionStatus(
5867 OSRF_STATUS_BADREQUEST,
5868 "osrfMethodException",
5870 "Cannot UPDATE readonly class"
5872 osrfAppRespondComplete( ctx, NULL );
5876 const char* trans_id = getXactId( ctx );
5878 // Set the last_xact_id
5879 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5881 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5882 trans_id, target->classname, index );
5883 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5886 char* pkey = osrfHashGet( meta, "primarykey" );
5887 osrfHash* fields = osrfHashGet( meta, "fields" );
5889 char* id = oilsFMGetString( target, pkey );
5893 "%s updating %s object with %s = %s",
5895 osrfHashGet( meta, "fieldmapper" ),
5900 dbhandle = writehandle;
5901 growing_buffer* sql = buffer_init( 128 );
5902 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5905 osrfHash* field_def = NULL;
5906 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5907 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5909 // Skip virtual fields, and the primary key
5910 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5913 const char* field_name = osrfHashIteratorKey( field_itr );
5914 if( ! strcmp( field_name, pkey ) )
5917 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5919 int value_is_numeric = 0; // boolean
5921 if( field_object && field_object->classname ) {
5922 value = oilsFMGetString(
5924 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5926 } else if( field_object && JSON_BOOL == field_object->type ) {
5927 if( jsonBoolIsTrue( field_object ) )
5928 value = strdup( "t" );
5930 value = strdup( "f" );
5932 value = jsonObjectToSimpleString( field_object );
5933 if( field_object && JSON_NUMBER == field_object->type )
5934 value_is_numeric = 1;
5937 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5938 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5940 if( !field_object || field_object->type == JSON_NULL ) {
5941 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5942 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5946 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5947 buffer_fadd( sql, " %s = NULL", field_name );
5950 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5954 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5956 const char* numtype = get_datatype( field_def );
5957 if( !strncmp( numtype, "INT", 3 ) ) {
5958 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5959 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5960 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5962 // Must really be intended as a string, so quote it
5963 if( dbi_conn_quote_string( dbhandle, &value )) {
5964 buffer_fadd( sql, " %s = %s", field_name, value );
5966 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5967 modulename, value );
5968 osrfAppSessionStatus(
5970 OSRF_STATUS_INTERNALSERVERERROR,
5971 "osrfMethodException",
5973 "Error quoting string -- please see the error log for more details"
5977 osrfHashIteratorFree( field_itr );
5979 osrfAppRespondComplete( ctx, NULL );
5984 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5987 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5991 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5992 buffer_fadd( sql, " %s = %s", field_name, value );
5994 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5995 osrfAppSessionStatus(
5997 OSRF_STATUS_INTERNALSERVERERROR,
5998 "osrfMethodException",
6000 "Error quoting string -- please see the error log for more details"
6004 osrfHashIteratorFree( field_itr );
6006 osrfAppRespondComplete( ctx, NULL );
6015 osrfHashIteratorFree( field_itr );
6017 jsonObject* obj = jsonNewObject( id );
6019 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6020 dbi_conn_quote_string( dbhandle, &id );
6022 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6024 char* query = buffer_release( sql );
6025 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6027 dbi_result result = dbi_conn_query( dbhandle, query );
6032 jsonObjectFree( obj );
6033 obj = jsonNewObject( NULL );
6035 int errnum = dbi_conn_error( dbhandle, &msg );
6038 "%s ERROR updating %s object with %s = %s: %d %s",
6040 osrfHashGet( meta, "fieldmapper" ),
6044 msg ? msg : "(No description available)"
6046 osrfAppSessionStatus(
6048 OSRF_STATUS_INTERNALSERVERERROR,
6049 "osrfMethodException",
6051 "Error in updating a row -- please see the error log for more details"
6053 if( !oilsIsDBConnected( dbhandle ))
6054 osrfAppSessionPanic( ctx->session );
6057 dbi_result_free( result );
6060 osrfAppRespondComplete( ctx, obj );
6061 jsonObjectFree( obj );
6065 int doDelete( osrfMethodContext* ctx ) {
6066 if( osrfMethodVerifyContext( ctx )) {
6067 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6072 timeout_needs_resetting = 1;
6074 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6076 if( getXactId( ctx ) == NULL ) {
6077 osrfAppSessionStatus(
6079 OSRF_STATUS_BADREQUEST,
6080 "osrfMethodException",
6082 "No active transaction -- required for DELETE"
6084 osrfAppRespondComplete( ctx, NULL );
6088 // The following test is harmless but redundant. If a class is
6089 // readonly, we don't register a delete method for it.
6090 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6091 osrfAppSessionStatus(
6093 OSRF_STATUS_BADREQUEST,
6094 "osrfMethodException",
6096 "Cannot DELETE readonly class"
6098 osrfAppRespondComplete( ctx, NULL );
6102 dbhandle = writehandle;
6104 char* pkey = osrfHashGet( meta, "primarykey" );
6111 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6112 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6113 osrfAppRespondComplete( ctx, NULL );
6117 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6119 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL, 1 )) {
6120 osrfAppRespondComplete( ctx, NULL );
6123 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6128 "%s deleting %s object with %s = %s",
6130 osrfHashGet( meta, "fieldmapper" ),
6135 jsonObject* obj = jsonNewObject( id );
6137 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6138 dbi_conn_quote_string( writehandle, &id );
6140 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6141 osrfHashGet( meta, "tablename" ), pkey, id );
6146 jsonObjectFree( obj );
6147 obj = jsonNewObject( NULL );
6149 int errnum = dbi_conn_error( writehandle, &msg );
6152 "%s ERROR deleting %s object with %s = %s: %d %s",
6154 osrfHashGet( meta, "fieldmapper" ),
6158 msg ? msg : "(No description available)"
6160 osrfAppSessionStatus(
6162 OSRF_STATUS_INTERNALSERVERERROR,
6163 "osrfMethodException",
6165 "Error in deleting a row -- please see the error log for more details"
6167 if( !oilsIsDBConnected( writehandle ))
6168 osrfAppSessionPanic( ctx->session );
6170 dbi_result_free( result );
6174 osrfAppRespondComplete( ctx, obj );
6175 jsonObjectFree( obj );
6180 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6181 @param result An iterator for a result set; we only look at the current row.
6182 @param @meta Pointer to the class metadata for the core class.
6183 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6185 If a column is not defined in the IDL, or if it has no array_position defined for it in
6186 the IDL, or if it is defined as virtual, ignore it.
6188 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6189 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6190 array_position in the IDL.
6192 A field defined in the IDL but not represented in the returned row will leave a hole
6193 in the JSON_ARRAY. In effect it will be treated as a null value.
6195 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6196 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6197 classname corresponding to the @a meta argument.
6199 The calling code is responsible for freeing the the resulting jsonObject by calling
6202 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6203 if( !( result && meta )) return NULL;
6205 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6206 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6207 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6209 osrfHash* fields = osrfHashGet( meta, "fields" );
6211 int columnIndex = 1;
6212 const char* columnName;
6214 /* cycle through the columns in the row returned from the database */
6215 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6217 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6219 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6221 /* determine the field type and storage attributes */
6222 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6223 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6225 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6226 // or if it has no sequence number there, or if it's virtual, skip it.
6227 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6230 if( str_is_true( osrfHashGet( _f, "virtual" )))
6231 continue; // skip this column: IDL says it's virtual
6233 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6234 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6235 continue; // since we assign sequence numbers dynamically as we load the IDL.
6237 fmIndex = atoi( pos );
6238 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6240 continue; // This field is not defined in the IDL
6243 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6244 // sequence number from the IDL (which is likely to be different from the sequence
6245 // of columns in the SELECT clause).
6246 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6247 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6252 case DBI_TYPE_INTEGER :
6254 if( attr & DBI_INTEGER_SIZE8 )
6255 jsonObjectSetIndex( object, fmIndex,
6256 jsonNewNumberObject(
6257 dbi_result_get_longlong_idx( result, columnIndex )));
6259 jsonObjectSetIndex( object, fmIndex,
6260 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6264 case DBI_TYPE_DECIMAL :
6265 jsonObjectSetIndex( object, fmIndex,
6266 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6269 case DBI_TYPE_STRING :
6274 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6279 case DBI_TYPE_DATETIME : {
6281 char dt_string[ 256 ] = "";
6284 // Fetch the date column as a time_t
6285 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6287 // Translate the time_t to a human-readable string
6288 if( !( attr & DBI_DATETIME_DATE )) {
6289 gmtime_r( &_tmp_dt, &gmdt );
6290 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6291 } else if( !( attr & DBI_DATETIME_TIME )) {
6292 localtime_r( &_tmp_dt, &gmdt );
6293 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6295 localtime_r( &_tmp_dt, &gmdt );
6296 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6299 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6303 case DBI_TYPE_BINARY :
6304 osrfLogError( OSRF_LOG_MARK,
6305 "Can't do binary at column %s : index %d", columnName, columnIndex );
6314 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6315 if( !result ) return NULL;
6317 jsonObject* object = jsonNewObject( NULL );
6320 char dt_string[ 256 ];
6324 int columnIndex = 1;
6326 unsigned short type;
6327 const char* columnName;
6329 /* cycle through the column list */
6330 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6332 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6334 fmIndex = -1; // reset the position
6336 /* determine the field type and storage attributes */
6337 type = dbi_result_get_field_type_idx( result, columnIndex );
6338 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6340 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6341 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6346 case DBI_TYPE_INTEGER :
6348 if( attr & DBI_INTEGER_SIZE8 )
6349 jsonObjectSetKey( object, columnName,
6350 jsonNewNumberObject( dbi_result_get_longlong_idx(
6351 result, columnIndex )) );
6353 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6354 dbi_result_get_int_idx( result, columnIndex )) );
6357 case DBI_TYPE_DECIMAL :
6358 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6359 dbi_result_get_double_idx( result, columnIndex )) );
6362 case DBI_TYPE_STRING :
6363 jsonObjectSetKey( object, columnName,
6364 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6367 case DBI_TYPE_DATETIME :
6369 memset( dt_string, '\0', sizeof( dt_string ));
6370 memset( &gmdt, '\0', sizeof( gmdt ));
6372 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6374 if( !( attr & DBI_DATETIME_DATE )) {
6375 gmtime_r( &_tmp_dt, &gmdt );
6376 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6377 } else if( !( attr & DBI_DATETIME_TIME )) {
6378 localtime_r( &_tmp_dt, &gmdt );
6379 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6381 localtime_r( &_tmp_dt, &gmdt );
6382 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6385 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6388 case DBI_TYPE_BINARY :
6389 osrfLogError( OSRF_LOG_MARK,
6390 "Can't do binary at column %s : index %d", columnName, columnIndex );
6394 } // end while loop traversing result
6399 // Interpret a string as true or false
6400 int str_is_true( const char* str ) {
6401 if( NULL == str || strcasecmp( str, "true" ) )
6407 // Interpret a jsonObject as true or false
6408 static int obj_is_true( const jsonObject* obj ) {
6411 else switch( obj->type )
6419 if( strcasecmp( obj->value.s, "true" ) )
6423 case JSON_NUMBER : // Support 1/0 for perl's sake
6424 if( jsonObjectGetNumber( obj ) == 1.0 )
6433 // Translate a numeric code into a text string identifying a type of
6434 // jsonObject. To be used for building error messages.
6435 static const char* json_type( int code ) {
6441 return "JSON_ARRAY";
6443 return "JSON_STRING";
6445 return "JSON_NUMBER";
6451 return "(unrecognized)";
6455 // Extract the "primitive" attribute from an IDL field definition.
6456 // If we haven't initialized the app, then we must be running in
6457 // some kind of testbed. In that case, default to "string".
6458 static const char* get_primitive( osrfHash* field ) {
6459 const char* s = osrfHashGet( field, "primitive" );
6461 if( child_initialized )
6464 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6466 osrfHashGet( field, "name" )
6474 // Extract the "datatype" attribute from an IDL field definition.
6475 // If we haven't initialized the app, then we must be running in
6476 // some kind of testbed. In that case, default to to NUMERIC,
6477 // since we look at the datatype only for numbers.
6478 static const char* get_datatype( osrfHash* field ) {
6479 const char* s = osrfHashGet( field, "datatype" );
6481 if( child_initialized )
6484 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6486 osrfHashGet( field, "name" )
6495 @brief Determine whether a string is potentially a valid SQL identifier.
6496 @param s The identifier to be tested.
6497 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6499 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6500 need to follow all the rules exactly, such as requiring that the first character not
6503 We allow leading and trailing white space. In between, we do not allow punctuation
6504 (except for underscores and dollar signs), control characters, or embedded white space.
6506 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6507 for the foreseeable future such quoted identifiers are not likely to be an issue.
6509 int is_identifier( const char* s) {
6513 // Skip leading white space
6514 while( isspace( (unsigned char) *s ) )
6518 return 0; // Nothing but white space? Not okay.
6520 // Check each character until we reach white space or
6521 // end-of-string. Letters, digits, underscores, and
6522 // dollar signs are okay. With the exception of periods
6523 // (as in schema.identifier), control characters and other
6524 // punctuation characters are not okay. Anything else
6525 // is okay -- it could for example be part of a multibyte
6526 // UTF8 character such as a letter with diacritical marks,
6527 // and those are allowed.
6529 if( isalnum( (unsigned char) *s )
6533 ; // Fine; keep going
6534 else if( ispunct( (unsigned char) *s )
6535 || iscntrl( (unsigned char) *s ) )
6538 } while( *s && ! isspace( (unsigned char) *s ) );
6540 // If we found any white space in the above loop,
6541 // the rest had better be all white space.
6543 while( isspace( (unsigned char) *s ) )
6547 return 0; // White space was embedded within non-white space
6553 @brief Determine whether to accept a character string as a comparison operator.
6554 @param op The candidate comparison operator.
6555 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6557 We don't validate the operator for real. We just make sure that it doesn't contain
6558 any semicolons or white space (with special exceptions for a few specific operators).
6559 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6560 space but it's still not a valid operator, then the database will complain.
6562 Another approach would be to compare the string against a short list of approved operators.
6563 We don't do that because we want to allow custom operators like ">100*", which at this
6564 writing would be difficult or impossible to express otherwise in a JSON query.
6566 int is_good_operator( const char* op ) {
6567 if( !op ) return 0; // Sanity check
6571 if( isspace( (unsigned char) *s ) ) {
6572 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6573 // and IS NOT DISTINCT FROM.
6574 if( !strcasecmp( op, "similar to" ) )
6576 else if( !strcasecmp( op, "is distinct from" ) )
6578 else if( !strcasecmp( op, "is not distinct from" ) )
6583 else if( ';' == *s )
6591 @name Query Frame Management
6593 The following machinery supports a stack of query frames for use by SELECT().
6595 A query frame caches information about one level of a SELECT query. When we enter
6596 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6598 The query frame stores information about the core class, and about any joined classes
6601 The main purpose is to map table aliases to classes and tables, so that a query can
6602 join to the same table more than once. A secondary goal is to reduce the number of
6603 lookups in the IDL by caching the results.
6607 #define STATIC_CLASS_INFO_COUNT 3
6609 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6612 @brief Allocate a ClassInfo as raw memory.
6613 @return Pointer to the newly allocated ClassInfo.
6615 Except for the in_use flag, which is used only by the allocation and deallocation
6616 logic, we don't initialize the ClassInfo here.
6618 static ClassInfo* allocate_class_info( void ) {
6619 // In order to reduce the number of mallocs and frees, we return a static
6620 // instance of ClassInfo, if we can find one that we're not already using.
6621 // We rely on the fact that the compiler will implicitly initialize the
6622 // static instances so that in_use == 0.
6625 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6626 if( ! static_class_info[ i ].in_use ) {
6627 static_class_info[ i ].in_use = 1;
6628 return static_class_info + i;
6632 // The static ones are all in use. Malloc one.
6634 return safe_malloc( sizeof( ClassInfo ) );
6638 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6639 @param info Pointer to the ClassInfo to be cleared.
6641 static void clear_class_info( ClassInfo* info ) {
6646 // Free any malloc'd strings
6648 if( info->alias != info->alias_store )
6649 free( info->alias );
6651 if( info->class_name != info->class_name_store )
6652 free( info->class_name );
6654 free( info->source_def );
6656 info->alias = info->class_name = info->source_def = NULL;
6661 @brief Free a ClassInfo and everything it owns.
6662 @param info Pointer to the ClassInfo to be freed.
6664 static void free_class_info( ClassInfo* info ) {
6669 clear_class_info( info );
6671 // If it's one of the static instances, just mark it as not in use
6674 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6675 if( info == static_class_info + i ) {
6676 static_class_info[ i ].in_use = 0;
6681 // Otherwise it must have been malloc'd, so free it
6687 @brief Populate an already-allocated ClassInfo.
6688 @param info Pointer to the ClassInfo to be populated.
6689 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6691 @param class Name of the class.
6692 @return Zero if successful, or 1 if not.
6694 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6695 the relevant portions of the IDL for the specified class.
6697 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6700 osrfLogError( OSRF_LOG_MARK,
6701 "%s ERROR: No ClassInfo available to populate", modulename );
6702 info->alias = info->class_name = info->source_def = NULL;
6703 info->class_def = info->fields = info->links = NULL;
6708 osrfLogError( OSRF_LOG_MARK,
6709 "%s ERROR: No class name provided for lookup", modulename );
6710 info->alias = info->class_name = info->source_def = NULL;
6711 info->class_def = info->fields = info->links = NULL;
6715 // Alias defaults to class name if not supplied
6716 if( ! alias || ! alias[ 0 ] )
6719 // Look up class info in the IDL
6720 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6722 osrfLogError( OSRF_LOG_MARK,
6723 "%s ERROR: Class %s not defined in IDL", modulename, class );
6724 info->alias = info->class_name = info->source_def = NULL;
6725 info->class_def = info->fields = info->links = NULL;
6727 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6728 osrfLogError( OSRF_LOG_MARK,
6729 "%s ERROR: Class %s is defined as virtual", modulename, class );
6730 info->alias = info->class_name = info->source_def = NULL;
6731 info->class_def = info->fields = info->links = NULL;
6735 osrfHash* links = osrfHashGet( class_def, "links" );
6737 osrfLogError( OSRF_LOG_MARK,
6738 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6739 info->alias = info->class_name = info->source_def = NULL;
6740 info->class_def = info->fields = info->links = NULL;
6744 osrfHash* fields = osrfHashGet( class_def, "fields" );
6746 osrfLogError( OSRF_LOG_MARK,
6747 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6748 info->alias = info->class_name = info->source_def = NULL;
6749 info->class_def = info->fields = info->links = NULL;
6753 char* source_def = oilsGetRelation( class_def );
6757 // We got everything we need, so populate the ClassInfo
6758 if( strlen( alias ) > ALIAS_STORE_SIZE )
6759 info->alias = strdup( alias );
6761 strcpy( info->alias_store, alias );
6762 info->alias = info->alias_store;
6765 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6766 info->class_name = strdup( class );
6768 strcpy( info->class_name_store, class );
6769 info->class_name = info->class_name_store;
6772 info->source_def = source_def;
6774 info->class_def = class_def;
6775 info->links = links;
6776 info->fields = fields;
6781 #define STATIC_FRAME_COUNT 3
6783 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6786 @brief Allocate a QueryFrame as raw memory.
6787 @return Pointer to the newly allocated QueryFrame.
6789 Except for the in_use flag, which is used only by the allocation and deallocation
6790 logic, we don't initialize the QueryFrame here.
6792 static QueryFrame* allocate_frame( void ) {
6793 // In order to reduce the number of mallocs and frees, we return a static
6794 // instance of QueryFrame, if we can find one that we're not already using.
6795 // We rely on the fact that the compiler will implicitly initialize the
6796 // static instances so that in_use == 0.
6799 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6800 if( ! static_frame[ i ].in_use ) {
6801 static_frame[ i ].in_use = 1;
6802 return static_frame + i;
6806 // The static ones are all in use. Malloc one.
6808 return safe_malloc( sizeof( QueryFrame ) );
6812 @brief Free a QueryFrame, and all the memory it owns.
6813 @param frame Pointer to the QueryFrame to be freed.
6815 static void free_query_frame( QueryFrame* frame ) {
6820 clear_class_info( &frame->core );
6822 // Free the join list
6824 ClassInfo* info = frame->join_list;
6827 free_class_info( info );
6831 frame->join_list = NULL;
6834 // If the frame is a static instance, just mark it as unused
6836 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6837 if( frame == static_frame + i ) {
6838 static_frame[ i ].in_use = 0;
6843 // Otherwise it must have been malloc'd, so free it
6849 @brief Search a given QueryFrame for a specified alias.
6850 @param frame Pointer to the QueryFrame to be searched.
6851 @param target The alias for which to search.
6852 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6854 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6855 if( ! frame || ! target ) {
6859 ClassInfo* found_class = NULL;
6861 if( !strcmp( target, frame->core.alias ) )
6862 return &(frame->core);
6864 ClassInfo* curr_class = frame->join_list;
6865 while( curr_class ) {
6866 if( strcmp( target, curr_class->alias ) )
6867 curr_class = curr_class->next;
6869 found_class = curr_class;
6879 @brief Push a new (blank) QueryFrame onto the stack.
6881 static void push_query_frame( void ) {
6882 QueryFrame* frame = allocate_frame();
6883 frame->join_list = NULL;
6884 frame->next = curr_query;
6886 // Initialize the ClassInfo for the core class
6887 ClassInfo* core = &frame->core;
6888 core->alias = core->class_name = core->source_def = NULL;
6889 core->class_def = core->fields = core->links = NULL;
6895 @brief Pop a QueryFrame off the stack and destroy it.
6897 static void pop_query_frame( void ) {
6902 QueryFrame* popped = curr_query;
6903 curr_query = popped->next;
6905 free_query_frame( popped );
6909 @brief Populate the ClassInfo for the core class.
6910 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6911 class name as an alias.
6912 @param class_name Name of the core class.
6913 @return Zero if successful, or 1 if not.
6915 Populate the ClassInfo of the core class with copies of the alias and class name, and
6916 with pointers to the relevant portions of the IDL for the core class.
6918 static int add_query_core( const char* alias, const char* class_name ) {
6921 if( ! curr_query ) {
6922 osrfLogError( OSRF_LOG_MARK,
6923 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6925 } else if( curr_query->core.alias ) {
6926 osrfLogError( OSRF_LOG_MARK,
6927 "%s ERROR: Core class %s already populated as %s",
6928 modulename, curr_query->core.class_name, curr_query->core.alias );
6932 build_class_info( &curr_query->core, alias, class_name );
6933 if( curr_query->core.alias )
6936 osrfLogError( OSRF_LOG_MARK,
6937 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6943 @brief Search the current QueryFrame for a specified alias.
6944 @param target The alias for which to search.
6945 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6947 static inline ClassInfo* search_alias( const char* target ) {
6948 return search_alias_in_frame( curr_query, target );
6952 @brief Search all levels of query for a specified alias, starting with the current query.
6953 @param target The alias for which to search.
6954 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6956 static ClassInfo* search_all_alias( const char* target ) {
6957 ClassInfo* found_class = NULL;
6958 QueryFrame* curr_frame = curr_query;
6960 while( curr_frame ) {
6961 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6964 curr_frame = curr_frame->next;
6971 @brief Add a class to the list of classes joined to the current query.
6972 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6973 the class name as an alias.
6974 @param classname The name of the class to be added.
6975 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6977 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6979 if( ! classname || ! *classname ) { // sanity check
6980 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6987 const ClassInfo* conflict = search_alias( alias );
6989 osrfLogError( OSRF_LOG_MARK,
6990 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6991 modulename, alias, conflict->class_name );
6995 ClassInfo* info = allocate_class_info();
6997 if( build_class_info( info, alias, classname ) ) {
6998 free_class_info( info );
7002 // Add the new ClassInfo to the join list of the current QueryFrame
7003 info->next = curr_query->join_list;
7004 curr_query->join_list = info;
7010 @brief Destroy all nodes on the query stack.
7012 static void clear_query_stack( void ) {