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*, osrfHash*, const jsonObject*, 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" ) || !strncmp( key, "rs_size_", 8) )
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 Initialize session cache.
527 @param ctx Pointer to the method context.
529 Create a cache for the session by making the session's userData member point
530 to an osrfHash instance.
532 static osrfHash* initSessionCache( osrfMethodContext* ctx ) {
533 ctx->session->userData = osrfNewHash();
534 osrfHashSetCallback( (osrfHash*) ctx->session->userData, &sessionDataFree );
535 ctx->session->userDataFree = &userDataFree;
536 return ctx->session->userData;
540 @brief Save a transaction id.
541 @param ctx Pointer to the method context.
543 Save the session_id of the current application session as a transaction id.
545 static void setXactId( osrfMethodContext* ctx ) {
546 if( ctx && ctx->session ) {
547 osrfAppSession* session = ctx->session;
549 osrfHash* cache = session->userData;
551 // If the session doesn't already have a hash, create one. Make sure
552 // that the application session frees the hash when it terminates.
554 cache = initSessionCache( ctx );
556 // Save the transaction id in the hash, with the key "xact_id"
557 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
562 @brief Get the transaction ID for the current transaction, if any.
563 @param ctx Pointer to the method context.
564 @return Pointer to the transaction ID.
566 The return value points to an internal buffer, and will become invalid upon issuing
567 a commit or rollback.
569 static inline const char* getXactId( osrfMethodContext* ctx ) {
570 if( ctx && ctx->session && ctx->session->userData )
571 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
577 @brief Clear the current transaction id.
578 @param ctx Pointer to the method context.
580 static inline void clearXactId( osrfMethodContext* ctx ) {
581 if( ctx && ctx->session && ctx->session->userData )
582 osrfHashRemove( ctx->session->userData, "xact_id" );
587 @brief Stash the location for a particular perm in the sessionData cache
588 @param ctx Pointer to the method context.
589 @param perm Name of the permission we're looking at
590 @param array StringArray of perm location ids
592 static void setPermLocationCache( osrfMethodContext* ctx, const char* perm, osrfStringArray* locations ) {
593 if( ctx && ctx->session ) {
594 osrfAppSession* session = ctx->session;
596 osrfHash* cache = session->userData;
598 // If the session doesn't already have a hash, create one. Make sure
599 // that the application session frees the hash when it terminates.
601 cache = initSessionCache( ctx );
603 osrfHash* pcache = osrfHashGet(cache, "pcache");
605 if( NULL == pcache ) {
606 pcache = osrfNewHash();
607 osrfHashSetCallback( pcache, &pcacheFree );
608 osrfHashSet( cache, pcache, "pcache" );
611 if( perm && locations )
612 osrfHashSet( pcache, locations, strdup(perm) );
617 @brief Grab stashed location for a particular perm in the sessionData cache
618 @param ctx Pointer to the method context.
619 @param perm Name of the permission we're looking at
621 static osrfStringArray* getPermLocationCache( osrfMethodContext* ctx, const char* perm ) {
622 if( ctx && ctx->session ) {
623 osrfAppSession* session = ctx->session;
624 osrfHash* cache = session->userData;
626 osrfHash* pcache = osrfHashGet(cache, "pcache");
628 return osrfHashGet( pcache, perm );
637 @brief Save the user's login in the userData for the current application session.
638 @param ctx Pointer to the method context.
639 @param user_login Pointer to the user login object to be cached (we cache the original,
642 If @a user_login is NULL, remove the user login if one is already cached.
644 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
645 if( ctx && ctx->session ) {
646 osrfAppSession* session = ctx->session;
648 osrfHash* cache = session->userData;
650 // If the session doesn't already have a hash, create one. Make sure
651 // that the application session frees the hash when it terminates.
653 cache = initSessionCache( ctx );
656 osrfHashSet( cache, user_login, "user_login" );
658 osrfHashRemove( cache, "user_login" );
663 @brief Get the user login object for the current application session, if any.
664 @param ctx Pointer to the method context.
665 @return Pointer to the user login object if found; otherwise NULL.
667 The user login object was returned from the authentication server, and then cached so
668 we don't have to call the authentication server again for the same user.
670 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
671 if( ctx && ctx->session && ctx->session->userData )
672 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
678 @brief Save a copy of an authkey in the userData of the current application session.
679 @param ctx Pointer to the method context.
680 @param authkey The authkey to be saved.
682 If @a authkey is NULL, remove the authkey if one is already cached.
684 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
685 if( ctx && ctx->session && authkey ) {
686 osrfAppSession* session = ctx->session;
687 osrfHash* cache = session->userData;
689 // If the session doesn't already have a hash, create one. Make sure
690 // that the application session frees the hash when it terminates.
692 cache = initSessionCache( ctx );
694 // Save the transaction id in the hash, with the key "xact_id"
695 if( authkey && *authkey )
696 osrfHashSet( cache, strdup( authkey ), "authkey" );
698 osrfHashRemove( cache, "authkey" );
703 @brief Reset the login timeout.
704 @param authkey The authentication key for the current login session.
705 @param now The current time.
706 @return Zero if successful, or 1 if not.
708 Tell the authentication server to reset the timeout so that the login session won't
709 expire for a while longer.
711 We could dispense with the @a now parameter by calling time(). But we just called
712 time() in order to decide whether to reset the timeout, so we might as well reuse
713 the result instead of calling time() again.
715 static int reset_timeout( const char* authkey, time_t now ) {
716 jsonObject* auth_object = jsonNewObject( authkey );
718 // Ask the authentication server to reset the timeout. It returns an event
719 // indicating success or failure.
720 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
721 "open-ils.auth.session.reset_timeout", auth_object );
722 jsonObjectFree( auth_object );
724 if( !result || result->type != JSON_HASH ) {
725 osrfLogError( OSRF_LOG_MARK,
726 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
727 jsonObjectFree( result );
728 return 1; // Not the right sort of object returned
731 const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
732 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
733 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
734 jsonObjectFree( result );
735 return 1; // Return code from method not available
738 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
739 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
741 desc = "(No reason available)"; // failsafe; shouldn't happen
742 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
743 jsonObjectFree( result );
747 // Revise our local proxy for the timeout deadline
748 // by a smallish fraction of the timeout interval
749 const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
751 timeout = "1"; // failsafe; shouldn't happen
752 time_next_reset = now + atoi( timeout ) / 15;
754 jsonObjectFree( result );
755 return 0; // Successfully reset timeout
759 @brief Get the authkey string for the current application session, if any.
760 @param ctx Pointer to the method context.
761 @return Pointer to the cached authkey if found; otherwise NULL.
763 If present, the authkey string was cached from a previous method call.
765 static const char* getAuthkey( osrfMethodContext* ctx ) {
766 if( ctx && ctx->session && ctx->session->userData ) {
767 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
768 // LFW recent changes mean the userData hash gets set up earlier, but
769 // doesn't necessarily have an authkey yet
773 // Possibly reset the authentication timeout to keep the login alive. We do so
774 // no more than once per method call, and not at all if it has been only a short
775 // time since the last reset.
777 // Here we reset explicitly, if at all. We also implicitly reset the timeout
778 // whenever we call the "open-ils.auth.session.retrieve" method.
779 if( timeout_needs_resetting ) {
780 time_t now = time( NULL );
781 if( now >= time_next_reset && reset_timeout( authkey, now ) )
782 authkey = NULL; // timeout has apparently expired already
785 timeout_needs_resetting = 0;
793 @brief Implement the transaction.begin method.
794 @param ctx Pointer to the method context.
795 @return Zero if successful, or -1 upon error.
797 Start a transaction. Save a transaction ID for future reference.
800 - authkey (PCRUD only)
802 Return to client: Transaction ID
804 int beginTransaction( osrfMethodContext* ctx ) {
805 if(osrfMethodVerifyContext( ctx )) {
806 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
810 if( enforce_pcrud ) {
811 timeout_needs_resetting = 1;
812 const jsonObject* user = verifyUserPCRUD( ctx );
817 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
820 int errnum = dbi_conn_error( writehandle, &msg );
821 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
822 modulename, errnum, msg ? msg : "(No description available)" );
823 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
824 "osrfMethodException", ctx->request, "Error starting transaction" );
825 if( !oilsIsDBConnected( writehandle ))
826 osrfAppSessionPanic( ctx->session );
829 dbi_result_free( result );
831 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
832 osrfAppRespondComplete( ctx, ret );
833 jsonObjectFree( ret );
839 @brief Implement the savepoint.set method.
840 @param ctx Pointer to the method context.
841 @return Zero if successful, or -1 if not.
843 Issue a SAVEPOINT to the database server.
846 - authkey (PCRUD only)
849 Return to client: Savepoint name
851 int setSavepoint( osrfMethodContext* ctx ) {
852 if(osrfMethodVerifyContext( ctx )) {
853 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
858 if( enforce_pcrud ) {
860 timeout_needs_resetting = 1;
861 const jsonObject* user = verifyUserPCRUD( ctx );
866 // Verify that a transaction is pending
867 const char* trans_id = getXactId( ctx );
868 if( NULL == trans_id ) {
869 osrfAppSessionStatus(
871 OSRF_STATUS_INTERNALSERVERERROR,
872 "osrfMethodException",
874 "No active transaction -- required for savepoints"
879 // Get the savepoint name from the method params
880 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
882 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
885 int errnum = dbi_conn_error( writehandle, &msg );
888 "%s: Error creating savepoint %s in transaction %s: %d %s",
893 msg ? msg : "(No description available)"
895 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
896 "osrfMethodException", ctx->request, "Error creating savepoint" );
897 if( !oilsIsDBConnected( writehandle ))
898 osrfAppSessionPanic( ctx->session );
901 dbi_result_free( result );
902 jsonObject* ret = jsonNewObject( spName );
903 osrfAppRespondComplete( ctx, ret );
904 jsonObjectFree( ret );
910 @brief Implement the savepoint.release method.
911 @param ctx Pointer to the method context.
912 @return Zero if successful, or -1 if not.
914 Issue a RELEASE SAVEPOINT to the database server.
917 - authkey (PCRUD only)
920 Return to client: Savepoint name
922 int releaseSavepoint( osrfMethodContext* ctx ) {
923 if(osrfMethodVerifyContext( ctx )) {
924 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
929 if( enforce_pcrud ) {
931 timeout_needs_resetting = 1;
932 const jsonObject* user = verifyUserPCRUD( ctx );
937 // Verify that a transaction is pending
938 const char* trans_id = getXactId( ctx );
939 if( NULL == trans_id ) {
940 osrfAppSessionStatus(
942 OSRF_STATUS_INTERNALSERVERERROR,
943 "osrfMethodException",
945 "No active transaction -- required for savepoints"
950 // Get the savepoint name from the method params
951 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
953 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
956 int errnum = dbi_conn_error( writehandle, &msg );
959 "%s: Error releasing savepoint %s in transaction %s: %d %s",
964 msg ? msg : "(No description available)"
966 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
967 "osrfMethodException", ctx->request, "Error releasing savepoint" );
968 if( !oilsIsDBConnected( writehandle ))
969 osrfAppSessionPanic( ctx->session );
972 dbi_result_free( result );
973 jsonObject* ret = jsonNewObject( spName );
974 osrfAppRespondComplete( ctx, ret );
975 jsonObjectFree( ret );
981 @brief Implement the savepoint.rollback method.
982 @param ctx Pointer to the method context.
983 @return Zero if successful, or -1 if not.
985 Issue a ROLLBACK TO SAVEPOINT to the database server.
988 - authkey (PCRUD only)
991 Return to client: Savepoint name
993 int rollbackSavepoint( osrfMethodContext* ctx ) {
994 if(osrfMethodVerifyContext( ctx )) {
995 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1000 if( enforce_pcrud ) {
1002 timeout_needs_resetting = 1;
1003 const jsonObject* user = verifyUserPCRUD( ctx );
1008 // Verify that a transaction is pending
1009 const char* trans_id = getXactId( ctx );
1010 if( NULL == trans_id ) {
1011 osrfAppSessionStatus(
1013 OSRF_STATUS_INTERNALSERVERERROR,
1014 "osrfMethodException",
1016 "No active transaction -- required for savepoints"
1021 // Get the savepoint name from the method params
1022 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1024 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
1027 int errnum = dbi_conn_error( writehandle, &msg );
1030 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
1035 msg ? msg : "(No description available)"
1037 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1038 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1039 if( !oilsIsDBConnected( writehandle ))
1040 osrfAppSessionPanic( ctx->session );
1043 dbi_result_free( result );
1044 jsonObject* ret = jsonNewObject( spName );
1045 osrfAppRespondComplete( ctx, ret );
1046 jsonObjectFree( ret );
1052 @brief Implement the transaction.commit method.
1053 @param ctx Pointer to the method context.
1054 @return Zero if successful, or -1 if not.
1056 Issue a COMMIT to the database server.
1059 - authkey (PCRUD only)
1061 Return to client: Transaction ID.
1063 int commitTransaction( osrfMethodContext* ctx ) {
1064 if(osrfMethodVerifyContext( ctx )) {
1065 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1069 if( enforce_pcrud ) {
1070 timeout_needs_resetting = 1;
1071 const jsonObject* user = verifyUserPCRUD( ctx );
1076 // Verify that a transaction is pending
1077 const char* trans_id = getXactId( ctx );
1078 if( NULL == trans_id ) {
1079 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1080 "osrfMethodException", ctx->request, "No active transaction to commit" );
1084 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1087 int errnum = dbi_conn_error( writehandle, &msg );
1088 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1089 modulename, errnum, msg ? msg : "(No description available)" );
1090 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1091 "osrfMethodException", ctx->request, "Error committing transaction" );
1092 if( !oilsIsDBConnected( writehandle ))
1093 osrfAppSessionPanic( ctx->session );
1096 dbi_result_free( result );
1097 jsonObject* ret = jsonNewObject( trans_id );
1098 osrfAppRespondComplete( ctx, ret );
1099 jsonObjectFree( ret );
1106 @brief Implement the transaction.rollback method.
1107 @param ctx Pointer to the method context.
1108 @return Zero if successful, or -1 if not.
1110 Issue a ROLLBACK to the database server.
1113 - authkey (PCRUD only)
1115 Return to client: Transaction ID
1117 int rollbackTransaction( osrfMethodContext* ctx ) {
1118 if( osrfMethodVerifyContext( ctx )) {
1119 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1123 if( enforce_pcrud ) {
1124 timeout_needs_resetting = 1;
1125 const jsonObject* user = verifyUserPCRUD( ctx );
1130 // Verify that a transaction is pending
1131 const char* trans_id = getXactId( ctx );
1132 if( NULL == trans_id ) {
1133 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1134 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1138 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1141 int errnum = dbi_conn_error( writehandle, &msg );
1142 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1143 modulename, errnum, msg ? msg : "(No description available)" );
1144 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1145 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1146 if( !oilsIsDBConnected( writehandle ))
1147 osrfAppSessionPanic( ctx->session );
1150 dbi_result_free( result );
1151 jsonObject* ret = jsonNewObject( trans_id );
1152 osrfAppRespondComplete( ctx, ret );
1153 jsonObjectFree( ret );
1160 @brief Implement the "search" method.
1161 @param ctx Pointer to the method context.
1162 @return Zero if successful, or -1 if not.
1165 - authkey (PCRUD only)
1166 - WHERE clause, as jsonObject
1167 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1169 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1170 Optionally flesh linked fields.
1172 int doSearch( osrfMethodContext* ctx ) {
1173 if( osrfMethodVerifyContext( ctx )) {
1174 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1179 timeout_needs_resetting = 1;
1181 jsonObject* where_clause;
1182 jsonObject* rest_of_query;
1184 if( enforce_pcrud ) {
1185 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1186 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1188 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1189 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1192 if( !where_clause ) {
1193 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1197 // Get the class metadata
1198 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1199 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1203 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1205 osrfAppRespondComplete( ctx, NULL );
1209 // doFieldmapperSearch() now takes care of our responding for us
1210 // // Return each row to the client
1211 // jsonObject* cur = 0;
1212 // unsigned long res_idx = 0;
1214 // while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1215 // // We used to discard based on perms here, but now that's
1216 // // inside doFieldmapperSearch()
1217 // osrfAppRespond( ctx, cur );
1220 jsonObjectFree( obj );
1222 osrfAppRespondComplete( ctx, NULL );
1227 @brief Implement the "id_list" method.
1228 @param ctx Pointer to the method context.
1229 @param err Pointer through which to return an error code.
1230 @return Zero if successful, or -1 if not.
1233 - authkey (PCRUD only)
1234 - WHERE clause, as jsonObject
1235 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1237 Return to client: The primary key values for all rows of the relevant class that
1238 satisfy a specified WHERE clause.
1240 This method relies on the assumption that every class has a primary key consisting of
1243 int doIdList( osrfMethodContext* ctx ) {
1244 if( osrfMethodVerifyContext( ctx )) {
1245 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1250 timeout_needs_resetting = 1;
1252 jsonObject* where_clause;
1253 jsonObject* rest_of_query;
1255 // We use the where clause without change. But we need to massage the rest of the
1256 // query, so we work with a copy of it instead of modifying the original.
1258 if( enforce_pcrud ) {
1259 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1260 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1262 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1263 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1266 if( !where_clause ) {
1267 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1271 // Eliminate certain SQL clauses, if present.
1272 if( rest_of_query ) {
1273 jsonObjectRemoveKey( rest_of_query, "select" );
1274 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1275 jsonObjectRemoveKey( rest_of_query, "flesh" );
1276 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1278 rest_of_query = jsonNewObjectType( JSON_HASH );
1281 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1283 // Get the class metadata
1284 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1285 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1287 // Build a SELECT list containing just the primary key,
1288 // i.e. like { "classname":["keyname"] }
1289 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1291 // Load array with name of primary key
1292 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1293 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1294 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1296 jsonObjectSetKey( rest_of_query, "select", select_clause );
1301 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1303 jsonObjectFree( rest_of_query );
1305 osrfAppRespondComplete( ctx, NULL );
1309 // Return each primary key value to the client
1311 unsigned long res_idx = 0;
1312 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1313 // We used to discard based on perms here, but now that's
1314 // inside doFieldmapperSearch()
1315 osrfAppRespond( ctx,
1316 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1319 jsonObjectFree( obj );
1320 osrfAppRespondComplete( ctx, NULL );
1325 @brief Verify that we have a valid class reference.
1326 @param ctx Pointer to the method context.
1327 @param param Pointer to the method parameters.
1328 @return 1 if the class reference is valid, or zero if it isn't.
1330 The class of the method params must match the class to which the method id devoted.
1331 For PCRUD there are additional restrictions.
1333 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1335 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1336 osrfHash* class = osrfHashGet( method_meta, "class" );
1338 // Compare the method's class to the parameters' class
1339 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1341 // Oops -- they don't match. Complain.
1342 growing_buffer* msg = buffer_init( 128 );
1345 "%s: %s method for type %s was passed a %s",
1347 osrfHashGet( method_meta, "methodtype" ),
1348 osrfHashGet( class, "classname" ),
1349 param->classname ? param->classname : "(null)"
1352 char* m = buffer_release( msg );
1353 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1361 return verifyObjectPCRUD( ctx, class, param, 1 );
1367 @brief (PCRUD only) Verify that the user is properly logged in.
1368 @param ctx Pointer to the method context.
1369 @return If the user is logged in, a pointer to the user object from the authentication
1370 server; otherwise NULL.
1372 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1374 // Get the authkey (the first method parameter)
1375 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1377 // See if we have the same authkey, and a user object,
1378 // locally cached from a previous call
1379 const char* cached_authkey = getAuthkey( ctx );
1380 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1381 const jsonObject* cached_user = getUserLogin( ctx );
1386 // We have no matching authentication data in the cache. Authenticate from scratch.
1387 jsonObject* auth_object = jsonNewObject( auth );
1389 // Fetch the user object from the authentication server
1390 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1392 jsonObjectFree( auth_object );
1394 if( !user->classname || strcmp(user->classname, "au" )) {
1396 growing_buffer* msg = buffer_init( 128 );
1399 "%s: permacrud received a bad auth token: %s",
1404 char* m = buffer_release( msg );
1405 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1409 jsonObjectFree( user );
1413 setUserLogin( ctx, user );
1414 setAuthkey( ctx, auth );
1416 // Allow ourselves up to a second before we have to reset the login timeout.
1417 // It would be nice to use some fraction of the timeout interval enforced by the
1418 // authentication server, but that value is not readily available at this point.
1419 // Instead, we use a conservative default interval.
1420 time_next_reset = time( NULL ) + 1;
1426 @brief For PCRUD: Determine whether the current user may access the current row.
1427 @param ctx Pointer to the method context.
1428 @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1429 @param obj Pointer to the row being potentially accessed.
1430 @return 1 if access is permitted, or 0 if it isn't.
1432 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1434 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1436 dbhandle = writehandle;
1438 // Figure out what class and method are involved
1439 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1440 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1443 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1444 if (rs_size_from_hash) {
1445 rs_size = *rs_size_from_hash;
1446 osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1450 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1451 // contexts we will do another lookup of the current row, even if we already have a
1452 // previously fetched row image, because the row image in hand may not include the
1453 // foreign key(s) that we need.
1455 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1456 // but they aren't implemented yet.
1459 if( *method_type == 's' || *method_type == 'i' ) {
1460 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1462 } else if( *method_type == 'u' || *method_type == 'd' ) {
1463 fetch = 1; // MUST go to the db for the object for update and delete
1466 // Get the appropriate permacrud entry from the IDL, depending on method type
1467 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1469 // No permacrud for this method type on this class
1471 growing_buffer* msg = buffer_init( 128 );
1474 "%s: %s on class %s has no permacrud IDL entry",
1476 osrfHashGet( method_metadata, "methodtype" ),
1477 osrfHashGet( class, "classname" )
1480 char* m = buffer_release( msg );
1481 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1482 "osrfMethodException", ctx->request, m );
1489 // Get the user id, and make sure the user is logged in
1490 const jsonObject* user = verifyUserPCRUD( ctx );
1492 return 0; // Not logged in? No access.
1494 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1496 // Get a list of permissions from the permacrud entry.
1497 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1498 if( permission->size == 0 ) {
1501 "No permissions required for this action (class %s), passing through",
1502 osrfHashGet(class, "classname")
1507 // Build a list of org units that own the row. This is fairly convoluted because there
1508 // are several different ways that an org unit may own the row, as defined by the
1511 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1512 // identifying an owning org_unit..
1513 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1515 // Foreign context adds a layer of indirection. The row points to some other row that
1516 // an org unit may own. The "jump" attribute, if present, adds another layer of
1518 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1520 // The following string array stores the list of org units. (We don't have a thingie
1521 // for storing lists of integers, so we fake it with a list of strings.)
1522 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1525 const char* pkey_value = NULL;
1526 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1527 // If the global_required attribute is present and true, then the only owning
1528 // org unit is the root org unit, i.e. the one with no parent.
1529 osrfLogDebug( OSRF_LOG_MARK,
1530 "global-level permissions required, fetching top of the org tree" );
1532 // no need to check perms for org tree root retrieval
1533 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1534 // check for perm at top of org tree
1535 const char* org_tree_root_id = org_tree_root( ctx );
1536 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1538 if( org_tree_root_id ) {
1539 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1540 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1542 osrfStringArrayFree( context_org_array );
1547 // If the global_required attribute is absent or false, then we look for
1548 // local and/or foreign context. In order to find the relevant foreign
1549 // keys, we must either read the relevant row from the database, or look at
1550 // the image of the row that we already have in memory.
1552 // Even if we have an image of the row in memory, that image may not include the
1553 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1554 // of the row to make sure that we have what we need.
1556 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1557 "fetching context org ids" );
1558 const char* pkey = osrfHashGet( class, "primarykey" );
1559 jsonObject *param = NULL;
1562 // There is no primary key, so we can't do a fresh lookup. Use the row
1563 // image that we already have. If it doesn't have everything we need, too bad.
1565 param = jsonObjectClone( obj );
1566 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1567 } else if( obj->classname ) {
1568 pkey_value = oilsFMGetStringConst( obj, pkey );
1570 param = jsonObjectClone( obj );
1571 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1574 pkey_value = jsonObjectGetString( obj );
1576 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1577 "of %s and retrieving from the database", pkey_value );
1581 // Fetch the row so that we can look at the foreign key(s)
1582 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1583 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1584 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1585 jsonObjectFree( _tmp_params );
1586 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1588 param = jsonObjectExtractIndex( _list, 0 );
1589 jsonObjectFree( _list );
1593 // The row doesn't exist. Complain, and deny access.
1594 osrfLogDebug( OSRF_LOG_MARK,
1595 "Object not found in the database with primary key %s of %s",
1598 growing_buffer* msg = buffer_init( 128 );
1601 "%s: no object found with primary key %s of %s",
1607 char* m = buffer_release( msg );
1608 osrfAppSessionStatus(
1610 OSRF_STATUS_INTERNALSERVERERROR,
1611 "osrfMethodException",
1620 if( local_context && local_context->size > 0 ) {
1621 // The IDL provides a list of column names for the foreign keys denoting
1622 // local context, i.e. columns identifying owing org units directly. Look up
1623 // the value of each one, and if it isn't null, add it to the list of org units.
1624 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1625 local_context->size );
1627 const char* lcontext = NULL;
1628 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1629 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1630 if( fkey_value ) { // if not null
1631 osrfStringArrayAdd( context_org_array, fkey_value );
1634 "adding class-local field %s (value: %s) to the context org list",
1636 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1642 if( foreign_context ) {
1643 unsigned long class_count = osrfHashGetCount( foreign_context );
1644 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1646 if( class_count > 0 ) {
1648 // The IDL provides a list of foreign key columns pointing to rows that
1649 // an org unit may own. Follow each link, identify the owning org unit,
1650 // and add it to the list.
1651 osrfHash* fcontext = NULL;
1652 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1653 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1654 // For each class to which a foreign key points:
1655 const char* class_name = osrfHashIteratorKey( class_itr );
1656 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1660 "%d foreign context fields(s) specified for class %s",
1661 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1665 // Get the name of the key field in the foreign table
1666 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1668 // Get the value of the foreign key pointing to the foreign table
1669 char* foreign_pkey_value =
1670 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1671 if( !foreign_pkey_value )
1672 continue; // Foreign key value is null; skip it
1674 // Look up the row to which the foreign key points
1675 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1677 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1678 jsonObject* _list = doFieldmapperSearch(
1679 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1680 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1682 jsonObject* _fparam = NULL;
1683 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1684 _fparam = jsonObjectExtractIndex( _list, 0 );
1686 jsonObjectFree( _tmp_params );
1687 jsonObjectFree( _list );
1689 // At this point _fparam either points to the row identified by the
1690 // foreign key, or it's NULL (no such row found).
1692 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1694 const char* bad_class = NULL; // For noting failed lookups
1696 bad_class = class_name; // Referenced row not found
1697 else if( jump_list ) {
1698 // Follow a chain of rows, linked by foreign keys, to find an owner
1699 const char* flink = NULL;
1701 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1702 // For each entry in the jump list. Each entry (i.e. flink) is
1703 // the name of a foreign key column in the current row.
1705 // From the IDL, get the linkage information for the next jump
1706 osrfHash* foreign_link_hash =
1707 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1709 // Get the class metadata for the class
1710 // to which the foreign key points
1711 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1712 osrfHashGet( foreign_link_hash, "class" ));
1714 // Get the name of the referenced key of that class
1715 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1717 // Get the value of the foreign key pointing to that class
1718 free( foreign_pkey_value );
1719 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1720 if( !foreign_pkey_value )
1721 break; // Foreign key is null; quit looking
1723 // Build a WHERE clause for the lookup
1724 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1727 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1728 _tmp_params, NULL, &err );
1730 // Get the resulting row
1731 jsonObjectFree( _fparam );
1732 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1733 _fparam = jsonObjectExtractIndex( _list, 0 );
1735 // Referenced row not found
1737 bad_class = osrfHashGet( foreign_link_hash, "class" );
1740 jsonObjectFree( _tmp_params );
1741 jsonObjectFree( _list );
1747 // We had a foreign key pointing to such-and-such a row, but then
1748 // we couldn't fetch that row. The data in the database are in an
1749 // inconsistent state; the database itself may even be corrupted.
1750 growing_buffer* msg = buffer_init( 128 );
1753 "%s: no object of class %s found with primary key %s of %s",
1757 foreign_pkey_value ? foreign_pkey_value : "(null)"
1760 char* m = buffer_release( msg );
1761 osrfAppSessionStatus(
1763 OSRF_STATUS_INTERNALSERVERERROR,
1764 "osrfMethodException",
1770 osrfHashIteratorFree( class_itr );
1771 free( foreign_pkey_value );
1772 jsonObjectFree( param );
1777 free( foreign_pkey_value );
1780 // Examine each context column of the foreign row,
1781 // and add its value to the list of org units.
1783 const char* foreign_field = NULL;
1784 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1785 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1786 osrfStringArrayAdd( context_org_array,
1787 oilsFMGetStringConst( _fparam, foreign_field ));
1788 osrfLogDebug( OSRF_LOG_MARK,
1789 "adding foreign class %s field %s (value: %s) "
1790 "to the context org list",
1793 osrfStringArrayGetString(
1794 context_org_array, context_org_array->size - 1 )
1798 jsonObjectFree( _fparam );
1802 osrfHashIteratorFree( class_itr );
1806 jsonObjectFree( param );
1809 const char* context_org = NULL;
1810 const char* perm = NULL;
1813 // For every combination of permission and context org unit: call a stored procedure
1814 // to determine if the user has this permission in the context of this org unit.
1815 // If the answer is yes at any point, then we're done, and the user has permission.
1816 // In other words permissions are additive.
1818 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1821 osrfStringArray* pcache = NULL;
1822 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
1823 pcache = getPermLocationCache(ctx, perm);
1826 pcache = osrfNewStringArray(0);
1828 result = dbi_conn_queryf(
1830 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
1838 "Received a result for permission [%s] for user %d",
1843 if( dbi_result_first_row( result )) {
1845 jsonObject* return_val = oilsMakeJSONFromResult( result );
1846 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
1847 jsonObjectFree( return_val );
1848 } while( dbi_result_next_row( result ));
1850 setPermLocationCache(ctx, perm, pcache);
1853 dbi_result_free( result );
1859 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1861 if (rs_size > perm_at_threshold) {
1862 if (osrfStringArrayContains( pcache, context_org )) {
1871 "Checking object permission [%s] for user %d "
1872 "on object %s (class %s) at org %d",
1876 osrfHashGet( class, "classname" ),
1880 result = dbi_conn_queryf(
1882 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1885 osrfHashGet( class, "classname" ),
1893 "Received a result for object permission [%s] "
1894 "for user %d on object %s (class %s) at org %d",
1898 osrfHashGet( class, "classname" ),
1902 if( dbi_result_first_row( result )) {
1903 jsonObject* return_val = oilsMakeJSONFromResult( result );
1904 const char* has_perm = jsonObjectGetString(
1905 jsonObjectGetKeyConst( return_val, "has_perm" ));
1909 "Status of object permission [%s] for user %d "
1910 "on object %s (class %s) at org %d is %s",
1914 osrfHashGet(class, "classname"),
1919 if( *has_perm == 't' )
1921 jsonObjectFree( return_val );
1924 dbi_result_free( result );
1929 int errnum = dbi_conn_error( writehandle, &msg );
1930 osrfLogWarning( OSRF_LOG_MARK,
1931 "Unable to call check object permissions: %d, %s",
1932 errnum, msg ? msg : "(No description available)" );
1933 if( !oilsIsDBConnected( writehandle ))
1934 osrfAppSessionPanic( ctx->session );
1938 if (rs_size > perm_at_threshold) break;
1940 osrfLogDebug( OSRF_LOG_MARK,
1941 "Checking non-object permission [%s] for user %d at org %d",
1942 perm, userid, atoi(context_org) );
1943 result = dbi_conn_queryf(
1945 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1952 osrfLogDebug( OSRF_LOG_MARK,
1953 "Received a result for permission [%s] for user %d at org %d",
1954 perm, userid, atoi( context_org ));
1955 if( dbi_result_first_row( result )) {
1956 jsonObject* return_val = oilsMakeJSONFromResult( result );
1957 const char* has_perm = jsonObjectGetString(
1958 jsonObjectGetKeyConst( return_val, "has_perm" ));
1959 osrfLogDebug( OSRF_LOG_MARK,
1960 "Status of permission [%s] for user %d at org %d is [%s]",
1961 perm, userid, atoi( context_org ), has_perm );
1962 if( *has_perm == 't' )
1964 jsonObjectFree( return_val );
1967 dbi_result_free( result );
1972 int errnum = dbi_conn_error( writehandle, &msg );
1973 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
1974 errnum, msg ? msg : "(No description available)" );
1975 if( !oilsIsDBConnected( writehandle ))
1976 osrfAppSessionPanic( ctx->session );
1985 osrfStringArrayFree( context_org_array );
1991 @brief Look up the root of the org_unit tree.
1992 @param ctx Pointer to the method context.
1993 @return The id of the root org unit, as a character string.
1995 Query actor.org_unit where parent_ou is null, and return the id as a string.
1997 This function assumes that there is only one root org unit, i.e. that we
1998 have a single tree, not a forest.
2000 The calling code is responsible for freeing the returned string.
2002 static const char* org_tree_root( osrfMethodContext* ctx ) {
2004 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
2005 static time_t last_lookup_time = 0;
2006 time_t current_time = time( NULL );
2008 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2009 // We successfully looked this up less than an hour ago.
2010 // It's not likely to have changed since then.
2011 return strdup( cached_root_id );
2013 last_lookup_time = current_time;
2016 jsonObject* where_clause = single_hash( "parent_ou", NULL );
2017 jsonObject* result = doFieldmapperSearch(
2018 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2019 jsonObjectFree( where_clause );
2021 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2024 jsonObjectFree( result );
2026 growing_buffer* msg = buffer_init( 128 );
2027 OSRF_BUFFER_ADD( msg, modulename );
2028 OSRF_BUFFER_ADD( msg,
2029 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2031 char* m = buffer_release( msg );
2032 osrfAppSessionStatus( ctx->session,
2033 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2036 cached_root_id[ 0 ] = '\0';
2040 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2041 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2043 strcpy( cached_root_id, root_org_unit_id );
2044 jsonObjectFree( result );
2045 return cached_root_id;
2049 @brief Create a JSON_HASH with a single key/value pair.
2050 @param key The key of the key/value pair.
2051 @param value the value of the key/value pair.
2052 @return Pointer to a newly created jsonObject of type JSON_HASH.
2054 The value of the key/value is either a string or (if @a value is NULL) a null.
2056 static jsonObject* single_hash( const char* key, const char* value ) {
2058 if( ! key ) key = "";
2060 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2061 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2066 int doCreate( osrfMethodContext* ctx ) {
2067 if(osrfMethodVerifyContext( ctx )) {
2068 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2073 timeout_needs_resetting = 1;
2075 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2076 jsonObject* target = NULL;
2077 jsonObject* options = NULL;
2079 if( enforce_pcrud ) {
2080 target = jsonObjectGetIndex( ctx->params, 1 );
2081 options = jsonObjectGetIndex( ctx->params, 2 );
2083 target = jsonObjectGetIndex( ctx->params, 0 );
2084 options = jsonObjectGetIndex( ctx->params, 1 );
2087 if( !verifyObjectClass( ctx, target )) {
2088 osrfAppRespondComplete( ctx, NULL );
2092 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2094 const char* trans_id = getXactId( ctx );
2096 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2098 osrfAppSessionStatus(
2100 OSRF_STATUS_BADREQUEST,
2101 "osrfMethodException",
2103 "No active transaction -- required for CREATE"
2105 osrfAppRespondComplete( ctx, NULL );
2109 // The following test is harmless but redundant. If a class is
2110 // readonly, we don't register a create method for it.
2111 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2112 osrfAppSessionStatus(
2114 OSRF_STATUS_BADREQUEST,
2115 "osrfMethodException",
2117 "Cannot INSERT readonly class"
2119 osrfAppRespondComplete( ctx, NULL );
2123 // Set the last_xact_id
2124 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2126 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2127 trans_id, target->classname, index);
2128 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2131 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2133 dbhandle = writehandle;
2135 osrfHash* fields = osrfHashGet( meta, "fields" );
2136 char* pkey = osrfHashGet( meta, "primarykey" );
2137 char* seq = osrfHashGet( meta, "sequence" );
2139 growing_buffer* table_buf = buffer_init( 128 );
2140 growing_buffer* col_buf = buffer_init( 128 );
2141 growing_buffer* val_buf = buffer_init( 128 );
2143 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2144 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2145 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2146 buffer_add( val_buf,"VALUES (" );
2150 osrfHash* field = NULL;
2151 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2152 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2154 const char* field_name = osrfHashIteratorKey( field_itr );
2156 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2159 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2162 if( field_object && field_object->classname ) {
2163 value = oilsFMGetString(
2165 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2167 } else if( field_object && JSON_BOOL == field_object->type ) {
2168 if( jsonBoolIsTrue( field_object ) )
2169 value = strdup( "t" );
2171 value = strdup( "f" );
2173 value = jsonObjectToSimpleString( field_object );
2179 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2180 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2183 buffer_add( col_buf, field_name );
2185 if( !field_object || field_object->type == JSON_NULL ) {
2186 buffer_add( val_buf, "DEFAULT" );
2188 } else if( !strcmp( get_primitive( field ), "number" )) {
2189 const char* numtype = get_datatype( field );
2190 if( !strcmp( numtype, "INT8" )) {
2191 buffer_fadd( val_buf, "%lld", atoll( value ));
2193 } else if( !strcmp( numtype, "INT" )) {
2194 buffer_fadd( val_buf, "%d", atoi( value ));
2196 } else if( !strcmp( numtype, "NUMERIC" )) {
2197 buffer_fadd( val_buf, "%f", atof( value ));
2200 if( dbi_conn_quote_string( writehandle, &value )) {
2201 OSRF_BUFFER_ADD( val_buf, value );
2204 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2205 osrfAppSessionStatus(
2207 OSRF_STATUS_INTERNALSERVERERROR,
2208 "osrfMethodException",
2210 "Error quoting string -- please see the error log for more details"
2213 buffer_free( table_buf );
2214 buffer_free( col_buf );
2215 buffer_free( val_buf );
2216 osrfAppRespondComplete( ctx, NULL );
2224 osrfHashIteratorFree( field_itr );
2226 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2227 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2229 char* table_str = buffer_release( table_buf );
2230 char* col_str = buffer_release( col_buf );
2231 char* val_str = buffer_release( val_buf );
2232 growing_buffer* sql = buffer_init( 128 );
2233 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2238 char* query = buffer_release( sql );
2240 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2242 jsonObject* obj = NULL;
2245 dbi_result result = dbi_conn_query( writehandle, query );
2247 obj = jsonNewObject( NULL );
2249 int errnum = dbi_conn_error( writehandle, &msg );
2252 "%s ERROR inserting %s object using query [%s]: %d %s",
2254 osrfHashGet(meta, "fieldmapper"),
2257 msg ? msg : "(No description available)"
2259 osrfAppSessionStatus(
2261 OSRF_STATUS_INTERNALSERVERERROR,
2262 "osrfMethodException",
2264 "INSERT error -- please see the error log for more details"
2266 if( !oilsIsDBConnected( writehandle ))
2267 osrfAppSessionPanic( ctx->session );
2270 dbi_result_free( result );
2272 char* id = oilsFMGetString( target, pkey );
2274 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2275 growing_buffer* _id = buffer_init( 10 );
2276 buffer_fadd( _id, "%lld", new_id );
2277 id = buffer_release( _id );
2280 // Find quietness specification, if present
2281 const char* quiet_str = NULL;
2283 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2285 quiet_str = jsonObjectGetString( quiet_obj );
2288 if( str_is_true( quiet_str )) { // if quietness is specified
2289 obj = jsonNewObject( id );
2293 // Fetch the row that we just inserted, so that we can return it to the client
2294 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2295 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2298 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2302 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2304 jsonObjectFree( list );
2305 jsonObjectFree( where_clause );
2312 osrfAppRespondComplete( ctx, obj );
2313 jsonObjectFree( obj );
2318 @brief Implement the retrieve method.
2319 @param ctx Pointer to the method context.
2320 @param err Pointer through which to return an error code.
2321 @return If successful, a pointer to the result to be returned to the client;
2324 From the method's class, fetch a row with a specified value in the primary key. This
2325 method relies on the database design convention that a primary key consists of a single
2329 - authkey (PCRUD only)
2330 - value of the primary key for the desired row, for building the WHERE clause
2331 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2333 Return to client: One row from the query.
2335 int doRetrieve( osrfMethodContext* ctx ) {
2336 if(osrfMethodVerifyContext( ctx )) {
2337 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2342 timeout_needs_resetting = 1;
2347 if( enforce_pcrud ) {
2352 // Get the class metadata
2353 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2355 // Get the value of the primary key, from a method parameter
2356 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2360 "%s retrieving %s object with primary key value of %s",
2362 osrfHashGet( class_def, "fieldmapper" ),
2363 jsonObjectGetString( id_obj )
2366 // Build a WHERE clause based on the key value
2367 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2370 osrfHashGet( class_def, "primarykey" ), // name of key column
2371 jsonObjectClone( id_obj ) // value of key column
2374 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2378 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2380 jsonObjectFree( where_clause );
2382 osrfAppRespondComplete( ctx, NULL );
2386 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2387 jsonObjectFree( list );
2389 if( enforce_pcrud ) {
2390 // no result, skip this entirely
2391 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2392 jsonObjectFree( obj );
2394 growing_buffer* msg = buffer_init( 128 );
2395 OSRF_BUFFER_ADD( msg, modulename );
2396 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2398 char* m = buffer_release( msg );
2399 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2403 osrfAppRespondComplete( ctx, NULL );
2408 // doFieldmapperSearch() now does the responding for us
2409 //osrfAppRespondComplete( ctx, obj );
2410 osrfAppRespondComplete( ctx, NULL );
2412 jsonObjectFree( obj );
2417 @brief Translate a numeric value to a string representation for the database.
2418 @param field Pointer to the IDL field definition.
2419 @param value Pointer to a jsonObject holding the value of a field.
2420 @return Pointer to a newly allocated string.
2422 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2423 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2424 or (what is worse) valid SQL that is wrong.
2426 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2428 The calling code is responsible for freeing the resulting string by calling free().
2430 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2431 growing_buffer* val_buf = buffer_init( 32 );
2432 const char* numtype = get_datatype( field );
2434 // For historical reasons the following contains cruft that could be cleaned up.
2435 if( !strncmp( numtype, "INT", 3 ) ) {
2436 if( value->type == JSON_NUMBER )
2437 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2438 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2440 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2443 } else if( !strcmp( numtype, "NUMERIC" )) {
2444 if( value->type == JSON_NUMBER )
2445 buffer_fadd( val_buf, jsonObjectGetString( value ));
2447 buffer_fadd( val_buf, jsonObjectGetString( value ));
2451 // Presumably this was really intended to be a string, so quote it
2452 char* str = jsonObjectToSimpleString( value );
2453 if( dbi_conn_quote_string( dbhandle, &str )) {
2454 OSRF_BUFFER_ADD( val_buf, str );
2457 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2459 buffer_free( val_buf );
2464 return buffer_release( val_buf );
2467 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2468 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2469 growing_buffer* sql_buf = buffer_init( 32 );
2475 osrfHashGet( field, "name" )
2479 buffer_add( sql_buf, "IN (" );
2480 } else if( !strcasecmp( op,"not in" )) {
2481 buffer_add( sql_buf, "NOT IN (" );
2483 buffer_add( sql_buf, "IN (" );
2486 if( node->type == JSON_HASH ) {
2487 // subquery predicate
2488 char* subpred = buildQuery( ctx, node, SUBSELECT );
2490 buffer_free( sql_buf );
2494 buffer_add( sql_buf, subpred );
2497 } else if( node->type == JSON_ARRAY ) {
2498 // literal value list
2499 int in_item_index = 0;
2500 int in_item_first = 1;
2501 const jsonObject* in_item;
2502 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2507 buffer_add( sql_buf, ", " );
2510 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2511 osrfLogError( OSRF_LOG_MARK,
2512 "%s: Expected string or number within IN list; found %s",
2513 modulename, json_type( in_item->type ) );
2514 buffer_free( sql_buf );
2518 // Append the literal value -- quoted if not a number
2519 if( JSON_NUMBER == in_item->type ) {
2520 char* val = jsonNumberToDBString( field, in_item );
2521 OSRF_BUFFER_ADD( sql_buf, val );
2524 } else if( !strcmp( get_primitive( field ), "number" )) {
2525 char* val = jsonNumberToDBString( field, in_item );
2526 OSRF_BUFFER_ADD( sql_buf, val );
2530 char* key_string = jsonObjectToSimpleString( in_item );
2531 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2532 OSRF_BUFFER_ADD( sql_buf, key_string );
2535 osrfLogError( OSRF_LOG_MARK,
2536 "%s: Error quoting key string [%s]", modulename, key_string );
2538 buffer_free( sql_buf );
2544 if( in_item_first ) {
2545 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2546 buffer_free( sql_buf );
2550 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2551 modulename, json_type( node->type ));
2552 buffer_free( sql_buf );
2556 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2558 return buffer_release( sql_buf );
2561 // Receive a JSON_ARRAY representing a function call. The first
2562 // entry in the array is the function name. The rest are parameters.
2563 static char* searchValueTransform( const jsonObject* array ) {
2565 if( array->size < 1 ) {
2566 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2570 // Get the function name
2571 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2572 if( func_item->type != JSON_STRING ) {
2573 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2574 modulename, json_type( func_item->type ));
2578 growing_buffer* sql_buf = buffer_init( 32 );
2580 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2581 OSRF_BUFFER_ADD( sql_buf, "( " );
2583 // Get the parameters
2584 int func_item_index = 1; // We already grabbed the zeroth entry
2585 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2587 // Add a separator comma, if we need one
2588 if( func_item_index > 2 )
2589 buffer_add( sql_buf, ", " );
2591 // Add the current parameter
2592 if( func_item->type == JSON_NULL ) {
2593 buffer_add( sql_buf, "NULL" );
2595 if( func_item->type == JSON_BOOL ) {
2596 if( jsonBoolIsTrue(func_item) ) {
2597 buffer_add( sql_buf, "TRUE" );
2599 buffer_add( sql_buf, "FALSE" );
2602 char* val = jsonObjectToSimpleString( func_item );
2603 if( dbi_conn_quote_string( dbhandle, &val )) {
2604 OSRF_BUFFER_ADD( sql_buf, val );
2607 osrfLogError( OSRF_LOG_MARK,
2608 "%s: Error quoting key string [%s]", modulename, val );
2609 buffer_free( sql_buf );
2617 buffer_add( sql_buf, " )" );
2619 return buffer_release( sql_buf );
2622 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2623 const jsonObject* node, const char* op ) {
2625 if( ! is_good_operator( op ) ) {
2626 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2630 char* val = searchValueTransform( node );
2634 growing_buffer* sql_buf = buffer_init( 32 );
2639 osrfHashGet( field, "name" ),
2646 return buffer_release( sql_buf );
2649 // class_alias is a class name or other table alias
2650 // field is a field definition as stored in the IDL
2651 // node comes from the method parameter, and may represent an entry in the SELECT list
2652 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2653 const jsonObject* node ) {
2654 growing_buffer* sql_buf = buffer_init( 32 );
2656 const char* field_transform = jsonObjectGetString(
2657 jsonObjectGetKeyConst( node, "transform" ) );
2658 const char* transform_subcolumn = jsonObjectGetString(
2659 jsonObjectGetKeyConst( node, "result_field" ) );
2661 if( transform_subcolumn ) {
2662 if( ! is_identifier( transform_subcolumn ) ) {
2663 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2664 modulename, transform_subcolumn );
2665 buffer_free( sql_buf );
2668 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2671 if( field_transform ) {
2673 if( ! is_identifier( field_transform ) ) {
2674 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2675 modulename, field_transform );
2676 buffer_free( sql_buf );
2680 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2681 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2682 field_transform, class_alias, osrfHashGet( field, "name" ));
2684 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2685 field_transform, class_alias, osrfHashGet( field, "name" ));
2688 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2691 if( array->type != JSON_ARRAY ) {
2692 osrfLogError( OSRF_LOG_MARK,
2693 "%s: Expected JSON_ARRAY for function params; found %s",
2694 modulename, json_type( array->type ) );
2695 buffer_free( sql_buf );
2698 int func_item_index = 0;
2699 jsonObject* func_item;
2700 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2702 char* val = jsonObjectToSimpleString( func_item );
2705 buffer_add( sql_buf, ",NULL" );
2706 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2707 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2708 OSRF_BUFFER_ADD( sql_buf, val );
2710 osrfLogError( OSRF_LOG_MARK,
2711 "%s: Error quoting key string [%s]", modulename, val );
2713 buffer_free( sql_buf );
2720 buffer_add( sql_buf, " )" );
2723 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2726 if( transform_subcolumn )
2727 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2729 return buffer_release( sql_buf );
2732 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2733 const jsonObject* node, const char* op ) {
2735 if( ! is_good_operator( op ) ) {
2736 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2740 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2741 if( ! field_transform )
2744 int extra_parens = 0; // boolean
2746 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2748 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2750 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2752 free( field_transform );
2756 } else if( value_obj->type == JSON_ARRAY ) {
2757 value = searchValueTransform( value_obj );
2759 osrfLogError( OSRF_LOG_MARK,
2760 "%s: Error building value transform for field transform", modulename );
2761 free( field_transform );
2764 } else if( value_obj->type == JSON_HASH ) {
2765 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2767 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2769 free( field_transform );
2773 } else if( value_obj->type == JSON_NUMBER ) {
2774 value = jsonNumberToDBString( field, value_obj );
2775 } else if( value_obj->type == JSON_NULL ) {
2776 osrfLogError( OSRF_LOG_MARK,
2777 "%s: Error building predicate for field transform: null value", modulename );
2778 free( field_transform );
2780 } else if( value_obj->type == JSON_BOOL ) {
2781 osrfLogError( OSRF_LOG_MARK,
2782 "%s: Error building predicate for field transform: boolean value", modulename );
2783 free( field_transform );
2786 if( !strcmp( get_primitive( field ), "number") ) {
2787 value = jsonNumberToDBString( field, value_obj );
2789 value = jsonObjectToSimpleString( value_obj );
2790 if( !dbi_conn_quote_string( dbhandle, &value )) {
2791 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2792 modulename, value );
2794 free( field_transform );
2800 const char* left_parens = "";
2801 const char* right_parens = "";
2803 if( extra_parens ) {
2808 const char* right_percent = "";
2809 const char* real_op = op;
2811 if( !strcasecmp( op, "startwith") ) {
2813 right_percent = "|| '%'";
2816 growing_buffer* sql_buf = buffer_init( 32 );
2820 "%s%s %s %s %s%s %s%s",
2832 free( field_transform );
2834 return buffer_release( sql_buf );
2837 static char* searchSimplePredicate( const char* op, const char* class_alias,
2838 osrfHash* field, const jsonObject* node ) {
2840 if( ! is_good_operator( op ) ) {
2841 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2847 // Get the value to which we are comparing the specified column
2848 if( node->type != JSON_NULL ) {
2849 if( node->type == JSON_NUMBER ) {
2850 val = jsonNumberToDBString( field, node );
2851 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2852 val = jsonNumberToDBString( field, node );
2854 val = jsonObjectToSimpleString( node );
2859 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2860 // Value is not numeric; enclose it in quotes
2861 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2862 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2869 // Compare to a null value
2870 val = strdup( "NULL" );
2871 if( strcmp( op, "=" ))
2877 growing_buffer* sql_buf = buffer_init( 32 );
2878 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2879 char* pred = buffer_release( sql_buf );
2886 static char* searchBETWEENPredicate( const char* class_alias,
2887 osrfHash* field, const jsonObject* node ) {
2889 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2890 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2892 if( NULL == y_node ) {
2893 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2896 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2897 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2904 if( !strcmp( get_primitive( field ), "number") ) {
2905 x_string = jsonNumberToDBString( field, x_node );
2906 y_string = jsonNumberToDBString( field, y_node );
2909 x_string = jsonObjectToSimpleString( x_node );
2910 y_string = jsonObjectToSimpleString( y_node );
2911 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2912 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2913 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2914 modulename, x_string, y_string );
2921 growing_buffer* sql_buf = buffer_init( 32 );
2922 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2923 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2927 return buffer_release( sql_buf );
2930 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2931 jsonObject* node, osrfMethodContext* ctx ) {
2934 if( node->type == JSON_ARRAY ) { // equality IN search
2935 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2936 } else if( node->type == JSON_HASH ) { // other search
2937 jsonIterator* pred_itr = jsonNewIterator( node );
2938 if( !jsonIteratorHasNext( pred_itr ) ) {
2939 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2940 modulename, osrfHashGet(field, "name" ));
2942 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2944 // Verify that there are no additional predicates
2945 if( jsonIteratorHasNext( pred_itr ) ) {
2946 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2947 modulename, osrfHashGet(field, "name" ));
2948 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2949 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2950 else if( !(strcasecmp( pred_itr->key,"in" ))
2951 || !(strcasecmp( pred_itr->key,"not in" )) )
2952 pred = searchINPredicate(
2953 class_info->alias, field, pred_node, pred_itr->key, ctx );
2954 else if( pred_node->type == JSON_ARRAY )
2955 pred = searchFunctionPredicate(
2956 class_info->alias, field, pred_node, pred_itr->key );
2957 else if( pred_node->type == JSON_HASH )
2958 pred = searchFieldTransformPredicate(
2959 class_info, field, pred_node, pred_itr->key );
2961 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2963 jsonIteratorFree( pred_itr );
2965 } else if( node->type == JSON_NULL ) { // IS NULL search
2966 growing_buffer* _p = buffer_init( 64 );
2969 "\"%s\".%s IS NULL",
2970 class_info->class_name,
2971 osrfHashGet( field, "name" )
2973 pred = buffer_release( _p );
2974 } else { // equality search
2975 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2994 field : call_number,
3010 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3012 const jsonObject* working_hash;
3013 jsonObject* freeable_hash = NULL;
3015 if( join_hash->type == JSON_HASH ) {
3016 working_hash = join_hash;
3017 } else if( join_hash->type == JSON_STRING ) {
3018 // turn it into a JSON_HASH by creating a wrapper
3019 // around a copy of the original
3020 const char* _tmp = jsonObjectGetString( join_hash );
3021 freeable_hash = jsonNewObjectType( JSON_HASH );
3022 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3023 working_hash = freeable_hash;
3027 "%s: JOIN failed; expected JSON object type not found",
3033 growing_buffer* join_buf = buffer_init( 128 );
3034 const char* leftclass = left_info->class_name;
3036 jsonObject* snode = NULL;
3037 jsonIterator* search_itr = jsonNewIterator( working_hash );
3039 while ( (snode = jsonIteratorNext( search_itr )) ) {
3040 const char* right_alias = search_itr->key;
3042 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3044 class = right_alias;
3046 const ClassInfo* right_info = add_joined_class( right_alias, class );
3050 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3054 jsonIteratorFree( search_itr );
3055 buffer_free( join_buf );
3057 jsonObjectFree( freeable_hash );
3060 osrfHash* links = right_info->links;
3061 const char* table = right_info->source_def;
3063 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3064 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3066 if( field && !fkey ) {
3067 // Look up the corresponding join column in the IDL.
3068 // The link must be defined in the child table,
3069 // and point to the right parent table.
3070 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3071 const char* reltype = NULL;
3072 const char* other_class = NULL;
3073 reltype = osrfHashGet( idl_link, "reltype" );
3074 if( reltype && strcmp( reltype, "has_many" ) )
3075 other_class = osrfHashGet( idl_link, "class" );
3076 if( other_class && !strcmp( other_class, leftclass ) )
3077 fkey = osrfHashGet( idl_link, "key" );
3081 "%s: JOIN failed. No link defined from %s.%s to %s",
3087 buffer_free( join_buf );
3089 jsonObjectFree( freeable_hash );
3090 jsonIteratorFree( search_itr );
3094 } else if( !field && fkey ) {
3095 // Look up the corresponding join column in the IDL.
3096 // The link must be defined in the child table,
3097 // and point to the right parent table.
3098 osrfHash* left_links = left_info->links;
3099 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3100 const char* reltype = NULL;
3101 const char* other_class = NULL;
3102 reltype = osrfHashGet( idl_link, "reltype" );
3103 if( reltype && strcmp( reltype, "has_many" ) )
3104 other_class = osrfHashGet( idl_link, "class" );
3105 if( other_class && !strcmp( other_class, class ) )
3106 field = osrfHashGet( idl_link, "key" );
3110 "%s: JOIN failed. No link defined from %s.%s to %s",
3116 buffer_free( join_buf );
3118 jsonObjectFree( freeable_hash );
3119 jsonIteratorFree( search_itr );
3123 } else if( !field && !fkey ) {
3124 osrfHash* left_links = left_info->links;
3126 // For each link defined for the left class:
3127 // see if the link references the joined class
3128 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3129 osrfHash* curr_link = NULL;
3130 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3131 const char* other_class = osrfHashGet( curr_link, "class" );
3132 if( other_class && !strcmp( other_class, class ) ) {
3134 // In the IDL, the parent class doesn't always know then names of the child
3135 // columns that are pointing to it, so don't use that end of the link
3136 const char* reltype = osrfHashGet( curr_link, "reltype" );
3137 if( reltype && strcmp( reltype, "has_many" ) ) {
3138 // Found a link between the classes
3139 fkey = osrfHashIteratorKey( itr );
3140 field = osrfHashGet( curr_link, "key" );
3145 osrfHashIteratorFree( itr );
3147 if( !field || !fkey ) {
3148 // Do another such search, with the classes reversed
3150 // For each link defined for the joined class:
3151 // see if the link references the left class
3152 osrfHashIterator* itr = osrfNewHashIterator( links );
3153 osrfHash* curr_link = NULL;
3154 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3155 const char* other_class = osrfHashGet( curr_link, "class" );
3156 if( other_class && !strcmp( other_class, leftclass ) ) {
3158 // In the IDL, the parent class doesn't know then names of the child
3159 // columns that are pointing to it, so don't use that end of the link
3160 const char* reltype = osrfHashGet( curr_link, "reltype" );
3161 if( reltype && strcmp( reltype, "has_many" ) ) {
3162 // Found a link between the classes
3163 field = osrfHashIteratorKey( itr );
3164 fkey = osrfHashGet( curr_link, "key" );
3169 osrfHashIteratorFree( itr );
3172 if( !field || !fkey ) {
3175 "%s: JOIN failed. No link defined between %s and %s",
3180 buffer_free( join_buf );
3182 jsonObjectFree( freeable_hash );
3183 jsonIteratorFree( search_itr );
3188 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3190 if( !strcasecmp( type,"left" )) {
3191 buffer_add( join_buf, " LEFT JOIN" );
3192 } else if( !strcasecmp( type,"right" )) {
3193 buffer_add( join_buf, " RIGHT JOIN" );
3194 } else if( !strcasecmp( type,"full" )) {
3195 buffer_add( join_buf, " FULL JOIN" );
3197 buffer_add( join_buf, " INNER JOIN" );
3200 buffer_add( join_buf, " INNER JOIN" );
3203 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3204 table, right_alias, right_alias, field, left_info->alias, fkey );
3206 // Add any other join conditions as specified by "filter"
3207 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3209 const char* filter_op = jsonObjectGetString(
3210 jsonObjectGetKeyConst( snode, "filter_op" ) );
3211 if( filter_op && !strcasecmp( "or",filter_op )) {
3212 buffer_add( join_buf, " OR " );
3214 buffer_add( join_buf, " AND " );
3217 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3219 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3220 OSRF_BUFFER_ADD( join_buf, jpred );
3225 "%s: JOIN failed. Invalid conditional expression.",
3228 jsonIteratorFree( search_itr );
3229 buffer_free( join_buf );
3231 jsonObjectFree( freeable_hash );
3236 buffer_add( join_buf, " ) " );
3238 // Recursively add a nested join, if one is present
3239 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3241 char* jpred = searchJOIN( join_filter, right_info );
3243 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3244 OSRF_BUFFER_ADD( join_buf, jpred );
3247 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3248 jsonIteratorFree( search_itr );
3249 buffer_free( join_buf );
3251 jsonObjectFree( freeable_hash );
3258 jsonObjectFree( freeable_hash );
3259 jsonIteratorFree( search_itr );
3261 return buffer_release( join_buf );
3266 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3267 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3268 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3270 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3272 search_hash is the JSON expression of the conditions.
3273 meta is the class definition from the IDL, for the relevant table.
3274 opjoin_type indicates whether multiple conditions, if present, should be
3275 connected by AND or OR.
3276 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3277 to pass it to other functions -- and all they do with it is to use the session
3278 and request members to send error messages back to the client.
3282 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3283 int opjoin_type, osrfMethodContext* ctx ) {
3287 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3288 "opjoin_type = %d, ctx addr = %p",
3291 class_info->class_def,
3296 growing_buffer* sql_buf = buffer_init( 128 );
3298 jsonObject* node = NULL;
3301 if( search_hash->type == JSON_ARRAY ) {
3302 if( 0 == search_hash->size ) {
3305 "%s: Invalid predicate structure: empty JSON array",
3308 buffer_free( sql_buf );
3312 unsigned long i = 0;
3313 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3317 if( opjoin_type == OR_OP_JOIN )
3318 buffer_add( sql_buf, " OR " );
3320 buffer_add( sql_buf, " AND " );
3323 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3325 buffer_free( sql_buf );
3329 buffer_fadd( sql_buf, "( %s )", subpred );
3333 } else if( search_hash->type == JSON_HASH ) {
3334 osrfLogDebug( OSRF_LOG_MARK,
3335 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3336 jsonIterator* search_itr = jsonNewIterator( search_hash );
3337 if( !jsonIteratorHasNext( search_itr ) ) {
3340 "%s: Invalid predicate structure: empty JSON object",
3343 jsonIteratorFree( search_itr );
3344 buffer_free( sql_buf );
3348 while( (node = jsonIteratorNext( search_itr )) ) {
3353 if( opjoin_type == OR_OP_JOIN )
3354 buffer_add( sql_buf, " OR " );
3356 buffer_add( sql_buf, " AND " );
3359 if( '+' == search_itr->key[ 0 ] ) {
3361 // This plus sign prefixes a class name or other table alias;
3362 // make sure the table alias is in scope
3363 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3364 if( ! alias_info ) {
3367 "%s: Invalid table alias \"%s\" in WHERE clause",
3371 jsonIteratorFree( search_itr );
3372 buffer_free( sql_buf );
3376 if( node->type == JSON_STRING ) {
3377 // It's the name of a column; make sure it belongs to the class
3378 const char* fieldname = jsonObjectGetString( node );
3379 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3382 "%s: Invalid column name \"%s\" in WHERE clause "
3383 "for table alias \"%s\"",
3388 jsonIteratorFree( search_itr );
3389 buffer_free( sql_buf );
3393 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3395 // It's something more complicated
3396 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3398 jsonIteratorFree( search_itr );
3399 buffer_free( sql_buf );
3403 buffer_fadd( sql_buf, "( %s )", subpred );
3406 } else if( '-' == search_itr->key[ 0 ] ) {
3407 if( !strcasecmp( "-or", search_itr->key )) {
3408 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3410 jsonIteratorFree( search_itr );
3411 buffer_free( sql_buf );
3415 buffer_fadd( sql_buf, "( %s )", subpred );
3417 } else if( !strcasecmp( "-and", search_itr->key )) {
3418 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3420 jsonIteratorFree( search_itr );
3421 buffer_free( sql_buf );
3425 buffer_fadd( sql_buf, "( %s )", subpred );
3427 } else if( !strcasecmp("-not",search_itr->key) ) {
3428 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3430 jsonIteratorFree( search_itr );
3431 buffer_free( sql_buf );
3435 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3437 } else if( !strcasecmp( "-exists", search_itr->key )) {
3438 char* subpred = buildQuery( ctx, node, SUBSELECT );
3440 jsonIteratorFree( search_itr );
3441 buffer_free( sql_buf );
3445 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3447 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3448 char* subpred = buildQuery( ctx, node, SUBSELECT );
3450 jsonIteratorFree( search_itr );
3451 buffer_free( sql_buf );
3455 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3457 } else { // Invalid "minus" operator
3460 "%s: Invalid operator \"%s\" in WHERE clause",
3464 jsonIteratorFree( search_itr );
3465 buffer_free( sql_buf );
3471 const char* class = class_info->class_name;
3472 osrfHash* fields = class_info->fields;
3473 osrfHash* field = osrfHashGet( fields, search_itr->key );
3476 const char* table = class_info->source_def;
3479 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3482 table ? table : "?",
3485 jsonIteratorFree( search_itr );
3486 buffer_free( sql_buf );
3490 char* subpred = searchPredicate( class_info, field, node, ctx );
3492 buffer_free( sql_buf );
3493 jsonIteratorFree( search_itr );
3497 buffer_add( sql_buf, subpred );
3501 jsonIteratorFree( search_itr );
3504 // ERROR ... only hash and array allowed at this level
3505 char* predicate_string = jsonObjectToJSON( search_hash );
3508 "%s: Invalid predicate structure: %s",
3512 buffer_free( sql_buf );
3513 free( predicate_string );
3517 return buffer_release( sql_buf );
3520 /* Build a JSON_ARRAY of field names for a given table alias
3522 static jsonObject* defaultSelectList( const char* table_alias ) {
3527 ClassInfo* class_info = search_all_alias( table_alias );
3528 if( ! class_info ) {
3531 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3538 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3539 osrfHash* field_def = NULL;
3540 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3541 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3542 const char* field_name = osrfHashIteratorKey( field_itr );
3543 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3544 jsonObjectPush( array, jsonNewObject( field_name ) );
3547 osrfHashIteratorFree( field_itr );
3552 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3553 // The jsonObject must be a JSON_HASH with an single entry for "union",
3554 // "intersect", or "except". The data associated with this key must be an
3555 // array of hashes, each hash being a query.
3556 // Also allowed but currently ignored: entries for "order_by" and "alias".
3557 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3559 if( ! combo || combo->type != JSON_HASH )
3560 return NULL; // should be impossible; validated by caller
3562 const jsonObject* query_array = NULL; // array of subordinate queries
3563 const char* op = NULL; // name of operator, e.g. UNION
3564 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3565 int op_count = 0; // for detecting conflicting operators
3566 int excepting = 0; // boolean
3567 int all = 0; // boolean
3568 jsonObject* order_obj = NULL;
3570 // Identify the elements in the hash
3571 jsonIterator* query_itr = jsonNewIterator( combo );
3572 jsonObject* curr_obj = NULL;
3573 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3574 if( ! strcmp( "union", query_itr->key ) ) {
3577 query_array = curr_obj;
3578 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3581 query_array = curr_obj;
3582 } else if( ! strcmp( "except", query_itr->key ) ) {
3586 query_array = curr_obj;
3587 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3590 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3593 order_obj = curr_obj;
3594 } else if( ! strcmp( "alias", query_itr->key ) ) {
3595 if( curr_obj->type != JSON_STRING ) {
3596 jsonIteratorFree( query_itr );
3599 alias = jsonObjectGetString( curr_obj );
3600 } else if( ! strcmp( "all", query_itr->key ) ) {
3601 if( obj_is_true( curr_obj ) )
3605 osrfAppSessionStatus(
3607 OSRF_STATUS_INTERNALSERVERERROR,
3608 "osrfMethodException",
3610 "Malformed query; unexpected entry in query object"
3614 "%s: Unexpected entry for \"%s\" in%squery",
3619 jsonIteratorFree( query_itr );
3623 jsonIteratorFree( query_itr );
3625 // More sanity checks
3626 if( ! query_array ) {
3628 osrfAppSessionStatus(
3630 OSRF_STATUS_INTERNALSERVERERROR,
3631 "osrfMethodException",
3633 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3637 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3640 return NULL; // should be impossible...
3641 } else if( op_count > 1 ) {
3643 osrfAppSessionStatus(
3645 OSRF_STATUS_INTERNALSERVERERROR,
3646 "osrfMethodException",
3648 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3652 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3656 } if( query_array->type != JSON_ARRAY ) {
3658 osrfAppSessionStatus(
3660 OSRF_STATUS_INTERNALSERVERERROR,
3661 "osrfMethodException",
3663 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3667 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3670 json_type( query_array->type )
3673 } if( query_array->size < 2 ) {
3675 osrfAppSessionStatus(
3677 OSRF_STATUS_INTERNALSERVERERROR,
3678 "osrfMethodException",
3680 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3684 "%s:%srequires multiple queries as operands",
3689 } else if( excepting && query_array->size > 2 ) {
3691 osrfAppSessionStatus(
3693 OSRF_STATUS_INTERNALSERVERERROR,
3694 "osrfMethodException",
3696 "EXCEPT operator has too many queries as operands"
3700 "%s:EXCEPT operator has too many queries as operands",
3704 } else if( order_obj && ! alias ) {
3706 osrfAppSessionStatus(
3708 OSRF_STATUS_INTERNALSERVERERROR,
3709 "osrfMethodException",
3711 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3715 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3721 // So far so good. Now build the SQL.
3722 growing_buffer* sql = buffer_init( 256 );
3724 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3725 // Add a layer of parentheses
3726 if( flags & SUBCOMBO )
3727 OSRF_BUFFER_ADD( sql, "( " );
3729 // Traverse the query array. Each entry should be a hash.
3730 int first = 1; // boolean
3732 jsonObject* query = NULL;
3733 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3734 if( query->type != JSON_HASH ) {
3736 osrfAppSessionStatus(
3738 OSRF_STATUS_INTERNALSERVERERROR,
3739 "osrfMethodException",
3741 "Malformed query under UNION, INTERSECT or EXCEPT"
3745 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3748 json_type( query->type )
3757 OSRF_BUFFER_ADD( sql, op );
3759 OSRF_BUFFER_ADD( sql, "ALL " );
3762 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3766 "%s: Error building query under%s",
3774 OSRF_BUFFER_ADD( sql, query_str );
3777 if( flags & SUBCOMBO )
3778 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3780 if( !(flags & SUBSELECT) )
3781 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3783 return buffer_release( sql );
3786 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3787 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3788 // or "except" to indicate the type of query.
3789 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3793 osrfAppSessionStatus(
3795 OSRF_STATUS_INTERNALSERVERERROR,
3796 "osrfMethodException",
3798 "Malformed query; no query object"
3800 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3802 } else if( query->type != JSON_HASH ) {
3804 osrfAppSessionStatus(
3806 OSRF_STATUS_INTERNALSERVERERROR,
3807 "osrfMethodException",
3809 "Malformed query object"
3813 "%s: Query object is %s instead of JSON_HASH",
3815 json_type( query->type )
3820 // Determine what kind of query it purports to be, and dispatch accordingly.
3821 if( jsonObjectGetKeyConst( query, "union" ) ||
3822 jsonObjectGetKeyConst( query, "intersect" ) ||
3823 jsonObjectGetKeyConst( query, "except" )) {
3824 return doCombo( ctx, query, flags );
3826 // It is presumably a SELECT query
3828 // Push a node onto the stack for the current query. Every level of
3829 // subquery gets its own QueryFrame on the Stack.
3832 // Build an SQL SELECT statement
3835 jsonObjectGetKey( query, "select" ),
3836 jsonObjectGetKeyConst( query, "from" ),
3837 jsonObjectGetKeyConst( query, "where" ),
3838 jsonObjectGetKeyConst( query, "having" ),
3839 jsonObjectGetKeyConst( query, "order_by" ),
3840 jsonObjectGetKeyConst( query, "limit" ),
3841 jsonObjectGetKeyConst( query, "offset" ),
3850 /* method context */ osrfMethodContext* ctx,
3852 /* SELECT */ jsonObject* selhash,
3853 /* FROM */ const jsonObject* join_hash,
3854 /* WHERE */ const jsonObject* search_hash,
3855 /* HAVING */ const jsonObject* having_hash,
3856 /* ORDER BY */ const jsonObject* order_hash,
3857 /* LIMIT */ const jsonObject* limit,
3858 /* OFFSET */ const jsonObject* offset,
3859 /* flags */ int flags
3861 const char* locale = osrf_message_get_last_locale();
3863 // general tmp objects
3864 const jsonObject* tmp_const;
3865 jsonObject* selclass = NULL;
3866 jsonObject* snode = NULL;
3867 jsonObject* onode = NULL;
3869 char* string = NULL;
3870 int from_function = 0;
3875 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3877 // punt if there's no FROM clause
3878 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3881 "%s: FROM clause is missing or empty",
3885 osrfAppSessionStatus(
3887 OSRF_STATUS_INTERNALSERVERERROR,
3888 "osrfMethodException",
3890 "FROM clause is missing or empty in JSON query"
3895 // the core search class
3896 const char* core_class = NULL;
3898 // get the core class -- the only key of the top level FROM clause, or a string
3899 if( join_hash->type == JSON_HASH ) {
3900 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3901 snode = jsonIteratorNext( tmp_itr );
3903 // Populate the current QueryFrame with information
3904 // about the core class
3905 if( add_query_core( NULL, tmp_itr->key ) ) {
3907 osrfAppSessionStatus(
3909 OSRF_STATUS_INTERNALSERVERERROR,
3910 "osrfMethodException",
3912 "Unable to look up core class"
3916 core_class = curr_query->core.class_name;
3919 jsonObject* extra = jsonIteratorNext( tmp_itr );
3921 jsonIteratorFree( tmp_itr );
3924 // There shouldn't be more than one entry in join_hash
3928 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3932 osrfAppSessionStatus(
3934 OSRF_STATUS_INTERNALSERVERERROR,
3935 "osrfMethodException",
3937 "Malformed FROM clause in JSON query"
3939 return NULL; // Malformed join_hash; extra entry
3941 } else if( join_hash->type == JSON_ARRAY ) {
3942 // We're selecting from a function, not from a table
3944 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3947 } else if( join_hash->type == JSON_STRING ) {
3948 // Populate the current QueryFrame with information
3949 // about the core class
3950 core_class = jsonObjectGetString( join_hash );
3952 if( add_query_core( NULL, core_class ) ) {
3954 osrfAppSessionStatus(
3956 OSRF_STATUS_INTERNALSERVERERROR,
3957 "osrfMethodException",
3959 "Unable to look up core class"
3967 "%s: FROM clause is unexpected JSON type: %s",
3969 json_type( join_hash->type )
3972 osrfAppSessionStatus(
3974 OSRF_STATUS_INTERNALSERVERERROR,
3975 "osrfMethodException",
3977 "Ill-formed FROM clause in JSON query"
3982 // Build the join clause, if any, while filling out the list
3983 // of joined classes in the current QueryFrame.
3984 char* join_clause = NULL;
3985 if( join_hash && ! from_function ) {
3987 join_clause = searchJOIN( join_hash, &curr_query->core );
3988 if( ! join_clause ) {
3990 osrfAppSessionStatus(
3992 OSRF_STATUS_INTERNALSERVERERROR,
3993 "osrfMethodException",
3995 "Unable to construct JOIN clause(s)"
4001 // For in case we don't get a select list
4002 jsonObject* defaultselhash = NULL;
4004 // if there is no select list, build a default select list ...
4005 if( !selhash && !from_function ) {
4006 jsonObject* default_list = defaultSelectList( core_class );
4007 if( ! default_list ) {
4009 osrfAppSessionStatus(
4011 OSRF_STATUS_INTERNALSERVERERROR,
4012 "osrfMethodException",
4014 "Unable to build default SELECT clause in JSON query"
4016 free( join_clause );
4021 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4022 jsonObjectSetKey( selhash, core_class, default_list );
4025 // The SELECT clause can be encoded only by a hash
4026 if( !from_function && selhash->type != JSON_HASH ) {
4029 "%s: Expected JSON_HASH for SELECT clause; found %s",
4031 json_type( selhash->type )
4035 osrfAppSessionStatus(
4037 OSRF_STATUS_INTERNALSERVERERROR,
4038 "osrfMethodException",
4040 "Malformed SELECT clause in JSON query"
4042 free( join_clause );
4046 // If you see a null or wild card specifier for the core class, or an
4047 // empty array, replace it with a default SELECT list
4048 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4050 int default_needed = 0; // boolean
4051 if( JSON_STRING == tmp_const->type
4052 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4054 else if( JSON_NULL == tmp_const->type )
4057 if( default_needed ) {
4058 // Build a default SELECT list
4059 jsonObject* default_list = defaultSelectList( core_class );
4060 if( ! default_list ) {
4062 osrfAppSessionStatus(
4064 OSRF_STATUS_INTERNALSERVERERROR,
4065 "osrfMethodException",
4067 "Can't build default SELECT clause in JSON query"
4069 free( join_clause );
4074 jsonObjectSetKey( selhash, core_class, default_list );
4078 // temp buffers for the SELECT list and GROUP BY clause
4079 growing_buffer* select_buf = buffer_init( 128 );
4080 growing_buffer* group_buf = buffer_init( 128 );
4082 int aggregate_found = 0; // boolean
4084 // Build a select list
4085 if( from_function ) // From a function we select everything
4086 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4089 // Build the SELECT list as SQL
4093 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4094 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4096 const char* cname = selclass_itr->key;
4098 // Make sure the target relation is in the FROM clause.
4100 // At this point join_hash is a step down from the join_hash we
4101 // received as a parameter. If the original was a JSON_STRING,
4102 // then json_hash is now NULL. If the original was a JSON_HASH,
4103 // then json_hash is now the first (and only) entry in it,
4104 // denoting the core class. We've already excluded the
4105 // possibility that the original was a JSON_ARRAY, because in
4106 // that case from_function would be non-NULL, and we wouldn't
4109 // If the current table alias isn't in scope, bail out
4110 ClassInfo* class_info = search_alias( cname );
4111 if( ! class_info ) {
4114 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4119 osrfAppSessionStatus(
4121 OSRF_STATUS_INTERNALSERVERERROR,
4122 "osrfMethodException",
4124 "Selected class not in FROM clause in JSON query"
4126 jsonIteratorFree( selclass_itr );
4127 buffer_free( select_buf );
4128 buffer_free( group_buf );
4129 if( defaultselhash )
4130 jsonObjectFree( defaultselhash );
4131 free( join_clause );
4135 if( selclass->type != JSON_ARRAY ) {
4138 "%s: Malformed SELECT list for class \"%s\"; not an array",
4143 osrfAppSessionStatus(
4145 OSRF_STATUS_INTERNALSERVERERROR,
4146 "osrfMethodException",
4148 "Selected class not in FROM clause in JSON query"
4151 jsonIteratorFree( selclass_itr );
4152 buffer_free( select_buf );
4153 buffer_free( group_buf );
4154 if( defaultselhash )
4155 jsonObjectFree( defaultselhash );
4156 free( join_clause );
4160 // Look up some attributes of the current class
4161 osrfHash* idlClass = class_info->class_def;
4162 osrfHash* class_field_set = class_info->fields;
4163 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4164 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4166 if( 0 == selclass->size ) {
4169 "%s: No columns selected from \"%s\"",
4175 // stitch together the column list for the current table alias...
4176 unsigned long field_idx = 0;
4177 jsonObject* selfield = NULL;
4178 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4180 // If we need a separator comma, add one
4184 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4187 // if the field specification is a string, add it to the list
4188 if( selfield->type == JSON_STRING ) {
4190 // Look up the field in the IDL
4191 const char* col_name = jsonObjectGetString( selfield );
4192 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4194 // No such field in current class
4197 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4203 osrfAppSessionStatus(
4205 OSRF_STATUS_INTERNALSERVERERROR,
4206 "osrfMethodException",
4208 "Selected column not defined in JSON query"
4210 jsonIteratorFree( selclass_itr );
4211 buffer_free( select_buf );
4212 buffer_free( group_buf );
4213 if( defaultselhash )
4214 jsonObjectFree( defaultselhash );
4215 free( join_clause );
4217 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4218 // Virtual field not allowed
4221 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4227 osrfAppSessionStatus(
4229 OSRF_STATUS_INTERNALSERVERERROR,
4230 "osrfMethodException",
4232 "Selected column may not be virtual in JSON query"
4234 jsonIteratorFree( selclass_itr );
4235 buffer_free( select_buf );
4236 buffer_free( group_buf );
4237 if( defaultselhash )
4238 jsonObjectFree( defaultselhash );
4239 free( join_clause );
4245 if( flags & DISABLE_I18N )
4248 i18n = osrfHashGet( field_def, "i18n" );
4250 if( str_is_true( i18n ) ) {
4251 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4252 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4253 class_tname, cname, col_name, class_pkey,
4254 cname, class_pkey, locale, col_name );
4256 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4257 cname, col_name, col_name );
4260 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4261 cname, col_name, col_name );
4264 // ... but it could be an object, in which case we check for a Field Transform
4265 } else if( selfield->type == JSON_HASH ) {
4267 const char* col_name = jsonObjectGetString(
4268 jsonObjectGetKeyConst( selfield, "column" ) );
4270 // Get the field definition from the IDL
4271 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4273 // No such field in current class
4276 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4282 osrfAppSessionStatus(
4284 OSRF_STATUS_INTERNALSERVERERROR,
4285 "osrfMethodException",
4287 "Selected column is not defined in JSON query"
4289 jsonIteratorFree( selclass_itr );
4290 buffer_free( select_buf );
4291 buffer_free( group_buf );
4292 if( defaultselhash )
4293 jsonObjectFree( defaultselhash );
4294 free( join_clause );
4296 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4297 // No such field in current class
4300 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4306 osrfAppSessionStatus(
4308 OSRF_STATUS_INTERNALSERVERERROR,
4309 "osrfMethodException",
4311 "Selected column is virtual in JSON query"
4313 jsonIteratorFree( selclass_itr );
4314 buffer_free( select_buf );
4315 buffer_free( group_buf );
4316 if( defaultselhash )
4317 jsonObjectFree( defaultselhash );
4318 free( join_clause );
4322 // Decide what to use as a column alias
4324 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4325 _alias = jsonObjectGetString( tmp_const );
4326 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4327 _alias = jsonObjectGetString( tmp_const );
4328 } else { // Use field name as the alias
4332 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4333 char* transform_str = searchFieldTransform(
4334 class_info->alias, field_def, selfield );
4335 if( transform_str ) {
4336 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4337 free( transform_str );
4340 osrfAppSessionStatus(
4342 OSRF_STATUS_INTERNALSERVERERROR,
4343 "osrfMethodException",
4345 "Unable to generate transform function in JSON query"
4347 jsonIteratorFree( selclass_itr );
4348 buffer_free( select_buf );
4349 buffer_free( group_buf );
4350 if( defaultselhash )
4351 jsonObjectFree( defaultselhash );
4352 free( join_clause );
4359 if( flags & DISABLE_I18N )
4362 i18n = osrfHashGet( field_def, "i18n" );
4364 if( str_is_true( i18n ) ) {
4365 buffer_fadd( select_buf,
4366 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4367 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4368 class_tname, cname, col_name, class_pkey, cname,
4369 class_pkey, locale, _alias );
4371 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4372 cname, col_name, _alias );
4375 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4376 cname, col_name, _alias );
4383 "%s: Selected item is unexpected JSON type: %s",
4385 json_type( selfield->type )
4388 osrfAppSessionStatus(
4390 OSRF_STATUS_INTERNALSERVERERROR,
4391 "osrfMethodException",
4393 "Ill-formed SELECT item in JSON query"
4395 jsonIteratorFree( selclass_itr );
4396 buffer_free( select_buf );
4397 buffer_free( group_buf );
4398 if( defaultselhash )
4399 jsonObjectFree( defaultselhash );
4400 free( join_clause );
4404 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4405 if( obj_is_true( agg_obj ) )
4406 aggregate_found = 1;
4408 // Append a comma (except for the first one)
4409 // and add the column to a GROUP BY clause
4413 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4415 buffer_fadd( group_buf, " %d", sel_pos );
4419 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4421 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( elfield, "aggregate");
4422 if ( ! obj_is_true( aggregate_obj ) ) {
4426 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4429 buffer_fadd(group_buf, " %d", sel_pos);
4432 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4436 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4439 _column = searchFieldTransform(class_info->alias, field, selfield);
4440 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4441 OSRF_BUFFER_ADD(group_buf, _column);
4442 _column = searchFieldTransform(class_info->alias, field, selfield);
4449 } // end while -- iterating across SELECT columns
4451 } // end while -- iterating across classes
4453 jsonIteratorFree( selclass_itr );
4456 char* col_list = buffer_release( select_buf );
4458 // Make sure the SELECT list isn't empty. This can happen, for example,
4459 // if we try to build a default SELECT clause from a non-core table.
4462 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4464 osrfAppSessionStatus(
4466 OSRF_STATUS_INTERNALSERVERERROR,
4467 "osrfMethodException",
4469 "SELECT list is empty"
4472 buffer_free( group_buf );
4473 if( defaultselhash )
4474 jsonObjectFree( defaultselhash );
4475 free( join_clause );
4481 table = searchValueTransform( join_hash );
4483 table = strdup( curr_query->core.source_def );
4487 osrfAppSessionStatus(
4489 OSRF_STATUS_INTERNALSERVERERROR,
4490 "osrfMethodException",
4492 "Unable to identify table for core class"
4495 buffer_free( group_buf );
4496 if( defaultselhash )
4497 jsonObjectFree( defaultselhash );
4498 free( join_clause );
4502 // Put it all together
4503 growing_buffer* sql_buf = buffer_init( 128 );
4504 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4508 // Append the join clause, if any
4510 buffer_add(sql_buf, join_clause );
4511 free( join_clause );
4514 char* order_by_list = NULL;
4515 char* having_buf = NULL;
4517 if( !from_function ) {
4519 // Build a WHERE clause, if there is one
4521 buffer_add( sql_buf, " WHERE " );
4523 // and it's on the WHERE clause
4524 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4527 osrfAppSessionStatus(
4529 OSRF_STATUS_INTERNALSERVERERROR,
4530 "osrfMethodException",
4532 "Severe query error in WHERE predicate -- see error log for more details"
4535 buffer_free( group_buf );
4536 buffer_free( sql_buf );
4537 if( defaultselhash )
4538 jsonObjectFree( defaultselhash );
4542 buffer_add( sql_buf, pred );
4546 // Build a HAVING clause, if there is one
4549 // and it's on the the WHERE clause
4550 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4552 if( ! having_buf ) {
4554 osrfAppSessionStatus(
4556 OSRF_STATUS_INTERNALSERVERERROR,
4557 "osrfMethodException",
4559 "Severe query error in HAVING predicate -- see error log for more details"
4562 buffer_free( group_buf );
4563 buffer_free( sql_buf );
4564 if( defaultselhash )
4565 jsonObjectFree( defaultselhash );
4570 // Build an ORDER BY clause, if there is one
4571 if( NULL == order_hash )
4572 ; // No ORDER BY? do nothing
4573 else if( JSON_ARRAY == order_hash->type ) {
4574 order_by_list = buildOrderByFromArray( ctx, order_hash );
4575 if( !order_by_list ) {
4577 buffer_free( group_buf );
4578 buffer_free( sql_buf );
4579 if( defaultselhash )
4580 jsonObjectFree( defaultselhash );
4583 } else if( JSON_HASH == order_hash->type ) {
4584 // This hash is keyed on class alias. Each class has either
4585 // an array of field names or a hash keyed on field name.
4586 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4587 jsonIterator* class_itr = jsonNewIterator( order_hash );
4588 while( (snode = jsonIteratorNext( class_itr )) ) {
4590 ClassInfo* order_class_info = search_alias( class_itr->key );
4591 if( ! order_class_info ) {
4592 osrfLogError( OSRF_LOG_MARK,
4593 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4594 modulename, class_itr->key );
4596 osrfAppSessionStatus(
4598 OSRF_STATUS_INTERNALSERVERERROR,
4599 "osrfMethodException",
4601 "Invalid class referenced in ORDER BY clause -- "
4602 "see error log for more details"
4604 jsonIteratorFree( class_itr );
4605 buffer_free( order_buf );
4607 buffer_free( group_buf );
4608 buffer_free( sql_buf );
4609 if( defaultselhash )
4610 jsonObjectFree( defaultselhash );
4614 osrfHash* field_list_def = order_class_info->fields;
4616 if( snode->type == JSON_HASH ) {
4618 // Hash is keyed on field names from the current class. For each field
4619 // there is another layer of hash to define the sorting details, if any,
4620 // or a string to indicate direction of sorting.
4621 jsonIterator* order_itr = jsonNewIterator( snode );
4622 while( (onode = jsonIteratorNext( order_itr )) ) {
4624 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4626 osrfLogError( OSRF_LOG_MARK,
4627 "%s: Invalid field \"%s\" in ORDER BY clause",
4628 modulename, order_itr->key );
4630 osrfAppSessionStatus(
4632 OSRF_STATUS_INTERNALSERVERERROR,
4633 "osrfMethodException",
4635 "Invalid field in ORDER BY clause -- "
4636 "see error log for more details"
4638 jsonIteratorFree( order_itr );
4639 jsonIteratorFree( class_itr );
4640 buffer_free( order_buf );
4642 buffer_free( group_buf );
4643 buffer_free( sql_buf );
4644 if( defaultselhash )
4645 jsonObjectFree( defaultselhash );
4647 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4648 osrfLogError( OSRF_LOG_MARK,
4649 "%s: Virtual field \"%s\" in ORDER BY clause",
4650 modulename, order_itr->key );
4652 osrfAppSessionStatus(
4654 OSRF_STATUS_INTERNALSERVERERROR,
4655 "osrfMethodException",
4657 "Virtual field in ORDER BY clause -- "
4658 "see error log for more details"
4660 jsonIteratorFree( order_itr );
4661 jsonIteratorFree( class_itr );
4662 buffer_free( order_buf );
4664 buffer_free( group_buf );
4665 buffer_free( sql_buf );
4666 if( defaultselhash )
4667 jsonObjectFree( defaultselhash );
4671 const char* direction = NULL;
4672 if( onode->type == JSON_HASH ) {
4673 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4674 string = searchFieldTransform(
4676 osrfHashGet( field_list_def, order_itr->key ),
4680 if( ctx ) osrfAppSessionStatus(
4682 OSRF_STATUS_INTERNALSERVERERROR,
4683 "osrfMethodException",
4685 "Severe query error in ORDER BY clause -- "
4686 "see error log for more details"
4688 jsonIteratorFree( order_itr );
4689 jsonIteratorFree( class_itr );
4691 buffer_free( group_buf );
4692 buffer_free( order_buf);
4693 buffer_free( sql_buf );
4694 if( defaultselhash )
4695 jsonObjectFree( defaultselhash );
4699 growing_buffer* field_buf = buffer_init( 16 );
4700 buffer_fadd( field_buf, "\"%s\".%s",
4701 class_itr->key, order_itr->key );
4702 string = buffer_release( field_buf );
4705 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4706 const char* dir = jsonObjectGetString( tmp_const );
4707 if(!strncasecmp( dir, "d", 1 )) {
4708 direction = " DESC";
4714 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4715 osrfLogError( OSRF_LOG_MARK,
4716 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4717 modulename, json_type( onode->type ) );
4719 osrfAppSessionStatus(
4721 OSRF_STATUS_INTERNALSERVERERROR,
4722 "osrfMethodException",
4724 "Malformed ORDER BY clause -- see error log for more details"
4726 jsonIteratorFree( order_itr );
4727 jsonIteratorFree( class_itr );
4729 buffer_free( group_buf );
4730 buffer_free( order_buf );
4731 buffer_free( sql_buf );
4732 if( defaultselhash )
4733 jsonObjectFree( defaultselhash );
4737 string = strdup( order_itr->key );
4738 const char* dir = jsonObjectGetString( onode );
4739 if( !strncasecmp( dir, "d", 1 )) {
4740 direction = " DESC";
4747 OSRF_BUFFER_ADD( order_buf, ", " );
4749 order_buf = buffer_init( 128 );
4751 OSRF_BUFFER_ADD( order_buf, string );
4755 OSRF_BUFFER_ADD( order_buf, direction );
4759 jsonIteratorFree( order_itr );
4761 } else if( snode->type == JSON_ARRAY ) {
4763 // Array is a list of fields from the current class
4764 unsigned long order_idx = 0;
4765 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4767 const char* _f = jsonObjectGetString( onode );
4769 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4771 osrfLogError( OSRF_LOG_MARK,
4772 "%s: Invalid field \"%s\" in ORDER BY clause",
4775 osrfAppSessionStatus(
4777 OSRF_STATUS_INTERNALSERVERERROR,
4778 "osrfMethodException",
4780 "Invalid field in ORDER BY clause -- "
4781 "see error log for more details"
4783 jsonIteratorFree( class_itr );
4784 buffer_free( order_buf );
4786 buffer_free( group_buf );
4787 buffer_free( sql_buf );
4788 if( defaultselhash )
4789 jsonObjectFree( defaultselhash );
4791 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4792 osrfLogError( OSRF_LOG_MARK,
4793 "%s: Virtual field \"%s\" in ORDER BY clause",
4796 osrfAppSessionStatus(
4798 OSRF_STATUS_INTERNALSERVERERROR,
4799 "osrfMethodException",
4801 "Virtual field in ORDER BY clause -- "
4802 "see error log for more details"
4804 jsonIteratorFree( class_itr );
4805 buffer_free( order_buf );
4807 buffer_free( group_buf );
4808 buffer_free( sql_buf );
4809 if( defaultselhash )
4810 jsonObjectFree( defaultselhash );
4815 OSRF_BUFFER_ADD( order_buf, ", " );
4817 order_buf = buffer_init( 128 );
4819 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4823 // IT'S THE OOOOOOOOOOOLD STYLE!
4825 osrfLogError( OSRF_LOG_MARK,
4826 "%s: Possible SQL injection attempt; direct order by is not allowed",
4829 osrfAppSessionStatus(
4831 OSRF_STATUS_INTERNALSERVERERROR,
4832 "osrfMethodException",
4834 "Severe query error -- see error log for more details"
4839 buffer_free( group_buf );
4840 buffer_free( order_buf );
4841 buffer_free( sql_buf );
4842 if( defaultselhash )
4843 jsonObjectFree( defaultselhash );
4844 jsonIteratorFree( class_itr );
4848 jsonIteratorFree( class_itr );
4850 order_by_list = buffer_release( order_buf );
4852 osrfLogError( OSRF_LOG_MARK,
4853 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4854 modulename, json_type( order_hash->type ) );
4856 osrfAppSessionStatus(
4858 OSRF_STATUS_INTERNALSERVERERROR,
4859 "osrfMethodException",
4861 "Malformed ORDER BY clause -- see error log for more details"
4864 buffer_free( group_buf );
4865 buffer_free( sql_buf );
4866 if( defaultselhash )
4867 jsonObjectFree( defaultselhash );
4872 string = buffer_release( group_buf );
4874 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4875 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4876 OSRF_BUFFER_ADD( sql_buf, string );
4881 if( having_buf && *having_buf ) {
4882 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4883 OSRF_BUFFER_ADD( sql_buf, having_buf );
4887 if( order_by_list ) {
4889 if( *order_by_list ) {
4890 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4891 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4894 free( order_by_list );
4898 const char* str = jsonObjectGetString( limit );
4899 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4903 const char* str = jsonObjectGetString( offset );
4904 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4907 if( !(flags & SUBSELECT) )
4908 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4910 if( defaultselhash )
4911 jsonObjectFree( defaultselhash );
4913 return buffer_release( sql_buf );
4915 } // end of SELECT()
4918 @brief Build a list of ORDER BY expressions.
4919 @param ctx Pointer to the method context.
4920 @param order_array Pointer to a JSON_ARRAY of field specifications.
4921 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
4922 Each expression may be either a column reference or a function call whose first parameter
4923 is a column reference.
4925 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
4926 It may optionally include entries for "direction" and/or "transform".
4928 The calling code is responsible for freeing the returned string.
4930 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
4931 if( ! order_array ) {
4932 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
4935 osrfAppSessionStatus(
4937 OSRF_STATUS_INTERNALSERVERERROR,
4938 "osrfMethodException",
4940 "Logic error: ORDER BY clause expected, not found; "
4941 "see error log for more details"
4944 } else if( order_array->type != JSON_ARRAY ) {
4945 osrfLogError( OSRF_LOG_MARK,
4946 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
4948 osrfAppSessionStatus(
4950 OSRF_STATUS_INTERNALSERVERERROR,
4951 "osrfMethodException",
4953 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
4957 growing_buffer* order_buf = buffer_init( 128 );
4958 int first = 1; // boolean
4960 jsonObject* order_spec;
4961 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
4963 if( JSON_HASH != order_spec->type ) {
4964 osrfLogError( OSRF_LOG_MARK,
4965 "%s: Malformed field specification in ORDER BY clause; "
4966 "expected JSON_HASH, found %s",
4967 modulename, json_type( order_spec->type ) );
4969 osrfAppSessionStatus(
4971 OSRF_STATUS_INTERNALSERVERERROR,
4972 "osrfMethodException",
4974 "Malformed ORDER BY clause -- see error log for more details"
4976 buffer_free( order_buf );
4980 const char* class_alias =
4981 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
4983 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
4985 jsonObject* compare_to = jsonObjectGetKeyConst( order_spec, "compare" );
4987 if( !field || !class_alias ) {
4988 osrfLogError( OSRF_LOG_MARK,
4989 "%s: Missing class or field name in field specification of ORDER BY clause",
4992 osrfAppSessionStatus(
4994 OSRF_STATUS_INTERNALSERVERERROR,
4995 "osrfMethodException",
4997 "Malformed ORDER BY clause -- see error log for more details"
4999 buffer_free( order_buf );
5003 const ClassInfo* order_class_info = search_alias( class_alias );
5004 if( ! order_class_info ) {
5005 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5006 "not in FROM clause, skipping it", modulename, class_alias );
5010 // Add a separating comma, except at the beginning
5014 OSRF_BUFFER_ADD( order_buf, ", " );
5016 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5018 osrfLogError( OSRF_LOG_MARK,
5019 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5020 modulename, class_alias, field );
5022 osrfAppSessionStatus(
5024 OSRF_STATUS_INTERNALSERVERERROR,
5025 "osrfMethodException",
5027 "Invalid field referenced in ORDER BY clause -- "
5028 "see error log for more details"
5032 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5033 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5034 modulename, field );
5036 osrfAppSessionStatus(
5038 OSRF_STATUS_INTERNALSERVERERROR,
5039 "osrfMethodException",
5041 "Virtual field in ORDER BY clause -- see error log for more details"
5043 buffer_free( order_buf );
5047 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5048 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5049 if( ! transform_str ) {
5051 osrfAppSessionStatus(
5053 OSRF_STATUS_INTERNALSERVERERROR,
5054 "osrfMethodException",
5056 "Severe query error in ORDER BY clause -- "
5057 "see error log for more details"
5059 buffer_free( order_buf );
5063 OSRF_BUFFER_ADD( order_buf, transform_str );
5064 free( transform_str );
5065 } else if( compare_to ) {
5066 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5067 if( ! compare_str ) {
5069 osrfAppSessionStatus(
5071 OSRF_STATUS_INTERNALSERVERERROR,
5072 "osrfMethodException",
5074 "Severe query error in ORDER BY clause -- "
5075 "see error log for more details"
5077 buffer_free( order_buf );
5081 buffer_fadd( order_buf, "(%s)", compare_str );
5082 free( compare_str );
5085 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5087 const char* direction =
5088 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5090 if( direction[ 0 ] || 'D' == direction[ 0 ] )
5091 OSRF_BUFFER_ADD( order_buf, " DESC" );
5093 OSRF_BUFFER_ADD( order_buf, " ASC" );
5097 return buffer_release( order_buf );
5101 @brief Build a SELECT statement.
5102 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5103 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5104 @param meta Pointer to the class metadata for the core class.
5105 @param ctx Pointer to the method context.
5106 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5108 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5109 "order_by", "limit", and "offset".
5111 The SELECT statements built here are distinct from those built for the json_query method.
5113 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5114 osrfHash* meta, osrfMethodContext* ctx ) {
5116 const char* locale = osrf_message_get_last_locale();
5118 osrfHash* fields = osrfHashGet( meta, "fields" );
5119 const char* core_class = osrfHashGet( meta, "classname" );
5121 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5123 jsonObject* selhash = NULL;
5124 jsonObject* defaultselhash = NULL;
5126 growing_buffer* sql_buf = buffer_init( 128 );
5127 growing_buffer* select_buf = buffer_init( 128 );
5129 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5130 defaultselhash = jsonNewObjectType( JSON_HASH );
5131 selhash = defaultselhash;
5134 // If there's no SELECT list for the core class, build one
5135 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5136 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5138 // Add every non-virtual field to the field list
5139 osrfHash* field_def = NULL;
5140 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5141 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5142 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5143 const char* field = osrfHashIteratorKey( field_itr );
5144 jsonObjectPush( field_list, jsonNewObject( field ) );
5147 osrfHashIteratorFree( field_itr );
5148 jsonObjectSetKey( selhash, core_class, field_list );
5151 // Build a list of columns for the SELECT clause
5153 const jsonObject* snode = NULL;
5154 jsonIterator* class_itr = jsonNewIterator( selhash );
5155 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5157 // If the class isn't in the IDL, ignore it
5158 const char* cname = class_itr->key;
5159 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5163 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5164 if( strcmp( core_class, class_itr->key )) {
5168 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5169 if( !found->size ) {
5170 jsonObjectFree( found );
5174 jsonObjectFree( found );
5177 const jsonObject* node = NULL;
5178 jsonIterator* select_itr = jsonNewIterator( snode );
5179 while( (node = jsonIteratorNext( select_itr )) ) {
5180 const char* item_str = jsonObjectGetString( node );
5181 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5182 char* fname = osrfHashGet( field, "name" );
5190 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5195 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5196 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5199 i18n = osrfHashGet( field, "i18n" );
5201 if( str_is_true( i18n ) ) {
5202 char* pkey = osrfHashGet( idlClass, "primarykey" );
5203 char* tname = osrfHashGet( idlClass, "tablename" );
5205 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5206 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5207 tname, cname, fname, pkey, cname, pkey, locale, fname );
5209 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5212 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5216 jsonIteratorFree( select_itr );
5219 jsonIteratorFree( class_itr );
5221 char* col_list = buffer_release( select_buf );
5222 char* table = oilsGetRelation( meta );
5224 table = strdup( "(null)" );
5226 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5230 // Clear the query stack (as a fail-safe precaution against possible
5231 // leftover garbage); then push the first query frame onto the stack.
5232 clear_query_stack();
5234 if( add_query_core( NULL, core_class ) ) {
5236 osrfAppSessionStatus(
5238 OSRF_STATUS_INTERNALSERVERERROR,
5239 "osrfMethodException",
5241 "Unable to build query frame for core class"
5243 buffer_free( sql_buf );
5244 if( defaultselhash )
5245 jsonObjectFree( defaultselhash );
5249 // Add the JOIN clauses, if any
5251 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5252 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5253 OSRF_BUFFER_ADD( sql_buf, join_clause );
5254 free( join_clause );
5257 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5258 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5260 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5262 // Add the conditions in the WHERE clause
5263 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5265 osrfAppSessionStatus(
5267 OSRF_STATUS_INTERNALSERVERERROR,
5268 "osrfMethodException",
5270 "Severe query error -- see error log for more details"
5272 buffer_free( sql_buf );
5273 if( defaultselhash )
5274 jsonObjectFree( defaultselhash );
5275 clear_query_stack();
5278 buffer_add( sql_buf, pred );
5282 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5283 if( rest_of_query ) {
5284 const jsonObject* order_by = NULL;
5285 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5287 char* order_by_list = NULL;
5289 if( JSON_ARRAY == order_by->type ) {
5290 order_by_list = buildOrderByFromArray( ctx, order_by );
5291 if( !order_by_list ) {
5292 buffer_free( sql_buf );
5293 if( defaultselhash )
5294 jsonObjectFree( defaultselhash );
5295 clear_query_stack();
5298 } else if( JSON_HASH == order_by->type ) {
5299 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5300 // and build a list of ORDER BY expressions.
5301 growing_buffer* order_buf = buffer_init( 128 );
5303 jsonIterator* class_itr = jsonNewIterator( order_by );
5304 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5306 ClassInfo* order_class_info = search_alias( class_itr->key );
5307 if( ! order_class_info )
5308 continue; // class not referenced by FROM clause? Ignore it.
5310 if( JSON_HASH == snode->type ) {
5312 // If the data for the current class is a JSON_HASH, then it is
5313 // keyed on field name.
5315 const jsonObject* onode = NULL;
5316 jsonIterator* order_itr = jsonNewIterator( snode );
5317 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5319 osrfHash* field_def = osrfHashGet(
5320 order_class_info->fields, order_itr->key );
5322 continue; // Field not defined in IDL? Ignore it.
5323 if( str_is_true( osrfHashGet( field_def, "virtual")))
5324 continue; // Field is virtual? Ignore it.
5326 char* field_str = NULL;
5327 char* direction = NULL;
5328 if( onode->type == JSON_HASH ) {
5329 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5330 field_str = searchFieldTransform(
5331 class_itr->key, field_def, onode );
5333 osrfAppSessionStatus(
5335 OSRF_STATUS_INTERNALSERVERERROR,
5336 "osrfMethodException",
5338 "Severe query error in ORDER BY clause -- "
5339 "see error log for more details"
5341 jsonIteratorFree( order_itr );
5342 jsonIteratorFree( class_itr );
5343 buffer_free( order_buf );
5344 buffer_free( sql_buf );
5345 if( defaultselhash )
5346 jsonObjectFree( defaultselhash );
5347 clear_query_stack();
5351 growing_buffer* field_buf = buffer_init( 16 );
5352 buffer_fadd( field_buf, "\"%s\".%s",
5353 class_itr->key, order_itr->key );
5354 field_str = buffer_release( field_buf );
5357 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5358 const char* dir = jsonObjectGetString( order_by );
5359 if(!strncasecmp( dir, "d", 1 )) {
5360 direction = " DESC";
5364 field_str = strdup( order_itr->key );
5365 const char* dir = jsonObjectGetString( onode );
5366 if( !strncasecmp( dir, "d", 1 )) {
5367 direction = " DESC";
5376 buffer_add( order_buf, ", " );
5379 buffer_add( order_buf, field_str );
5383 buffer_add( order_buf, direction );
5385 } // end while; looping over ORDER BY expressions
5387 jsonIteratorFree( order_itr );
5389 } else if( JSON_STRING == snode->type ) {
5390 // We expect a comma-separated list of sort fields.
5391 const char* str = jsonObjectGetString( snode );
5392 if( strchr( str, ';' )) {
5393 // No semicolons allowed. It is theoretically possible for a
5394 // legitimate semicolon to occur within quotes, but it's not likely
5395 // to occur in practice in the context of an ORDER BY list.
5396 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5397 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5399 osrfAppSessionStatus(
5401 OSRF_STATUS_INTERNALSERVERERROR,
5402 "osrfMethodException",
5404 "Possible attempt at SOL injection -- "
5405 "semicolon found in ORDER BY list"
5408 jsonIteratorFree( class_itr );
5409 buffer_free( order_buf );
5410 buffer_free( sql_buf );
5411 if( defaultselhash )
5412 jsonObjectFree( defaultselhash );
5413 clear_query_stack();
5416 buffer_add( order_buf, str );
5420 } // end while; looping over order_by classes
5422 jsonIteratorFree( class_itr );
5423 order_by_list = buffer_release( order_buf );
5426 osrfLogWarning( OSRF_LOG_MARK,
5427 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5428 "no ORDER BY generated" );
5431 if( order_by_list && *order_by_list ) {
5432 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5433 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5436 free( order_by_list );
5439 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5441 const char* str = jsonObjectGetString( limit );
5449 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5451 const char* str = jsonObjectGetString( offset );
5460 if( defaultselhash )
5461 jsonObjectFree( defaultselhash );
5462 clear_query_stack();
5464 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5465 return buffer_release( sql_buf );
5468 int doJSONSearch ( osrfMethodContext* ctx ) {
5469 if(osrfMethodVerifyContext( ctx )) {
5470 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5474 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5478 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5482 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5483 flags |= SELECT_DISTINCT;
5485 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5486 flags |= DISABLE_I18N;
5488 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5489 clear_query_stack(); // a possibly needless precaution
5490 char* sql = buildQuery( ctx, hash, flags );
5491 clear_query_stack();
5498 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5501 dbhandle = writehandle;
5503 dbi_result result = dbi_conn_query( dbhandle, sql );
5506 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5508 if( dbi_result_first_row( result )) {
5509 /* JSONify the result */
5510 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5513 jsonObject* return_val = oilsMakeJSONFromResult( result );
5514 osrfAppRespond( ctx, return_val );
5515 jsonObjectFree( return_val );
5516 } while( dbi_result_next_row( result ));
5519 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5522 osrfAppRespondComplete( ctx, NULL );
5524 /* clean up the query */
5525 dbi_result_free( result );
5530 int errnum = dbi_conn_error( dbhandle, &msg );
5531 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5532 modulename, sql, errnum, msg ? msg : "(No description available)" );
5533 osrfAppSessionStatus(
5535 OSRF_STATUS_INTERNALSERVERERROR,
5536 "osrfMethodException",
5538 "Severe query error -- see error log for more details"
5540 if( !oilsIsDBConnected( dbhandle ))
5541 osrfAppSessionPanic( ctx->session );
5548 // The last parameter, err, is used to report an error condition by updating an int owned by
5549 // the calling code.
5551 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5552 // It is the responsibility of the calling code to initialize *err before the
5553 // call, so that it will be able to make sense of the result.
5555 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5556 // redundant anyway.
5557 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5558 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5561 dbhandle = writehandle;
5563 char* core_class = osrfHashGet( class_meta, "classname" );
5564 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5566 char* pkey = osrfHashGet( class_meta, "primarykey" );
5568 if (!ctx->session->userData)
5569 (void) initSessionCache( ctx );
5571 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5572 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5573 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5574 int has_controller = osrfStringArrayContains(osrfHashGet(class_meta, "controller"), modulename);
5576 int i_respond_directly = 0;
5577 int flesh_depth = 0;
5579 // XXX This can be redundant with another instance of the same test that happens
5580 // within the functions that call doFieldmapperSearch(), but we have it here to
5581 // prevent any non-pcrud-controlled classes from being fleshed on.
5583 // TODO To avoid redundancy, move this block to right before we recurse,
5584 // and change the class we're checking to the one we're /about/ to search for,
5585 // not the one we're currently searching for.
5587 (!has_controller && !enforce_pcrud) // cstore client-level case: we require the controller, period
5588 || (!has_controller && enforce_pcrud && need_to_verify) // pcrud case: we require the controller in need_to_verify mode
5590 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
5591 modulename, core_class);
5592 return jsonNewObjectType( JSON_ARRAY ); /* empty */
5595 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5597 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5602 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5604 dbi_result result = dbi_conn_query( dbhandle, sql );
5605 if( NULL == result ) {
5607 int errnum = dbi_conn_error( dbhandle, &msg );
5608 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5609 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5610 msg ? msg : "(No description available)" );
5611 if( !oilsIsDBConnected( dbhandle ))
5612 osrfAppSessionPanic( ctx->session );
5613 osrfAppSessionStatus(
5615 OSRF_STATUS_INTERNALSERVERERROR,
5616 "osrfMethodException",
5618 "Severe query error -- see error log for more details"
5625 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5628 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5629 jsonObject* row_obj = NULL;
5631 // The following two steps are for verifyObjectPCRUD()'s benefit.
5632 // 1. get the flesh depth
5633 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5635 flesh_depth = (int) jsonObjectGetNumber( _tmp );
5636 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5637 flesh_depth = max_flesh_depth;
5640 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
5641 // over the whole life of this request. This means if we've already set
5642 // up a rs_size_req_%d, do nothing.
5643 // a. Incidentally, we can also use this opportunity to set i_respond_directly
5644 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
5645 if( !rs_size ) { // pointer null, so value not set in hash
5646 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
5647 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
5649 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
5650 unsigned long long result_count = dbi_result_get_numrows( result );
5651 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
5652 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
5655 if( dbi_result_first_row( result )) {
5657 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5658 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5659 // eliminate the duplicates.
5660 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5661 osrfHash* dedup = osrfNewHash();
5663 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5664 char* pkey_val = oilsFMGetString( row_obj, pkey );
5665 if( osrfHashGet( dedup, pkey_val ) ) {
5666 jsonObjectFree( row_obj );
5669 if( !enforce_pcrud || !need_to_verify ||
5670 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
5671 osrfHashSet( dedup, pkey_val, pkey_val );
5672 jsonObjectPush( res_list, row_obj );
5675 } while( dbi_result_next_row( result ));
5676 osrfHashFree( dedup );
5679 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5683 /* clean up the query */
5684 dbi_result_free( result );
5687 // If we're asked to flesh, and there's anything to flesh, then flesh it
5688 // (formerly we would skip fleshing if in pcrud mode, but now we support
5689 // fleshing even in PCRUD).
5690 if( res_list->size ) {
5691 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
5692 jsonObject* flesh_fields;
5693 jsonObject* flesh_blob = NULL;
5694 osrfStringArray* link_fields = NULL;
5695 osrfHash* links = NULL;
5699 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
5700 if( temp_blob && flesh_depth > 0 ) {
5702 flesh_blob = jsonObjectClone( temp_blob );
5703 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
5705 links = osrfHashGet( class_meta, "links" );
5707 // Make an osrfStringArray of the names of fields to be fleshed
5708 if( flesh_fields ) {
5709 if( flesh_fields->size == 1 ) {
5710 const char* _t = jsonObjectGetString(
5711 jsonObjectGetIndex( flesh_fields, 0 ) );
5712 if( !strcmp( _t, "*" ))
5713 link_fields = osrfHashKeys( links );
5716 if( !link_fields ) {
5718 link_fields = osrfNewStringArray( 1 );
5719 jsonIterator* _i = jsonNewIterator( flesh_fields );
5720 while ((_f = jsonIteratorNext( _i ))) {
5721 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5723 jsonIteratorFree( _i );
5726 want_flesh = link_fields ? 1 : 0;
5730 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5732 // Iterate over the JSON_ARRAY of rows
5734 unsigned long res_idx = 0;
5735 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5738 const char* link_field;
5740 // Iterate over the list of fleshable fields
5742 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5744 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5746 osrfHash* kid_link = osrfHashGet( links, link_field );
5748 continue; // Not a link field; skip it
5750 osrfHash* field = osrfHashGet( fields, link_field );
5752 continue; // Not a field at all; skip it (IDL is ill-formed)
5754 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5755 osrfHashGet( kid_link, "class" ));
5757 continue; // The class it links to doesn't exist; skip it
5759 const char* reltype = osrfHashGet( kid_link, "reltype" );
5761 continue; // No reltype; skip it (IDL is ill-formed)
5763 osrfHash* value_field = field;
5765 if( !strcmp( reltype, "has_many" )
5766 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5767 value_field = osrfHashGet(
5768 fields, osrfHashGet( class_meta, "primarykey" ) );
5771 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5773 if( link_map->size > 0 ) {
5774 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5777 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5782 osrfHashGet( kid_link, "class" ),
5789 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5790 osrfHashGet( kid_link, "field" ),
5791 osrfHashGet( kid_link, "class" ),
5792 osrfHashGet( kid_link, "key" ),
5793 osrfHashGet( kid_link, "reltype" )
5796 const char* search_key = jsonObjectGetString(
5797 jsonObjectGetIndex( cur,
5798 atoi( osrfHashGet( value_field, "array_position" ) )
5803 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5807 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5809 // construct WHERE clause
5810 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5813 osrfHashGet( kid_link, "key" ),
5814 jsonNewObject( search_key )
5817 // construct the rest of the query, mostly
5818 // by copying pieces of the previous level of query
5819 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5820 jsonObjectSetKey( rest_of_query, "flesh",
5821 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5825 jsonObjectSetKey( rest_of_query, "flesh_fields",
5826 jsonObjectClone( flesh_blob ));
5828 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5829 jsonObjectSetKey( rest_of_query, "order_by",
5830 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5834 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5835 jsonObjectSetKey( rest_of_query, "select",
5836 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5840 // do the query, recursively, to expand the fleshable field
5841 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5842 where_clause, rest_of_query, err );
5844 jsonObjectFree( where_clause );
5845 jsonObjectFree( rest_of_query );
5848 osrfStringArrayFree( link_fields );
5849 jsonObjectFree( res_list );
5850 jsonObjectFree( flesh_blob );
5854 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5855 osrfHashGet( kid_link, "class" ), kids->size );
5857 // Traverse the result set
5858 jsonObject* X = NULL;
5859 if( link_map->size > 0 && kids->size > 0 ) {
5861 kids = jsonNewObjectType( JSON_ARRAY );
5863 jsonObject* _k_node;
5864 unsigned long res_idx = 0;
5865 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5871 (unsigned long) atoi(
5877 osrfHashGet( kid_link, "class" )
5881 osrfStringArrayGetString( link_map, 0 )
5889 } // end while loop traversing X
5892 if (kids->size > 0) {
5894 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5895 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
5897 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5898 osrfHashGet( kid_link, "field" ));
5901 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5902 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5907 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5909 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5910 osrfHashGet( kid_link, "field" ) );
5913 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5914 jsonObjectClone( kids )
5919 jsonObjectFree( kids );
5923 jsonObjectFree( kids );
5925 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5926 osrfHashGet( kid_link, "field" ) );
5927 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5929 } // end while loop traversing list of fleshable fields
5932 if( i_respond_directly ) {
5933 if ( *methodtype == 'i' ) {
5934 osrfAppRespond( ctx,
5935 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
5937 osrfAppRespond( ctx, cur );
5940 } // end while loop traversing res_list
5941 jsonObjectFree( flesh_blob );
5942 osrfStringArrayFree( link_fields );
5945 if( i_respond_directly ) {
5946 jsonObjectFree( res_list );
5947 return jsonNewObjectType( JSON_ARRAY );
5954 int doUpdate( osrfMethodContext* ctx ) {
5955 if( osrfMethodVerifyContext( ctx )) {
5956 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5961 timeout_needs_resetting = 1;
5963 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5965 jsonObject* target = NULL;
5967 target = jsonObjectGetIndex( ctx->params, 1 );
5969 target = jsonObjectGetIndex( ctx->params, 0 );
5971 if(!verifyObjectClass( ctx, target )) {
5972 osrfAppRespondComplete( ctx, NULL );
5976 if( getXactId( ctx ) == NULL ) {
5977 osrfAppSessionStatus(
5979 OSRF_STATUS_BADREQUEST,
5980 "osrfMethodException",
5982 "No active transaction -- required for UPDATE"
5984 osrfAppRespondComplete( ctx, NULL );
5988 // The following test is harmless but redundant. If a class is
5989 // readonly, we don't register an update method for it.
5990 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5991 osrfAppSessionStatus(
5993 OSRF_STATUS_BADREQUEST,
5994 "osrfMethodException",
5996 "Cannot UPDATE readonly class"
5998 osrfAppRespondComplete( ctx, NULL );
6002 const char* trans_id = getXactId( ctx );
6004 // Set the last_xact_id
6005 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6007 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6008 trans_id, target->classname, index );
6009 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6012 char* pkey = osrfHashGet( meta, "primarykey" );
6013 osrfHash* fields = osrfHashGet( meta, "fields" );
6015 char* id = oilsFMGetString( target, pkey );
6019 "%s updating %s object with %s = %s",
6021 osrfHashGet( meta, "fieldmapper" ),
6026 dbhandle = writehandle;
6027 growing_buffer* sql = buffer_init( 128 );
6028 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6031 osrfHash* field_def = NULL;
6032 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6033 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6035 // Skip virtual fields, and the primary key
6036 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6039 const char* field_name = osrfHashIteratorKey( field_itr );
6040 if( ! strcmp( field_name, pkey ) )
6043 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6045 int value_is_numeric = 0; // boolean
6047 if( field_object && field_object->classname ) {
6048 value = oilsFMGetString(
6050 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6052 } else if( field_object && JSON_BOOL == field_object->type ) {
6053 if( jsonBoolIsTrue( field_object ) )
6054 value = strdup( "t" );
6056 value = strdup( "f" );
6058 value = jsonObjectToSimpleString( field_object );
6059 if( field_object && JSON_NUMBER == field_object->type )
6060 value_is_numeric = 1;
6063 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6064 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6066 if( !field_object || field_object->type == JSON_NULL ) {
6067 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6068 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6072 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6073 buffer_fadd( sql, " %s = NULL", field_name );
6076 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6080 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6082 const char* numtype = get_datatype( field_def );
6083 if( !strncmp( numtype, "INT", 3 ) ) {
6084 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6085 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6086 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6088 // Must really be intended as a string, so quote it
6089 if( dbi_conn_quote_string( dbhandle, &value )) {
6090 buffer_fadd( sql, " %s = %s", field_name, value );
6092 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6093 modulename, value );
6094 osrfAppSessionStatus(
6096 OSRF_STATUS_INTERNALSERVERERROR,
6097 "osrfMethodException",
6099 "Error quoting string -- please see the error log for more details"
6103 osrfHashIteratorFree( field_itr );
6105 osrfAppRespondComplete( ctx, NULL );
6110 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6113 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6117 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6118 buffer_fadd( sql, " %s = %s", field_name, value );
6120 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6121 osrfAppSessionStatus(
6123 OSRF_STATUS_INTERNALSERVERERROR,
6124 "osrfMethodException",
6126 "Error quoting string -- please see the error log for more details"
6130 osrfHashIteratorFree( field_itr );
6132 osrfAppRespondComplete( ctx, NULL );
6141 osrfHashIteratorFree( field_itr );
6143 jsonObject* obj = jsonNewObject( id );
6145 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6146 dbi_conn_quote_string( dbhandle, &id );
6148 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6150 char* query = buffer_release( sql );
6151 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6153 dbi_result result = dbi_conn_query( dbhandle, query );
6158 jsonObjectFree( obj );
6159 obj = jsonNewObject( NULL );
6161 int errnum = dbi_conn_error( dbhandle, &msg );
6164 "%s ERROR updating %s object with %s = %s: %d %s",
6166 osrfHashGet( meta, "fieldmapper" ),
6170 msg ? msg : "(No description available)"
6172 osrfAppSessionStatus(
6174 OSRF_STATUS_INTERNALSERVERERROR,
6175 "osrfMethodException",
6177 "Error in updating a row -- please see the error log for more details"
6179 if( !oilsIsDBConnected( dbhandle ))
6180 osrfAppSessionPanic( ctx->session );
6183 dbi_result_free( result );
6186 osrfAppRespondComplete( ctx, obj );
6187 jsonObjectFree( obj );
6191 int doDelete( osrfMethodContext* ctx ) {
6192 if( osrfMethodVerifyContext( ctx )) {
6193 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6198 timeout_needs_resetting = 1;
6200 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6202 if( getXactId( ctx ) == NULL ) {
6203 osrfAppSessionStatus(
6205 OSRF_STATUS_BADREQUEST,
6206 "osrfMethodException",
6208 "No active transaction -- required for DELETE"
6210 osrfAppRespondComplete( ctx, NULL );
6214 // The following test is harmless but redundant. If a class is
6215 // readonly, we don't register a delete method for it.
6216 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6217 osrfAppSessionStatus(
6219 OSRF_STATUS_BADREQUEST,
6220 "osrfMethodException",
6222 "Cannot DELETE readonly class"
6224 osrfAppRespondComplete( ctx, NULL );
6228 dbhandle = writehandle;
6230 char* pkey = osrfHashGet( meta, "primarykey" );
6237 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6238 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6239 osrfAppRespondComplete( ctx, NULL );
6243 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6245 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6246 osrfAppRespondComplete( ctx, NULL );
6249 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6254 "%s deleting %s object with %s = %s",
6256 osrfHashGet( meta, "fieldmapper" ),
6261 jsonObject* obj = jsonNewObject( id );
6263 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6264 dbi_conn_quote_string( writehandle, &id );
6266 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6267 osrfHashGet( meta, "tablename" ), pkey, id );
6272 jsonObjectFree( obj );
6273 obj = jsonNewObject( NULL );
6275 int errnum = dbi_conn_error( writehandle, &msg );
6278 "%s ERROR deleting %s object with %s = %s: %d %s",
6280 osrfHashGet( meta, "fieldmapper" ),
6284 msg ? msg : "(No description available)"
6286 osrfAppSessionStatus(
6288 OSRF_STATUS_INTERNALSERVERERROR,
6289 "osrfMethodException",
6291 "Error in deleting a row -- please see the error log for more details"
6293 if( !oilsIsDBConnected( writehandle ))
6294 osrfAppSessionPanic( ctx->session );
6296 dbi_result_free( result );
6300 osrfAppRespondComplete( ctx, obj );
6301 jsonObjectFree( obj );
6306 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6307 @param result An iterator for a result set; we only look at the current row.
6308 @param @meta Pointer to the class metadata for the core class.
6309 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6311 If a column is not defined in the IDL, or if it has no array_position defined for it in
6312 the IDL, or if it is defined as virtual, ignore it.
6314 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6315 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6316 array_position in the IDL.
6318 A field defined in the IDL but not represented in the returned row will leave a hole
6319 in the JSON_ARRAY. In effect it will be treated as a null value.
6321 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6322 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6323 classname corresponding to the @a meta argument.
6325 The calling code is responsible for freeing the the resulting jsonObject by calling
6328 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6329 if( !( result && meta )) return NULL;
6331 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6332 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6333 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6335 osrfHash* fields = osrfHashGet( meta, "fields" );
6337 int columnIndex = 1;
6338 const char* columnName;
6340 /* cycle through the columns in the row returned from the database */
6341 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6343 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6345 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6347 /* determine the field type and storage attributes */
6348 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6349 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6351 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6352 // or if it has no sequence number there, or if it's virtual, skip it.
6353 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6356 if( str_is_true( osrfHashGet( _f, "virtual" )))
6357 continue; // skip this column: IDL says it's virtual
6359 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6360 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6361 continue; // since we assign sequence numbers dynamically as we load the IDL.
6363 fmIndex = atoi( pos );
6364 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6366 continue; // This field is not defined in the IDL
6369 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6370 // sequence number from the IDL (which is likely to be different from the sequence
6371 // of columns in the SELECT clause).
6372 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6373 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6378 case DBI_TYPE_INTEGER :
6380 if( attr & DBI_INTEGER_SIZE8 )
6381 jsonObjectSetIndex( object, fmIndex,
6382 jsonNewNumberObject(
6383 dbi_result_get_longlong_idx( result, columnIndex )));
6385 jsonObjectSetIndex( object, fmIndex,
6386 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6390 case DBI_TYPE_DECIMAL :
6391 jsonObjectSetIndex( object, fmIndex,
6392 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6395 case DBI_TYPE_STRING :
6400 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6405 case DBI_TYPE_DATETIME : {
6407 char dt_string[ 256 ] = "";
6410 // Fetch the date column as a time_t
6411 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6413 // Translate the time_t to a human-readable string
6414 if( !( attr & DBI_DATETIME_DATE )) {
6415 gmtime_r( &_tmp_dt, &gmdt );
6416 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6417 } else if( !( attr & DBI_DATETIME_TIME )) {
6418 localtime_r( &_tmp_dt, &gmdt );
6419 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6421 localtime_r( &_tmp_dt, &gmdt );
6422 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6425 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6429 case DBI_TYPE_BINARY :
6430 osrfLogError( OSRF_LOG_MARK,
6431 "Can't do binary at column %s : index %d", columnName, columnIndex );
6440 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6441 if( !result ) return NULL;
6443 jsonObject* object = jsonNewObject( NULL );
6446 char dt_string[ 256 ];
6450 int columnIndex = 1;
6452 unsigned short type;
6453 const char* columnName;
6455 /* cycle through the column list */
6456 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6458 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6460 fmIndex = -1; // reset the position
6462 /* determine the field type and storage attributes */
6463 type = dbi_result_get_field_type_idx( result, columnIndex );
6464 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6466 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6467 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6472 case DBI_TYPE_INTEGER :
6474 if( attr & DBI_INTEGER_SIZE8 )
6475 jsonObjectSetKey( object, columnName,
6476 jsonNewNumberObject( dbi_result_get_longlong_idx(
6477 result, columnIndex )) );
6479 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6480 dbi_result_get_int_idx( result, columnIndex )) );
6483 case DBI_TYPE_DECIMAL :
6484 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6485 dbi_result_get_double_idx( result, columnIndex )) );
6488 case DBI_TYPE_STRING :
6489 jsonObjectSetKey( object, columnName,
6490 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6493 case DBI_TYPE_DATETIME :
6495 memset( dt_string, '\0', sizeof( dt_string ));
6496 memset( &gmdt, '\0', sizeof( gmdt ));
6498 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6500 if( !( attr & DBI_DATETIME_DATE )) {
6501 gmtime_r( &_tmp_dt, &gmdt );
6502 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6503 } else if( !( attr & DBI_DATETIME_TIME )) {
6504 localtime_r( &_tmp_dt, &gmdt );
6505 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6507 localtime_r( &_tmp_dt, &gmdt );
6508 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6511 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6514 case DBI_TYPE_BINARY :
6515 osrfLogError( OSRF_LOG_MARK,
6516 "Can't do binary at column %s : index %d", columnName, columnIndex );
6520 } // end while loop traversing result
6525 // Interpret a string as true or false
6526 int str_is_true( const char* str ) {
6527 if( NULL == str || strcasecmp( str, "true" ) )
6533 // Interpret a jsonObject as true or false
6534 static int obj_is_true( const jsonObject* obj ) {
6537 else switch( obj->type )
6545 if( strcasecmp( obj->value.s, "true" ) )
6549 case JSON_NUMBER : // Support 1/0 for perl's sake
6550 if( jsonObjectGetNumber( obj ) == 1.0 )
6559 // Translate a numeric code into a text string identifying a type of
6560 // jsonObject. To be used for building error messages.
6561 static const char* json_type( int code ) {
6567 return "JSON_ARRAY";
6569 return "JSON_STRING";
6571 return "JSON_NUMBER";
6577 return "(unrecognized)";
6581 // Extract the "primitive" attribute from an IDL field definition.
6582 // If we haven't initialized the app, then we must be running in
6583 // some kind of testbed. In that case, default to "string".
6584 static const char* get_primitive( osrfHash* field ) {
6585 const char* s = osrfHashGet( field, "primitive" );
6587 if( child_initialized )
6590 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6592 osrfHashGet( field, "name" )
6600 // Extract the "datatype" attribute from an IDL field definition.
6601 // If we haven't initialized the app, then we must be running in
6602 // some kind of testbed. In that case, default to to NUMERIC,
6603 // since we look at the datatype only for numbers.
6604 static const char* get_datatype( osrfHash* field ) {
6605 const char* s = osrfHashGet( field, "datatype" );
6607 if( child_initialized )
6610 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6612 osrfHashGet( field, "name" )
6621 @brief Determine whether a string is potentially a valid SQL identifier.
6622 @param s The identifier to be tested.
6623 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6625 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6626 need to follow all the rules exactly, such as requiring that the first character not
6629 We allow leading and trailing white space. In between, we do not allow punctuation
6630 (except for underscores and dollar signs), control characters, or embedded white space.
6632 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6633 for the foreseeable future such quoted identifiers are not likely to be an issue.
6635 int is_identifier( const char* s) {
6639 // Skip leading white space
6640 while( isspace( (unsigned char) *s ) )
6644 return 0; // Nothing but white space? Not okay.
6646 // Check each character until we reach white space or
6647 // end-of-string. Letters, digits, underscores, and
6648 // dollar signs are okay. With the exception of periods
6649 // (as in schema.identifier), control characters and other
6650 // punctuation characters are not okay. Anything else
6651 // is okay -- it could for example be part of a multibyte
6652 // UTF8 character such as a letter with diacritical marks,
6653 // and those are allowed.
6655 if( isalnum( (unsigned char) *s )
6659 ; // Fine; keep going
6660 else if( ispunct( (unsigned char) *s )
6661 || iscntrl( (unsigned char) *s ) )
6664 } while( *s && ! isspace( (unsigned char) *s ) );
6666 // If we found any white space in the above loop,
6667 // the rest had better be all white space.
6669 while( isspace( (unsigned char) *s ) )
6673 return 0; // White space was embedded within non-white space
6679 @brief Determine whether to accept a character string as a comparison operator.
6680 @param op The candidate comparison operator.
6681 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6683 We don't validate the operator for real. We just make sure that it doesn't contain
6684 any semicolons or white space (with special exceptions for a few specific operators).
6685 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6686 space but it's still not a valid operator, then the database will complain.
6688 Another approach would be to compare the string against a short list of approved operators.
6689 We don't do that because we want to allow custom operators like ">100*", which at this
6690 writing would be difficult or impossible to express otherwise in a JSON query.
6692 int is_good_operator( const char* op ) {
6693 if( !op ) return 0; // Sanity check
6697 if( isspace( (unsigned char) *s ) ) {
6698 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6699 // and IS NOT DISTINCT FROM.
6700 if( !strcasecmp( op, "similar to" ) )
6702 else if( !strcasecmp( op, "is distinct from" ) )
6704 else if( !strcasecmp( op, "is not distinct from" ) )
6709 else if( ';' == *s )
6717 @name Query Frame Management
6719 The following machinery supports a stack of query frames for use by SELECT().
6721 A query frame caches information about one level of a SELECT query. When we enter
6722 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6724 The query frame stores information about the core class, and about any joined classes
6727 The main purpose is to map table aliases to classes and tables, so that a query can
6728 join to the same table more than once. A secondary goal is to reduce the number of
6729 lookups in the IDL by caching the results.
6733 #define STATIC_CLASS_INFO_COUNT 3
6735 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6738 @brief Allocate a ClassInfo as raw memory.
6739 @return Pointer to the newly allocated ClassInfo.
6741 Except for the in_use flag, which is used only by the allocation and deallocation
6742 logic, we don't initialize the ClassInfo here.
6744 static ClassInfo* allocate_class_info( void ) {
6745 // In order to reduce the number of mallocs and frees, we return a static
6746 // instance of ClassInfo, if we can find one that we're not already using.
6747 // We rely on the fact that the compiler will implicitly initialize the
6748 // static instances so that in_use == 0.
6751 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6752 if( ! static_class_info[ i ].in_use ) {
6753 static_class_info[ i ].in_use = 1;
6754 return static_class_info + i;
6758 // The static ones are all in use. Malloc one.
6760 return safe_malloc( sizeof( ClassInfo ) );
6764 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6765 @param info Pointer to the ClassInfo to be cleared.
6767 static void clear_class_info( ClassInfo* info ) {
6772 // Free any malloc'd strings
6774 if( info->alias != info->alias_store )
6775 free( info->alias );
6777 if( info->class_name != info->class_name_store )
6778 free( info->class_name );
6780 free( info->source_def );
6782 info->alias = info->class_name = info->source_def = NULL;
6787 @brief Free a ClassInfo and everything it owns.
6788 @param info Pointer to the ClassInfo to be freed.
6790 static void free_class_info( ClassInfo* info ) {
6795 clear_class_info( info );
6797 // If it's one of the static instances, just mark it as not in use
6800 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6801 if( info == static_class_info + i ) {
6802 static_class_info[ i ].in_use = 0;
6807 // Otherwise it must have been malloc'd, so free it
6813 @brief Populate an already-allocated ClassInfo.
6814 @param info Pointer to the ClassInfo to be populated.
6815 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6817 @param class Name of the class.
6818 @return Zero if successful, or 1 if not.
6820 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6821 the relevant portions of the IDL for the specified class.
6823 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6826 osrfLogError( OSRF_LOG_MARK,
6827 "%s ERROR: No ClassInfo available to populate", modulename );
6828 info->alias = info->class_name = info->source_def = NULL;
6829 info->class_def = info->fields = info->links = NULL;
6834 osrfLogError( OSRF_LOG_MARK,
6835 "%s ERROR: No class name provided for lookup", modulename );
6836 info->alias = info->class_name = info->source_def = NULL;
6837 info->class_def = info->fields = info->links = NULL;
6841 // Alias defaults to class name if not supplied
6842 if( ! alias || ! alias[ 0 ] )
6845 // Look up class info in the IDL
6846 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6848 osrfLogError( OSRF_LOG_MARK,
6849 "%s ERROR: Class %s not defined in IDL", modulename, class );
6850 info->alias = info->class_name = info->source_def = NULL;
6851 info->class_def = info->fields = info->links = NULL;
6853 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6854 osrfLogError( OSRF_LOG_MARK,
6855 "%s ERROR: Class %s is defined as virtual", modulename, class );
6856 info->alias = info->class_name = info->source_def = NULL;
6857 info->class_def = info->fields = info->links = NULL;
6861 osrfHash* links = osrfHashGet( class_def, "links" );
6863 osrfLogError( OSRF_LOG_MARK,
6864 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6865 info->alias = info->class_name = info->source_def = NULL;
6866 info->class_def = info->fields = info->links = NULL;
6870 osrfHash* fields = osrfHashGet( class_def, "fields" );
6872 osrfLogError( OSRF_LOG_MARK,
6873 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6874 info->alias = info->class_name = info->source_def = NULL;
6875 info->class_def = info->fields = info->links = NULL;
6879 char* source_def = oilsGetRelation( class_def );
6883 // We got everything we need, so populate the ClassInfo
6884 if( strlen( alias ) > ALIAS_STORE_SIZE )
6885 info->alias = strdup( alias );
6887 strcpy( info->alias_store, alias );
6888 info->alias = info->alias_store;
6891 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6892 info->class_name = strdup( class );
6894 strcpy( info->class_name_store, class );
6895 info->class_name = info->class_name_store;
6898 info->source_def = source_def;
6900 info->class_def = class_def;
6901 info->links = links;
6902 info->fields = fields;
6907 #define STATIC_FRAME_COUNT 3
6909 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6912 @brief Allocate a QueryFrame as raw memory.
6913 @return Pointer to the newly allocated QueryFrame.
6915 Except for the in_use flag, which is used only by the allocation and deallocation
6916 logic, we don't initialize the QueryFrame here.
6918 static QueryFrame* allocate_frame( void ) {
6919 // In order to reduce the number of mallocs and frees, we return a static
6920 // instance of QueryFrame, if we can find one that we're not already using.
6921 // We rely on the fact that the compiler will implicitly initialize the
6922 // static instances so that in_use == 0.
6925 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6926 if( ! static_frame[ i ].in_use ) {
6927 static_frame[ i ].in_use = 1;
6928 return static_frame + i;
6932 // The static ones are all in use. Malloc one.
6934 return safe_malloc( sizeof( QueryFrame ) );
6938 @brief Free a QueryFrame, and all the memory it owns.
6939 @param frame Pointer to the QueryFrame to be freed.
6941 static void free_query_frame( QueryFrame* frame ) {
6946 clear_class_info( &frame->core );
6948 // Free the join list
6950 ClassInfo* info = frame->join_list;
6953 free_class_info( info );
6957 frame->join_list = NULL;
6960 // If the frame is a static instance, just mark it as unused
6962 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6963 if( frame == static_frame + i ) {
6964 static_frame[ i ].in_use = 0;
6969 // Otherwise it must have been malloc'd, so free it
6975 @brief Search a given QueryFrame for a specified alias.
6976 @param frame Pointer to the QueryFrame to be searched.
6977 @param target The alias for which to search.
6978 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6980 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6981 if( ! frame || ! target ) {
6985 ClassInfo* found_class = NULL;
6987 if( !strcmp( target, frame->core.alias ) )
6988 return &(frame->core);
6990 ClassInfo* curr_class = frame->join_list;
6991 while( curr_class ) {
6992 if( strcmp( target, curr_class->alias ) )
6993 curr_class = curr_class->next;
6995 found_class = curr_class;
7005 @brief Push a new (blank) QueryFrame onto the stack.
7007 static void push_query_frame( void ) {
7008 QueryFrame* frame = allocate_frame();
7009 frame->join_list = NULL;
7010 frame->next = curr_query;
7012 // Initialize the ClassInfo for the core class
7013 ClassInfo* core = &frame->core;
7014 core->alias = core->class_name = core->source_def = NULL;
7015 core->class_def = core->fields = core->links = NULL;
7021 @brief Pop a QueryFrame off the stack and destroy it.
7023 static void pop_query_frame( void ) {
7028 QueryFrame* popped = curr_query;
7029 curr_query = popped->next;
7031 free_query_frame( popped );
7035 @brief Populate the ClassInfo for the core class.
7036 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7037 class name as an alias.
7038 @param class_name Name of the core class.
7039 @return Zero if successful, or 1 if not.
7041 Populate the ClassInfo of the core class with copies of the alias and class name, and
7042 with pointers to the relevant portions of the IDL for the core class.
7044 static int add_query_core( const char* alias, const char* class_name ) {
7047 if( ! curr_query ) {
7048 osrfLogError( OSRF_LOG_MARK,
7049 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7051 } else if( curr_query->core.alias ) {
7052 osrfLogError( OSRF_LOG_MARK,
7053 "%s ERROR: Core class %s already populated as %s",
7054 modulename, curr_query->core.class_name, curr_query->core.alias );
7058 build_class_info( &curr_query->core, alias, class_name );
7059 if( curr_query->core.alias )
7062 osrfLogError( OSRF_LOG_MARK,
7063 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7069 @brief Search the current QueryFrame for a specified alias.
7070 @param target The alias for which to search.
7071 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7073 static inline ClassInfo* search_alias( const char* target ) {
7074 return search_alias_in_frame( curr_query, target );
7078 @brief Search all levels of query for a specified alias, starting with the current query.
7079 @param target The alias for which to search.
7080 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7082 static ClassInfo* search_all_alias( const char* target ) {
7083 ClassInfo* found_class = NULL;
7084 QueryFrame* curr_frame = curr_query;
7086 while( curr_frame ) {
7087 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7090 curr_frame = curr_frame->next;
7097 @brief Add a class to the list of classes joined to the current query.
7098 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7099 the class name as an alias.
7100 @param classname The name of the class to be added.
7101 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7103 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7105 if( ! classname || ! *classname ) { // sanity check
7106 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7113 const ClassInfo* conflict = search_alias( alias );
7115 osrfLogError( OSRF_LOG_MARK,
7116 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7117 modulename, alias, conflict->class_name );
7121 ClassInfo* info = allocate_class_info();
7123 if( build_class_info( info, alias, classname ) ) {
7124 free_class_info( info );
7128 // Add the new ClassInfo to the join list of the current QueryFrame
7129 info->next = curr_query->join_list;
7130 curr_query->join_list = info;
7136 @brief Destroy all nodes on the query stack.
7138 static void clear_query_stack( void ) {