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;
146 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id);
149 @brief Connect to the database.
150 @return A database connection if successful, or NULL if not.
152 dbi_conn oilsConnectDB( const char* mod_name ) {
154 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
155 if( dbi_initialize( NULL ) == -1 ) {
156 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
159 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
161 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
162 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
163 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
164 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
165 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
166 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
168 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
169 dbi_conn handle = dbi_conn_new( driver );
172 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
175 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
177 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
178 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
180 if( host ) dbi_conn_set_option( handle, "host", host );
181 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
182 if( user ) dbi_conn_set_option( handle, "username", user );
183 if( pw ) dbi_conn_set_option( handle, "password", pw );
184 if( db ) dbi_conn_set_option( handle, "dbname", db );
192 if( dbi_conn_connect( handle ) < 0 ) {
194 if( dbi_conn_connect( handle ) < 0 ) {
196 dbi_conn_error( handle, &msg );
197 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
198 msg ? msg : "(No description available)" );
203 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
209 @brief Select some options.
210 @param module_name: Name of the server.
211 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
213 This source file is used (at this writing) to implement three different servers:
214 - open-ils.reporter-store
218 These servers behave mostly the same, but they implement different combinations of
219 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
221 Here we use the server name in messages to identify which kind of server issued them.
222 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
224 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
226 module_name = "open-ils.cstore"; // bulletproofing with a default
231 modulename = strdup( module_name );
232 enforce_pcrud = do_pcrud;
233 max_flesh_depth = flesh_depth;
237 @brief Install a database connection.
238 @param conn Pointer to a database connection.
240 In some contexts, @a conn may merely provide a driver so that we can process strings
241 properly, without providing an open database connection.
243 void oilsSetDBConnection( dbi_conn conn ) {
244 dbhandle = writehandle = conn;
248 @brief Determine whether a database connection is alive.
249 @param handle Handle for a database connection.
250 @return 1 if the connection is alive, or zero if it isn't.
252 int oilsIsDBConnected( dbi_conn handle ) {
253 // Do an innocuous SELECT. If it succeeds, the database connection is still good.
254 dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
256 dbi_result_free( result );
259 // This is a terrible, horrible, no good, very bad kludge.
260 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
261 // but because (due to a previous error) the database is ignoring all commands,
262 // even innocuous SELECTs, until the current transaction is rolled back. The only
263 // known way to detect this condition via the dbi library is by looking at the error
264 // message. This approach will break if the language or wording of the message ever
266 // Note: the dbi_conn_ping function purports to determine whether the database
267 // connection is live, but at this writing this function is unreliable and useless.
268 static const char* ok_msg = "ERROR: current transaction is aborted, commands "
269 "ignored until end of transaction block\n";
271 dbi_conn_error( handle, &msg );
272 if( strcmp( msg, ok_msg )) {
273 osrfLogError( OSRF_LOG_MARK, "Database connection isn't working" );
276 return 1; // ignoring SELECT due to previous error; that's okay
281 @brief Get a table name, view name, or subquery for use in a FROM clause.
282 @param class Pointer to the IDL class entry.
283 @return A table name, a view name, or a subquery in parentheses.
285 In some cases the IDL defines a class, not with a table name or a view name, but with
286 a SELECT statement, which may be used as a subquery.
288 char* oilsGetRelation( osrfHash* classdef ) {
290 char* source_def = NULL;
291 const char* tabledef = osrfHashGet( classdef, "tablename" );
294 source_def = strdup( tabledef ); // Return the name of a table or view
296 tabledef = osrfHashGet( classdef, "source_definition" );
298 // Return a subquery, enclosed in parentheses
299 source_def = safe_malloc( strlen( tabledef ) + 3 );
300 source_def[ 0 ] = '(';
301 strcpy( source_def + 1, tabledef );
302 strcat( source_def, ")" );
304 // Not found: return an error
305 const char* classname = osrfHashGet( classdef, "classname" );
310 "%s ERROR No tablename or source_definition for class \"%s\"",
321 @brief Add datatypes from the database to the fields in the IDL.
322 @param handle Handle for a database connection
323 @return Zero if successful, or 1 upon error.
325 For each relevant class in the IDL: ask the database for the datatype of every field.
326 In particular, determine which fields are text fields and which fields are numeric
327 fields, so that we know whether to enclose their values in quotes.
329 int oilsExtendIDL( dbi_conn handle ) {
330 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
331 osrfHash* class = NULL;
332 growing_buffer* query_buf = buffer_init( 64 );
333 int results_found = 0; // boolean
335 // For each class in the IDL...
336 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
337 const char* classname = osrfHashIteratorKey( class_itr );
338 osrfHash* fields = osrfHashGet( class, "fields" );
340 // If the class is virtual, ignore it
341 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
342 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
346 char* tabledef = oilsGetRelation( class );
348 continue; // No such relation -- a query of it would be doomed to failure
350 buffer_reset( query_buf );
351 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
355 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
356 modulename, OSRF_BUFFER_C_STR( query_buf ) );
358 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
363 const char* columnName;
364 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
366 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
369 /* fetch the fieldmapper index */
370 osrfHash* _f = osrfHashGet(fields, columnName);
373 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
375 /* determine the field type and storage attributes */
377 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
379 case DBI_TYPE_INTEGER : {
381 if( !osrfHashGet(_f, "primitive") )
382 osrfHashSet(_f, "number", "primitive");
384 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
385 if( attr & DBI_INTEGER_SIZE8 )
386 osrfHashSet( _f, "INT8", "datatype" );
388 osrfHashSet( _f, "INT", "datatype" );
391 case DBI_TYPE_DECIMAL :
392 if( !osrfHashGet( _f, "primitive" ))
393 osrfHashSet( _f, "number", "primitive" );
395 osrfHashSet( _f, "NUMERIC", "datatype" );
398 case DBI_TYPE_STRING :
399 if( !osrfHashGet( _f, "primitive" ))
400 osrfHashSet( _f, "string", "primitive" );
402 osrfHashSet( _f,"TEXT", "datatype" );
405 case DBI_TYPE_DATETIME :
406 if( !osrfHashGet( _f, "primitive" ))
407 osrfHashSet( _f, "string", "primitive" );
409 osrfHashSet( _f, "TIMESTAMP", "datatype" );
412 case DBI_TYPE_BINARY :
413 if( !osrfHashGet( _f, "primitive" ))
414 osrfHashSet( _f, "string", "primitive" );
416 osrfHashSet( _f, "BYTEA", "datatype" );
421 "Setting [%s] to primitive [%s] and datatype [%s]...",
423 osrfHashGet( _f, "primitive" ),
424 osrfHashGet( _f, "datatype" )
428 } // end while loop for traversing columns of result
429 dbi_result_free( result );
432 int errnum = dbi_conn_error( handle, &msg );
433 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
434 errnum, msg ? msg : "(No description available)" );
435 // We don't check the database connection here. It's routine to get failures at
436 // this point; we routinely try to query tables that don't exist, because they
437 // are defined in the IDL but not in the database.
439 } // end for each class in IDL
441 buffer_free( query_buf );
442 osrfHashIteratorFree( class_itr );
443 child_initialized = 1;
445 if( !results_found ) {
446 osrfLogError( OSRF_LOG_MARK,
447 "No results found for any class -- bad database connection?" );
449 } else if( ! oilsIsDBConnected( handle )) {
450 osrfLogError( OSRF_LOG_MARK,
451 "Unable to extend IDL: database connection isn't working" );
459 @brief Free an osrfHash that stores a transaction ID.
460 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
462 This function is a callback, to be called by the application session when it ends.
463 The application session stores the osrfHash via an opaque pointer.
465 If the osrfHash contains an entry for the key "xact_id", it means that an
466 uncommitted transaction is pending. Roll it back.
468 void userDataFree( void* blob ) {
469 osrfHash* hash = (osrfHash*) blob;
470 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
471 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
473 int errnum = dbi_conn_error( writehandle, &msg );
474 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
475 errnum, msg ? msg : "(No description available)" );
479 if( !dbi_conn_query( writehandle, "SELECT auditor.clear_audit_info();" ) ) {
481 int errnum = dbi_conn_error( writehandle, &msg );
482 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform audit info clearing: %d %s",
483 errnum, msg ? msg : "(No description available)" );
487 osrfHashFree( hash );
491 @name Managing session data
492 @brief Maintain data stored via the userData pointer of the application session.
494 Currently, session-level data is stored in an osrfHash. Other arrangements are
495 possible, and some would be more efficient. The application session calls a
496 callback function to free userData before terminating.
498 Currently, the only data we store at the session level is the transaction id. By this
499 means we can ensure that any pending transactions are rolled back before the application
505 @brief Free an item in the application session's userData.
506 @param key The name of a key for an osrfHash.
507 @param item An opaque pointer to the item associated with the key.
509 We store an osrfHash as userData with the application session, and arrange (by
510 installing userDataFree() as a different callback) for the session to free that
511 osrfHash before terminating.
513 This function is a callback for freeing items in the osrfHash. Currently we store
515 - Transaction id of a pending transaction; a character string. Key: "xact_id".
516 - Authkey; a character string. Key: "authkey".
517 - User object from the authentication server; a jsonObject. Key: "user_login".
519 If we ever store anything else in userData, we will need to revisit this function so
520 that it will free whatever else needs freeing.
522 static void sessionDataFree( char* key, void* item ) {
523 if( !strcmp( key, "xact_id" ) || !strcmp( key, "authkey" ) || !strncmp( key, "rs_size_", 8) )
525 else if( !strcmp( key, "user_login" ) )
526 jsonObjectFree( (jsonObject*) item );
527 else if( !strcmp( key, "pcache" ) )
528 osrfHashFree( (osrfHash*) item );
531 static void pcacheFree( char* key, void* item ) {
532 osrfStringArrayFree( (osrfStringArray*) item );
536 @brief Initialize session cache.
537 @param ctx Pointer to the method context.
539 Create a cache for the session by making the session's userData member point
540 to an osrfHash instance.
542 static osrfHash* initSessionCache( osrfMethodContext* ctx ) {
543 ctx->session->userData = osrfNewHash();
544 osrfHashSetCallback( (osrfHash*) ctx->session->userData, &sessionDataFree );
545 ctx->session->userDataFree = &userDataFree;
546 return ctx->session->userData;
550 @brief Save a transaction id.
551 @param ctx Pointer to the method context.
553 Save the session_id of the current application session as a transaction id.
555 static void setXactId( osrfMethodContext* ctx ) {
556 if( ctx && ctx->session ) {
557 osrfAppSession* session = ctx->session;
559 osrfHash* cache = session->userData;
561 // If the session doesn't already have a hash, create one. Make sure
562 // that the application session frees the hash when it terminates.
564 cache = initSessionCache( ctx );
566 // Save the transaction id in the hash, with the key "xact_id"
567 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
572 @brief Get the transaction ID for the current transaction, if any.
573 @param ctx Pointer to the method context.
574 @return Pointer to the transaction ID.
576 The return value points to an internal buffer, and will become invalid upon issuing
577 a commit or rollback.
579 static inline const char* getXactId( osrfMethodContext* ctx ) {
580 if( ctx && ctx->session && ctx->session->userData )
581 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
587 @brief Clear the current transaction id.
588 @param ctx Pointer to the method context.
590 static inline void clearXactId( osrfMethodContext* ctx ) {
591 if( ctx && ctx->session && ctx->session->userData )
592 osrfHashRemove( ctx->session->userData, "xact_id" );
597 @brief Stash the location for a particular perm in the sessionData cache
598 @param ctx Pointer to the method context.
599 @param perm Name of the permission we're looking at
600 @param array StringArray of perm location ids
602 static void setPermLocationCache( osrfMethodContext* ctx, const char* perm, osrfStringArray* locations ) {
603 if( ctx && ctx->session ) {
604 osrfAppSession* session = ctx->session;
606 osrfHash* cache = session->userData;
608 // If the session doesn't already have a hash, create one. Make sure
609 // that the application session frees the hash when it terminates.
611 cache = initSessionCache( ctx );
613 osrfHash* pcache = osrfHashGet(cache, "pcache");
615 if( NULL == pcache ) {
616 pcache = osrfNewHash();
617 osrfHashSetCallback( pcache, &pcacheFree );
618 osrfHashSet( cache, pcache, "pcache" );
621 if( perm && locations )
622 osrfHashSet( pcache, locations, strdup(perm) );
627 @brief Grab stashed location for a particular perm in the sessionData cache
628 @param ctx Pointer to the method context.
629 @param perm Name of the permission we're looking at
631 static osrfStringArray* getPermLocationCache( osrfMethodContext* ctx, const char* perm ) {
632 if( ctx && ctx->session ) {
633 osrfAppSession* session = ctx->session;
634 osrfHash* cache = session->userData;
636 osrfHash* pcache = osrfHashGet(cache, "pcache");
638 return osrfHashGet( pcache, perm );
647 @brief Save the user's login in the userData for the current application session.
648 @param ctx Pointer to the method context.
649 @param user_login Pointer to the user login object to be cached (we cache the original,
652 If @a user_login is NULL, remove the user login if one is already cached.
654 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
655 if( ctx && ctx->session ) {
656 osrfAppSession* session = ctx->session;
658 osrfHash* cache = session->userData;
660 // If the session doesn't already have a hash, create one. Make sure
661 // that the application session frees the hash when it terminates.
663 cache = initSessionCache( ctx );
666 osrfHashSet( cache, user_login, "user_login" );
668 osrfHashRemove( cache, "user_login" );
673 @brief Get the user login object for the current application session, if any.
674 @param ctx Pointer to the method context.
675 @return Pointer to the user login object if found; otherwise NULL.
677 The user login object was returned from the authentication server, and then cached so
678 we don't have to call the authentication server again for the same user.
680 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
681 if( ctx && ctx->session && ctx->session->userData )
682 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
688 @brief Save a copy of an authkey in the userData of the current application session.
689 @param ctx Pointer to the method context.
690 @param authkey The authkey to be saved.
692 If @a authkey is NULL, remove the authkey if one is already cached.
694 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
695 if( ctx && ctx->session && authkey ) {
696 osrfAppSession* session = ctx->session;
697 osrfHash* cache = session->userData;
699 // If the session doesn't already have a hash, create one. Make sure
700 // that the application session frees the hash when it terminates.
702 cache = initSessionCache( ctx );
704 // Save the transaction id in the hash, with the key "xact_id"
705 if( authkey && *authkey )
706 osrfHashSet( cache, strdup( authkey ), "authkey" );
708 osrfHashRemove( cache, "authkey" );
713 @brief Reset the login timeout.
714 @param authkey The authentication key for the current login session.
715 @param now The current time.
716 @return Zero if successful, or 1 if not.
718 Tell the authentication server to reset the timeout so that the login session won't
719 expire for a while longer.
721 We could dispense with the @a now parameter by calling time(). But we just called
722 time() in order to decide whether to reset the timeout, so we might as well reuse
723 the result instead of calling time() again.
725 static int reset_timeout( const char* authkey, time_t now ) {
726 jsonObject* auth_object = jsonNewObject( authkey );
728 // Ask the authentication server to reset the timeout. It returns an event
729 // indicating success or failure.
730 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
731 "open-ils.auth.session.reset_timeout", auth_object );
732 jsonObjectFree( auth_object );
734 if( !result || result->type != JSON_HASH ) {
735 osrfLogError( OSRF_LOG_MARK,
736 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
737 jsonObjectFree( result );
738 return 1; // Not the right sort of object returned
741 const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
742 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
743 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
744 jsonObjectFree( result );
745 return 1; // Return code from method not available
748 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
749 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
751 desc = "(No reason available)"; // failsafe; shouldn't happen
752 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
753 jsonObjectFree( result );
757 // Revise our local proxy for the timeout deadline
758 // by a smallish fraction of the timeout interval
759 const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
761 timeout = "1"; // failsafe; shouldn't happen
762 time_next_reset = now + atoi( timeout ) / 15;
764 jsonObjectFree( result );
765 return 0; // Successfully reset timeout
769 @brief Get the authkey string for the current application session, if any.
770 @param ctx Pointer to the method context.
771 @return Pointer to the cached authkey if found; otherwise NULL.
773 If present, the authkey string was cached from a previous method call.
775 static const char* getAuthkey( osrfMethodContext* ctx ) {
776 if( ctx && ctx->session && ctx->session->userData ) {
777 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
778 // LFW recent changes mean the userData hash gets set up earlier, but
779 // doesn't necessarily have an authkey yet
783 // Possibly reset the authentication timeout to keep the login alive. We do so
784 // no more than once per method call, and not at all if it has been only a short
785 // time since the last reset.
787 // Here we reset explicitly, if at all. We also implicitly reset the timeout
788 // whenever we call the "open-ils.auth.session.retrieve" method.
789 if( timeout_needs_resetting ) {
790 time_t now = time( NULL );
791 if( now >= time_next_reset && reset_timeout( authkey, now ) )
792 authkey = NULL; // timeout has apparently expired already
795 timeout_needs_resetting = 0;
803 @brief Implement the transaction.begin method.
804 @param ctx Pointer to the method context.
805 @return Zero if successful, or -1 upon error.
807 Start a transaction. Save a transaction ID for future reference.
810 - authkey (PCRUD only)
812 Return to client: Transaction ID
814 int beginTransaction( osrfMethodContext* ctx ) {
815 if(osrfMethodVerifyContext( ctx )) {
816 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
820 if( enforce_pcrud ) {
821 timeout_needs_resetting = 1;
822 const jsonObject* user = verifyUserPCRUD( ctx );
827 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
830 int errnum = dbi_conn_error( writehandle, &msg );
831 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
832 modulename, errnum, msg ? msg : "(No description available)" );
833 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
834 "osrfMethodException", ctx->request, "Error starting transaction" );
835 if( !oilsIsDBConnected( writehandle ))
836 osrfAppSessionPanic( ctx->session );
839 dbi_result_free( result );
841 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
842 osrfAppRespondComplete( ctx, ret );
843 jsonObjectFree( ret );
849 @brief Implement the savepoint.set method.
850 @param ctx Pointer to the method context.
851 @return Zero if successful, or -1 if not.
853 Issue a SAVEPOINT to the database server.
856 - authkey (PCRUD only)
859 Return to client: Savepoint name
861 int setSavepoint( osrfMethodContext* ctx ) {
862 if(osrfMethodVerifyContext( ctx )) {
863 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
868 if( enforce_pcrud ) {
870 timeout_needs_resetting = 1;
871 const jsonObject* user = verifyUserPCRUD( ctx );
876 // Verify that a transaction is pending
877 const char* trans_id = getXactId( ctx );
878 if( NULL == trans_id ) {
879 osrfAppSessionStatus(
881 OSRF_STATUS_INTERNALSERVERERROR,
882 "osrfMethodException",
884 "No active transaction -- required for savepoints"
889 // Get the savepoint name from the method params
890 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
892 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
895 int errnum = dbi_conn_error( writehandle, &msg );
898 "%s: Error creating savepoint %s in transaction %s: %d %s",
903 msg ? msg : "(No description available)"
905 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
906 "osrfMethodException", ctx->request, "Error creating savepoint" );
907 if( !oilsIsDBConnected( writehandle ))
908 osrfAppSessionPanic( ctx->session );
911 dbi_result_free( result );
912 jsonObject* ret = jsonNewObject( spName );
913 osrfAppRespondComplete( ctx, ret );
914 jsonObjectFree( ret );
920 @brief Implement the savepoint.release method.
921 @param ctx Pointer to the method context.
922 @return Zero if successful, or -1 if not.
924 Issue a RELEASE SAVEPOINT to the database server.
927 - authkey (PCRUD only)
930 Return to client: Savepoint name
932 int releaseSavepoint( osrfMethodContext* ctx ) {
933 if(osrfMethodVerifyContext( ctx )) {
934 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
939 if( enforce_pcrud ) {
941 timeout_needs_resetting = 1;
942 const jsonObject* user = verifyUserPCRUD( ctx );
947 // Verify that a transaction is pending
948 const char* trans_id = getXactId( ctx );
949 if( NULL == trans_id ) {
950 osrfAppSessionStatus(
952 OSRF_STATUS_INTERNALSERVERERROR,
953 "osrfMethodException",
955 "No active transaction -- required for savepoints"
960 // Get the savepoint name from the method params
961 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
963 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
966 int errnum = dbi_conn_error( writehandle, &msg );
969 "%s: Error releasing savepoint %s in transaction %s: %d %s",
974 msg ? msg : "(No description available)"
976 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
977 "osrfMethodException", ctx->request, "Error releasing savepoint" );
978 if( !oilsIsDBConnected( writehandle ))
979 osrfAppSessionPanic( ctx->session );
982 dbi_result_free( result );
983 jsonObject* ret = jsonNewObject( spName );
984 osrfAppRespondComplete( ctx, ret );
985 jsonObjectFree( ret );
991 @brief Implement the savepoint.rollback method.
992 @param ctx Pointer to the method context.
993 @return Zero if successful, or -1 if not.
995 Issue a ROLLBACK TO SAVEPOINT to the database server.
998 - authkey (PCRUD only)
1001 Return to client: Savepoint name
1003 int rollbackSavepoint( osrfMethodContext* ctx ) {
1004 if(osrfMethodVerifyContext( ctx )) {
1005 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1010 if( enforce_pcrud ) {
1012 timeout_needs_resetting = 1;
1013 const jsonObject* user = verifyUserPCRUD( ctx );
1018 // Verify that a transaction is pending
1019 const char* trans_id = getXactId( ctx );
1020 if( NULL == trans_id ) {
1021 osrfAppSessionStatus(
1023 OSRF_STATUS_INTERNALSERVERERROR,
1024 "osrfMethodException",
1026 "No active transaction -- required for savepoints"
1031 // Get the savepoint name from the method params
1032 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1034 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
1037 int errnum = dbi_conn_error( writehandle, &msg );
1040 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
1045 msg ? msg : "(No description available)"
1047 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1048 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1049 if( !oilsIsDBConnected( writehandle ))
1050 osrfAppSessionPanic( ctx->session );
1053 dbi_result_free( result );
1054 jsonObject* ret = jsonNewObject( spName );
1055 osrfAppRespondComplete( ctx, ret );
1056 jsonObjectFree( ret );
1062 @brief Implement the transaction.commit method.
1063 @param ctx Pointer to the method context.
1064 @return Zero if successful, or -1 if not.
1066 Issue a COMMIT to the database server.
1069 - authkey (PCRUD only)
1071 Return to client: Transaction ID.
1073 int commitTransaction( osrfMethodContext* ctx ) {
1074 if(osrfMethodVerifyContext( ctx )) {
1075 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1079 if( enforce_pcrud ) {
1080 timeout_needs_resetting = 1;
1081 const jsonObject* user = verifyUserPCRUD( ctx );
1086 // Verify that a transaction is pending
1087 const char* trans_id = getXactId( ctx );
1088 if( NULL == trans_id ) {
1089 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1090 "osrfMethodException", ctx->request, "No active transaction to commit" );
1094 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1097 int errnum = dbi_conn_error( writehandle, &msg );
1098 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1099 modulename, errnum, msg ? msg : "(No description available)" );
1100 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1101 "osrfMethodException", ctx->request, "Error committing transaction" );
1102 if( !oilsIsDBConnected( writehandle ))
1103 osrfAppSessionPanic( ctx->session );
1106 dbi_result_free( result );
1107 jsonObject* ret = jsonNewObject( trans_id );
1108 osrfAppRespondComplete( ctx, ret );
1109 jsonObjectFree( ret );
1116 @brief Implement the transaction.rollback method.
1117 @param ctx Pointer to the method context.
1118 @return Zero if successful, or -1 if not.
1120 Issue a ROLLBACK to the database server.
1123 - authkey (PCRUD only)
1125 Return to client: Transaction ID
1127 int rollbackTransaction( osrfMethodContext* ctx ) {
1128 if( osrfMethodVerifyContext( ctx )) {
1129 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1133 if( enforce_pcrud ) {
1134 timeout_needs_resetting = 1;
1135 const jsonObject* user = verifyUserPCRUD( ctx );
1140 // Verify that a transaction is pending
1141 const char* trans_id = getXactId( ctx );
1142 if( NULL == trans_id ) {
1143 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1144 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1148 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1151 int errnum = dbi_conn_error( writehandle, &msg );
1152 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1153 modulename, errnum, msg ? msg : "(No description available)" );
1154 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1155 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1156 if( !oilsIsDBConnected( writehandle ))
1157 osrfAppSessionPanic( ctx->session );
1160 dbi_result_free( result );
1161 jsonObject* ret = jsonNewObject( trans_id );
1162 osrfAppRespondComplete( ctx, ret );
1163 jsonObjectFree( ret );
1170 @brief Implement the "search" method.
1171 @param ctx Pointer to the method context.
1172 @return Zero if successful, or -1 if not.
1175 - authkey (PCRUD only)
1176 - WHERE clause, as jsonObject
1177 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1179 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1180 Optionally flesh linked fields.
1182 int doSearch( osrfMethodContext* ctx ) {
1183 if( osrfMethodVerifyContext( ctx )) {
1184 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1189 timeout_needs_resetting = 1;
1191 jsonObject* where_clause;
1192 jsonObject* rest_of_query;
1194 if( enforce_pcrud ) {
1195 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1196 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1198 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1199 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1202 if( !where_clause ) {
1203 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1207 // Get the class metadata
1208 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1209 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1213 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1215 osrfAppRespondComplete( ctx, NULL );
1219 // doFieldmapperSearch() now takes care of our responding for us
1220 // // Return each row to the client
1221 // jsonObject* cur = 0;
1222 // unsigned long res_idx = 0;
1224 // while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1225 // // We used to discard based on perms here, but now that's
1226 // // inside doFieldmapperSearch()
1227 // osrfAppRespond( ctx, cur );
1230 jsonObjectFree( obj );
1232 osrfAppRespondComplete( ctx, NULL );
1237 @brief Implement the "id_list" method.
1238 @param ctx Pointer to the method context.
1239 @param err Pointer through which to return an error code.
1240 @return Zero if successful, or -1 if not.
1243 - authkey (PCRUD only)
1244 - WHERE clause, as jsonObject
1245 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1247 Return to client: The primary key values for all rows of the relevant class that
1248 satisfy a specified WHERE clause.
1250 This method relies on the assumption that every class has a primary key consisting of
1253 int doIdList( osrfMethodContext* ctx ) {
1254 if( osrfMethodVerifyContext( ctx )) {
1255 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1260 timeout_needs_resetting = 1;
1262 jsonObject* where_clause;
1263 jsonObject* rest_of_query;
1265 // We use the where clause without change. But we need to massage the rest of the
1266 // query, so we work with a copy of it instead of modifying the original.
1268 if( enforce_pcrud ) {
1269 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1270 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1272 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1273 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1276 if( !where_clause ) {
1277 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1281 // Eliminate certain SQL clauses, if present.
1282 if( rest_of_query ) {
1283 jsonObjectRemoveKey( rest_of_query, "select" );
1284 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1285 jsonObjectRemoveKey( rest_of_query, "flesh" );
1286 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1288 rest_of_query = jsonNewObjectType( JSON_HASH );
1291 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1293 // Get the class metadata
1294 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1295 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1297 // Build a SELECT list containing just the primary key,
1298 // i.e. like { "classname":["keyname"] }
1299 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1301 // Load array with name of primary key
1302 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1303 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1304 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1306 jsonObjectSetKey( rest_of_query, "select", select_clause );
1311 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1313 jsonObjectFree( rest_of_query );
1315 osrfAppRespondComplete( ctx, NULL );
1319 // Return each primary key value to the client
1321 unsigned long res_idx = 0;
1322 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1323 // We used to discard based on perms here, but now that's
1324 // inside doFieldmapperSearch()
1325 osrfAppRespond( ctx,
1326 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1329 jsonObjectFree( obj );
1330 osrfAppRespondComplete( ctx, NULL );
1335 @brief Verify that we have a valid class reference.
1336 @param ctx Pointer to the method context.
1337 @param param Pointer to the method parameters.
1338 @return 1 if the class reference is valid, or zero if it isn't.
1340 The class of the method params must match the class to which the method id devoted.
1341 For PCRUD there are additional restrictions.
1343 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1345 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1346 osrfHash* class = osrfHashGet( method_meta, "class" );
1348 // Compare the method's class to the parameters' class
1349 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1351 // Oops -- they don't match. Complain.
1352 growing_buffer* msg = buffer_init( 128 );
1355 "%s: %s method for type %s was passed a %s",
1357 osrfHashGet( method_meta, "methodtype" ),
1358 osrfHashGet( class, "classname" ),
1359 param->classname ? param->classname : "(null)"
1362 char* m = buffer_release( msg );
1363 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1371 return verifyObjectPCRUD( ctx, class, param, 1 );
1377 @brief (PCRUD only) Verify that the user is properly logged in.
1378 @param ctx Pointer to the method context.
1379 @return If the user is logged in, a pointer to the user object from the authentication
1380 server; otherwise NULL.
1382 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1384 // Get the authkey (the first method parameter)
1385 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1387 // See if we have the same authkey, and a user object,
1388 // locally cached from a previous call
1389 const char* cached_authkey = getAuthkey( ctx );
1390 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1391 const jsonObject* cached_user = getUserLogin( ctx );
1396 // We have no matching authentication data in the cache. Authenticate from scratch.
1397 jsonObject* auth_object = jsonNewObject( auth );
1399 // Fetch the user object from the authentication server
1400 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1402 jsonObjectFree( auth_object );
1404 if( !user->classname || strcmp(user->classname, "au" )) {
1406 growing_buffer* msg = buffer_init( 128 );
1409 "%s: permacrud received a bad auth token: %s",
1414 char* m = buffer_release( msg );
1415 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1419 jsonObjectFree( user );
1421 } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
1422 // Failed to set audit information - But note that write_audit_info already set error information.
1423 jsonObjectFree( user );
1427 setUserLogin( ctx, user );
1428 setAuthkey( ctx, auth );
1430 // Allow ourselves up to a second before we have to reset the login timeout.
1431 // It would be nice to use some fraction of the timeout interval enforced by the
1432 // authentication server, but that value is not readily available at this point.
1433 // Instead, we use a conservative default interval.
1434 time_next_reset = time( NULL ) + 1;
1440 @brief For PCRUD: Determine whether the current user may access the current row.
1441 @param ctx Pointer to the method context.
1442 @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1443 @param obj Pointer to the row being potentially accessed.
1444 @return 1 if access is permitted, or 0 if it isn't.
1446 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1448 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1450 dbhandle = writehandle;
1452 // Figure out what class and method are involved
1453 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1454 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1457 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1458 if (rs_size_from_hash) {
1459 rs_size = *rs_size_from_hash;
1460 osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1464 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1465 // contexts we will do another lookup of the current row, even if we already have a
1466 // previously fetched row image, because the row image in hand may not include the
1467 // foreign key(s) that we need.
1469 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1470 // but they aren't implemented yet.
1473 if( *method_type == 's' || *method_type == 'i' ) {
1474 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1476 } else if( *method_type == 'u' || *method_type == 'd' ) {
1477 fetch = 1; // MUST go to the db for the object for update and delete
1480 // Get the appropriate permacrud entry from the IDL, depending on method type
1481 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1483 // No permacrud for this method type on this class
1485 growing_buffer* msg = buffer_init( 128 );
1488 "%s: %s on class %s has no permacrud IDL entry",
1490 osrfHashGet( method_metadata, "methodtype" ),
1491 osrfHashGet( class, "classname" )
1494 char* m = buffer_release( msg );
1495 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1496 "osrfMethodException", ctx->request, m );
1503 // Get the user id, and make sure the user is logged in
1504 const jsonObject* user = verifyUserPCRUD( ctx );
1506 return 0; // Not logged in? No access.
1508 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1510 // Get a list of permissions from the permacrud entry.
1511 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1512 if( permission->size == 0 ) {
1515 "No permissions required for this action (class %s), passing through",
1516 osrfHashGet(class, "classname")
1521 // Build a list of org units that own the row. This is fairly convoluted because there
1522 // are several different ways that an org unit may own the row, as defined by the
1525 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1526 // identifying an owning org_unit..
1527 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1529 // Foreign context adds a layer of indirection. The row points to some other row that
1530 // an org unit may own. The "jump" attribute, if present, adds another layer of
1532 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1534 // The following string array stores the list of org units. (We don't have a thingie
1535 // for storing lists of integers, so we fake it with a list of strings.)
1536 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1539 const char* pkey_value = NULL;
1540 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1541 // If the global_required attribute is present and true, then the only owning
1542 // org unit is the root org unit, i.e. the one with no parent.
1543 osrfLogDebug( OSRF_LOG_MARK,
1544 "global-level permissions required, fetching top of the org tree" );
1546 // no need to check perms for org tree root retrieval
1547 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1548 // check for perm at top of org tree
1549 const char* org_tree_root_id = org_tree_root( ctx );
1550 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1552 if( org_tree_root_id ) {
1553 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1554 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1556 osrfStringArrayFree( context_org_array );
1561 // If the global_required attribute is absent or false, then we look for
1562 // local and/or foreign context. In order to find the relevant foreign
1563 // keys, we must either read the relevant row from the database, or look at
1564 // the image of the row that we already have in memory.
1566 // Even if we have an image of the row in memory, that image may not include the
1567 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1568 // of the row to make sure that we have what we need.
1570 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1571 "fetching context org ids" );
1572 const char* pkey = osrfHashGet( class, "primarykey" );
1573 jsonObject *param = NULL;
1576 // There is no primary key, so we can't do a fresh lookup. Use the row
1577 // image that we already have. If it doesn't have everything we need, too bad.
1579 param = jsonObjectClone( obj );
1580 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1581 } else if( obj->classname ) {
1582 pkey_value = oilsFMGetStringConst( obj, pkey );
1584 param = jsonObjectClone( obj );
1585 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1588 pkey_value = jsonObjectGetString( obj );
1590 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1591 "of %s and retrieving from the database", pkey_value );
1595 // Fetch the row so that we can look at the foreign key(s)
1596 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1597 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1598 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1599 jsonObjectFree( _tmp_params );
1600 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1602 param = jsonObjectExtractIndex( _list, 0 );
1603 jsonObjectFree( _list );
1607 // The row doesn't exist. Complain, and deny access.
1608 osrfLogDebug( OSRF_LOG_MARK,
1609 "Object not found in the database with primary key %s of %s",
1612 growing_buffer* msg = buffer_init( 128 );
1615 "%s: no object found with primary key %s of %s",
1621 char* m = buffer_release( msg );
1622 osrfAppSessionStatus(
1624 OSRF_STATUS_INTERNALSERVERERROR,
1625 "osrfMethodException",
1634 if( local_context && local_context->size > 0 ) {
1635 // The IDL provides a list of column names for the foreign keys denoting
1636 // local context, i.e. columns identifying owing org units directly. Look up
1637 // the value of each one, and if it isn't null, add it to the list of org units.
1638 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1639 local_context->size );
1641 const char* lcontext = NULL;
1642 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1643 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1644 if( fkey_value ) { // if not null
1645 osrfStringArrayAdd( context_org_array, fkey_value );
1648 "adding class-local field %s (value: %s) to the context org list",
1650 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1656 if( foreign_context ) {
1657 unsigned long class_count = osrfHashGetCount( foreign_context );
1658 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1660 if( class_count > 0 ) {
1662 // The IDL provides a list of foreign key columns pointing to rows that
1663 // an org unit may own. Follow each link, identify the owning org unit,
1664 // and add it to the list.
1665 osrfHash* fcontext = NULL;
1666 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1667 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1668 // For each class to which a foreign key points:
1669 const char* class_name = osrfHashIteratorKey( class_itr );
1670 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1674 "%d foreign context fields(s) specified for class %s",
1675 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1679 // Get the name of the key field in the foreign table
1680 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1682 // Get the value of the foreign key pointing to the foreign table
1683 char* foreign_pkey_value =
1684 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1685 if( !foreign_pkey_value )
1686 continue; // Foreign key value is null; skip it
1688 // Look up the row to which the foreign key points
1689 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1691 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1692 jsonObject* _list = doFieldmapperSearch(
1693 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1694 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1696 jsonObject* _fparam = NULL;
1697 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1698 _fparam = jsonObjectExtractIndex( _list, 0 );
1700 jsonObjectFree( _tmp_params );
1701 jsonObjectFree( _list );
1703 // At this point _fparam either points to the row identified by the
1704 // foreign key, or it's NULL (no such row found).
1706 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1708 const char* bad_class = NULL; // For noting failed lookups
1710 bad_class = class_name; // Referenced row not found
1711 else if( jump_list ) {
1712 // Follow a chain of rows, linked by foreign keys, to find an owner
1713 const char* flink = NULL;
1715 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1716 // For each entry in the jump list. Each entry (i.e. flink) is
1717 // the name of a foreign key column in the current row.
1719 // From the IDL, get the linkage information for the next jump
1720 osrfHash* foreign_link_hash =
1721 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1723 // Get the class metadata for the class
1724 // to which the foreign key points
1725 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1726 osrfHashGet( foreign_link_hash, "class" ));
1728 // Get the name of the referenced key of that class
1729 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1731 // Get the value of the foreign key pointing to that class
1732 free( foreign_pkey_value );
1733 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1734 if( !foreign_pkey_value )
1735 break; // Foreign key is null; quit looking
1737 // Build a WHERE clause for the lookup
1738 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1741 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1742 _tmp_params, NULL, &err );
1744 // Get the resulting row
1745 jsonObjectFree( _fparam );
1746 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1747 _fparam = jsonObjectExtractIndex( _list, 0 );
1749 // Referenced row not found
1751 bad_class = osrfHashGet( foreign_link_hash, "class" );
1754 jsonObjectFree( _tmp_params );
1755 jsonObjectFree( _list );
1761 // We had a foreign key pointing to such-and-such a row, but then
1762 // we couldn't fetch that row. The data in the database are in an
1763 // inconsistent state; the database itself may even be corrupted.
1764 growing_buffer* msg = buffer_init( 128 );
1767 "%s: no object of class %s found with primary key %s of %s",
1771 foreign_pkey_value ? foreign_pkey_value : "(null)"
1774 char* m = buffer_release( msg );
1775 osrfAppSessionStatus(
1777 OSRF_STATUS_INTERNALSERVERERROR,
1778 "osrfMethodException",
1784 osrfHashIteratorFree( class_itr );
1785 free( foreign_pkey_value );
1786 jsonObjectFree( param );
1791 free( foreign_pkey_value );
1794 // Examine each context column of the foreign row,
1795 // and add its value to the list of org units.
1797 const char* foreign_field = NULL;
1798 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1799 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1800 osrfStringArrayAdd( context_org_array,
1801 oilsFMGetStringConst( _fparam, foreign_field ));
1802 osrfLogDebug( OSRF_LOG_MARK,
1803 "adding foreign class %s field %s (value: %s) "
1804 "to the context org list",
1807 osrfStringArrayGetString(
1808 context_org_array, context_org_array->size - 1 )
1812 jsonObjectFree( _fparam );
1816 osrfHashIteratorFree( class_itr );
1820 jsonObjectFree( param );
1823 const char* context_org = NULL;
1824 const char* perm = NULL;
1827 // For every combination of permission and context org unit: call a stored procedure
1828 // to determine if the user has this permission in the context of this org unit.
1829 // If the answer is yes at any point, then we're done, and the user has permission.
1830 // In other words permissions are additive.
1832 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1835 osrfStringArray* pcache = NULL;
1836 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
1837 pcache = getPermLocationCache(ctx, perm);
1840 pcache = osrfNewStringArray(0);
1842 result = dbi_conn_queryf(
1844 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
1852 "Received a result for permission [%s] for user %d",
1857 if( dbi_result_first_row( result )) {
1859 jsonObject* return_val = oilsMakeJSONFromResult( result );
1860 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
1861 jsonObjectFree( return_val );
1862 } while( dbi_result_next_row( result ));
1864 setPermLocationCache(ctx, perm, pcache);
1867 dbi_result_free( result );
1873 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1875 if (rs_size > perm_at_threshold) {
1876 if (osrfStringArrayContains( pcache, context_org )) {
1885 "Checking object permission [%s] for user %d "
1886 "on object %s (class %s) at org %d",
1890 osrfHashGet( class, "classname" ),
1894 result = dbi_conn_queryf(
1896 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1899 osrfHashGet( class, "classname" ),
1907 "Received a result for object permission [%s] "
1908 "for user %d on object %s (class %s) at org %d",
1912 osrfHashGet( class, "classname" ),
1916 if( dbi_result_first_row( result )) {
1917 jsonObject* return_val = oilsMakeJSONFromResult( result );
1918 const char* has_perm = jsonObjectGetString(
1919 jsonObjectGetKeyConst( return_val, "has_perm" ));
1923 "Status of object permission [%s] for user %d "
1924 "on object %s (class %s) at org %d is %s",
1928 osrfHashGet(class, "classname"),
1933 if( *has_perm == 't' )
1935 jsonObjectFree( return_val );
1938 dbi_result_free( result );
1943 int errnum = dbi_conn_error( writehandle, &msg );
1944 osrfLogWarning( OSRF_LOG_MARK,
1945 "Unable to call check object permissions: %d, %s",
1946 errnum, msg ? msg : "(No description available)" );
1947 if( !oilsIsDBConnected( writehandle ))
1948 osrfAppSessionPanic( ctx->session );
1952 if (rs_size > perm_at_threshold) break;
1954 osrfLogDebug( OSRF_LOG_MARK,
1955 "Checking non-object permission [%s] for user %d at org %d",
1956 perm, userid, atoi(context_org) );
1957 result = dbi_conn_queryf(
1959 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1966 osrfLogDebug( OSRF_LOG_MARK,
1967 "Received a result for permission [%s] for user %d at org %d",
1968 perm, userid, atoi( context_org ));
1969 if( dbi_result_first_row( result )) {
1970 jsonObject* return_val = oilsMakeJSONFromResult( result );
1971 const char* has_perm = jsonObjectGetString(
1972 jsonObjectGetKeyConst( return_val, "has_perm" ));
1973 osrfLogDebug( OSRF_LOG_MARK,
1974 "Status of permission [%s] for user %d at org %d is [%s]",
1975 perm, userid, atoi( context_org ), has_perm );
1976 if( *has_perm == 't' )
1978 jsonObjectFree( return_val );
1981 dbi_result_free( result );
1986 int errnum = dbi_conn_error( writehandle, &msg );
1987 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
1988 errnum, msg ? msg : "(No description available)" );
1989 if( !oilsIsDBConnected( writehandle ))
1990 osrfAppSessionPanic( ctx->session );
1999 osrfStringArrayFree( context_org_array );
2005 @brief Look up the root of the org_unit tree.
2006 @param ctx Pointer to the method context.
2007 @return The id of the root org unit, as a character string.
2009 Query actor.org_unit where parent_ou is null, and return the id as a string.
2011 This function assumes that there is only one root org unit, i.e. that we
2012 have a single tree, not a forest.
2014 The calling code is responsible for freeing the returned string.
2016 static const char* org_tree_root( osrfMethodContext* ctx ) {
2018 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
2019 static time_t last_lookup_time = 0;
2020 time_t current_time = time( NULL );
2022 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2023 // We successfully looked this up less than an hour ago.
2024 // It's not likely to have changed since then.
2025 return strdup( cached_root_id );
2027 last_lookup_time = current_time;
2030 jsonObject* where_clause = single_hash( "parent_ou", NULL );
2031 jsonObject* result = doFieldmapperSearch(
2032 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2033 jsonObjectFree( where_clause );
2035 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2038 jsonObjectFree( result );
2040 growing_buffer* msg = buffer_init( 128 );
2041 OSRF_BUFFER_ADD( msg, modulename );
2042 OSRF_BUFFER_ADD( msg,
2043 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2045 char* m = buffer_release( msg );
2046 osrfAppSessionStatus( ctx->session,
2047 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2050 cached_root_id[ 0 ] = '\0';
2054 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2055 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2057 strcpy( cached_root_id, root_org_unit_id );
2058 jsonObjectFree( result );
2059 return cached_root_id;
2063 @brief Create a JSON_HASH with a single key/value pair.
2064 @param key The key of the key/value pair.
2065 @param value the value of the key/value pair.
2066 @return Pointer to a newly created jsonObject of type JSON_HASH.
2068 The value of the key/value is either a string or (if @a value is NULL) a null.
2070 static jsonObject* single_hash( const char* key, const char* value ) {
2072 if( ! key ) key = "";
2074 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2075 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2080 int doCreate( osrfMethodContext* ctx ) {
2081 if(osrfMethodVerifyContext( ctx )) {
2082 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2087 timeout_needs_resetting = 1;
2089 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2090 jsonObject* target = NULL;
2091 jsonObject* options = NULL;
2093 if( enforce_pcrud ) {
2094 target = jsonObjectGetIndex( ctx->params, 1 );
2095 options = jsonObjectGetIndex( ctx->params, 2 );
2097 target = jsonObjectGetIndex( ctx->params, 0 );
2098 options = jsonObjectGetIndex( ctx->params, 1 );
2101 if( !verifyObjectClass( ctx, target )) {
2102 osrfAppRespondComplete( ctx, NULL );
2106 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2108 const char* trans_id = getXactId( ctx );
2110 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2112 osrfAppSessionStatus(
2114 OSRF_STATUS_BADREQUEST,
2115 "osrfMethodException",
2117 "No active transaction -- required for CREATE"
2119 osrfAppRespondComplete( ctx, NULL );
2123 // The following test is harmless but redundant. If a class is
2124 // readonly, we don't register a create method for it.
2125 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2126 osrfAppSessionStatus(
2128 OSRF_STATUS_BADREQUEST,
2129 "osrfMethodException",
2131 "Cannot INSERT readonly class"
2133 osrfAppRespondComplete( ctx, NULL );
2137 // Set the last_xact_id
2138 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2140 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2141 trans_id, target->classname, index);
2142 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2145 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2147 dbhandle = writehandle;
2149 osrfHash* fields = osrfHashGet( meta, "fields" );
2150 char* pkey = osrfHashGet( meta, "primarykey" );
2151 char* seq = osrfHashGet( meta, "sequence" );
2153 growing_buffer* table_buf = buffer_init( 128 );
2154 growing_buffer* col_buf = buffer_init( 128 );
2155 growing_buffer* val_buf = buffer_init( 128 );
2157 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2158 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2159 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2160 buffer_add( val_buf,"VALUES (" );
2164 osrfHash* field = NULL;
2165 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2166 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2168 const char* field_name = osrfHashIteratorKey( field_itr );
2170 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2173 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2176 if( field_object && field_object->classname ) {
2177 value = oilsFMGetString(
2179 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2181 } else if( field_object && JSON_BOOL == field_object->type ) {
2182 if( jsonBoolIsTrue( field_object ) )
2183 value = strdup( "t" );
2185 value = strdup( "f" );
2187 value = jsonObjectToSimpleString( field_object );
2193 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2194 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2197 buffer_add( col_buf, field_name );
2199 if( !field_object || field_object->type == JSON_NULL ) {
2200 buffer_add( val_buf, "DEFAULT" );
2202 } else if( !strcmp( get_primitive( field ), "number" )) {
2203 const char* numtype = get_datatype( field );
2204 if( !strcmp( numtype, "INT8" )) {
2205 buffer_fadd( val_buf, "%lld", atoll( value ));
2207 } else if( !strcmp( numtype, "INT" )) {
2208 buffer_fadd( val_buf, "%d", atoi( value ));
2210 } else if( !strcmp( numtype, "NUMERIC" )) {
2211 buffer_fadd( val_buf, "%f", atof( value ));
2214 if( dbi_conn_quote_string( writehandle, &value )) {
2215 OSRF_BUFFER_ADD( val_buf, value );
2218 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2219 osrfAppSessionStatus(
2221 OSRF_STATUS_INTERNALSERVERERROR,
2222 "osrfMethodException",
2224 "Error quoting string -- please see the error log for more details"
2227 buffer_free( table_buf );
2228 buffer_free( col_buf );
2229 buffer_free( val_buf );
2230 osrfAppRespondComplete( ctx, NULL );
2238 osrfHashIteratorFree( field_itr );
2240 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2241 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2243 char* table_str = buffer_release( table_buf );
2244 char* col_str = buffer_release( col_buf );
2245 char* val_str = buffer_release( val_buf );
2246 growing_buffer* sql = buffer_init( 128 );
2247 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2252 char* query = buffer_release( sql );
2254 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2256 jsonObject* obj = NULL;
2259 dbi_result result = dbi_conn_query( writehandle, query );
2261 obj = jsonNewObject( NULL );
2263 int errnum = dbi_conn_error( writehandle, &msg );
2266 "%s ERROR inserting %s object using query [%s]: %d %s",
2268 osrfHashGet(meta, "fieldmapper"),
2271 msg ? msg : "(No description available)"
2273 osrfAppSessionStatus(
2275 OSRF_STATUS_INTERNALSERVERERROR,
2276 "osrfMethodException",
2278 "INSERT error -- please see the error log for more details"
2280 if( !oilsIsDBConnected( writehandle ))
2281 osrfAppSessionPanic( ctx->session );
2284 dbi_result_free( result );
2286 char* id = oilsFMGetString( target, pkey );
2288 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2289 growing_buffer* _id = buffer_init( 10 );
2290 buffer_fadd( _id, "%lld", new_id );
2291 id = buffer_release( _id );
2294 // Find quietness specification, if present
2295 const char* quiet_str = NULL;
2297 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2299 quiet_str = jsonObjectGetString( quiet_obj );
2302 if( str_is_true( quiet_str )) { // if quietness is specified
2303 obj = jsonNewObject( id );
2307 // Fetch the row that we just inserted, so that we can return it to the client
2308 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2309 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2312 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2316 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2318 jsonObjectFree( list );
2319 jsonObjectFree( where_clause );
2326 osrfAppRespondComplete( ctx, obj );
2327 jsonObjectFree( obj );
2332 @brief Implement the retrieve method.
2333 @param ctx Pointer to the method context.
2334 @param err Pointer through which to return an error code.
2335 @return If successful, a pointer to the result to be returned to the client;
2338 From the method's class, fetch a row with a specified value in the primary key. This
2339 method relies on the database design convention that a primary key consists of a single
2343 - authkey (PCRUD only)
2344 - value of the primary key for the desired row, for building the WHERE clause
2345 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2347 Return to client: One row from the query.
2349 int doRetrieve( osrfMethodContext* ctx ) {
2350 if(osrfMethodVerifyContext( ctx )) {
2351 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2356 timeout_needs_resetting = 1;
2361 if( enforce_pcrud ) {
2366 // Get the class metadata
2367 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2369 // Get the value of the primary key, from a method parameter
2370 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2374 "%s retrieving %s object with primary key value of %s",
2376 osrfHashGet( class_def, "fieldmapper" ),
2377 jsonObjectGetString( id_obj )
2380 // Build a WHERE clause based on the key value
2381 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2384 osrfHashGet( class_def, "primarykey" ), // name of key column
2385 jsonObjectClone( id_obj ) // value of key column
2388 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2392 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2394 jsonObjectFree( where_clause );
2396 osrfAppRespondComplete( ctx, NULL );
2400 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2401 jsonObjectFree( list );
2403 if( enforce_pcrud ) {
2404 // no result, skip this entirely
2405 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2406 jsonObjectFree( obj );
2408 growing_buffer* msg = buffer_init( 128 );
2409 OSRF_BUFFER_ADD( msg, modulename );
2410 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2412 char* m = buffer_release( msg );
2413 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2417 osrfAppRespondComplete( ctx, NULL );
2422 // doFieldmapperSearch() now does the responding for us
2423 //osrfAppRespondComplete( ctx, obj );
2424 osrfAppRespondComplete( ctx, NULL );
2426 jsonObjectFree( obj );
2431 @brief Translate a numeric value to a string representation for the database.
2432 @param field Pointer to the IDL field definition.
2433 @param value Pointer to a jsonObject holding the value of a field.
2434 @return Pointer to a newly allocated string.
2436 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2437 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2438 or (what is worse) valid SQL that is wrong.
2440 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2442 The calling code is responsible for freeing the resulting string by calling free().
2444 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2445 growing_buffer* val_buf = buffer_init( 32 );
2446 const char* numtype = get_datatype( field );
2448 // For historical reasons the following contains cruft that could be cleaned up.
2449 if( !strncmp( numtype, "INT", 3 ) ) {
2450 if( value->type == JSON_NUMBER )
2451 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2452 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2454 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2457 } else if( !strcmp( numtype, "NUMERIC" )) {
2458 if( value->type == JSON_NUMBER )
2459 buffer_fadd( val_buf, jsonObjectGetString( value ));
2461 buffer_fadd( val_buf, jsonObjectGetString( value ));
2465 // Presumably this was really intended to be a string, so quote it
2466 char* str = jsonObjectToSimpleString( value );
2467 if( dbi_conn_quote_string( dbhandle, &str )) {
2468 OSRF_BUFFER_ADD( val_buf, str );
2471 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2473 buffer_free( val_buf );
2478 return buffer_release( val_buf );
2481 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2482 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2483 growing_buffer* sql_buf = buffer_init( 32 );
2489 osrfHashGet( field, "name" )
2493 buffer_add( sql_buf, "IN (" );
2494 } else if( !strcasecmp( op,"not in" )) {
2495 buffer_add( sql_buf, "NOT IN (" );
2497 buffer_add( sql_buf, "IN (" );
2500 if( node->type == JSON_HASH ) {
2501 // subquery predicate
2502 char* subpred = buildQuery( ctx, node, SUBSELECT );
2504 buffer_free( sql_buf );
2508 buffer_add( sql_buf, subpred );
2511 } else if( node->type == JSON_ARRAY ) {
2512 // literal value list
2513 int in_item_index = 0;
2514 int in_item_first = 1;
2515 const jsonObject* in_item;
2516 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2521 buffer_add( sql_buf, ", " );
2524 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2525 osrfLogError( OSRF_LOG_MARK,
2526 "%s: Expected string or number within IN list; found %s",
2527 modulename, json_type( in_item->type ) );
2528 buffer_free( sql_buf );
2532 // Append the literal value -- quoted if not a number
2533 if( JSON_NUMBER == in_item->type ) {
2534 char* val = jsonNumberToDBString( field, in_item );
2535 OSRF_BUFFER_ADD( sql_buf, val );
2538 } else if( !strcmp( get_primitive( field ), "number" )) {
2539 char* val = jsonNumberToDBString( field, in_item );
2540 OSRF_BUFFER_ADD( sql_buf, val );
2544 char* key_string = jsonObjectToSimpleString( in_item );
2545 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2546 OSRF_BUFFER_ADD( sql_buf, key_string );
2549 osrfLogError( OSRF_LOG_MARK,
2550 "%s: Error quoting key string [%s]", modulename, key_string );
2552 buffer_free( sql_buf );
2558 if( in_item_first ) {
2559 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2560 buffer_free( sql_buf );
2564 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2565 modulename, json_type( node->type ));
2566 buffer_free( sql_buf );
2570 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2572 return buffer_release( sql_buf );
2575 // Receive a JSON_ARRAY representing a function call. The first
2576 // entry in the array is the function name. The rest are parameters.
2577 static char* searchValueTransform( const jsonObject* array ) {
2579 if( array->size < 1 ) {
2580 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2584 // Get the function name
2585 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2586 if( func_item->type != JSON_STRING ) {
2587 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2588 modulename, json_type( func_item->type ));
2592 growing_buffer* sql_buf = buffer_init( 32 );
2594 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2595 OSRF_BUFFER_ADD( sql_buf, "( " );
2597 // Get the parameters
2598 int func_item_index = 1; // We already grabbed the zeroth entry
2599 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2601 // Add a separator comma, if we need one
2602 if( func_item_index > 2 )
2603 buffer_add( sql_buf, ", " );
2605 // Add the current parameter
2606 if( func_item->type == JSON_NULL ) {
2607 buffer_add( sql_buf, "NULL" );
2609 if( func_item->type == JSON_BOOL ) {
2610 if( jsonBoolIsTrue(func_item) ) {
2611 buffer_add( sql_buf, "TRUE" );
2613 buffer_add( sql_buf, "FALSE" );
2616 char* val = jsonObjectToSimpleString( func_item );
2617 if( dbi_conn_quote_string( dbhandle, &val )) {
2618 OSRF_BUFFER_ADD( sql_buf, val );
2621 osrfLogError( OSRF_LOG_MARK,
2622 "%s: Error quoting key string [%s]", modulename, val );
2623 buffer_free( sql_buf );
2631 buffer_add( sql_buf, " )" );
2633 return buffer_release( sql_buf );
2636 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2637 const jsonObject* node, const char* op ) {
2639 if( ! is_good_operator( op ) ) {
2640 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2644 char* val = searchValueTransform( node );
2648 growing_buffer* sql_buf = buffer_init( 32 );
2653 osrfHashGet( field, "name" ),
2660 return buffer_release( sql_buf );
2663 // class_alias is a class name or other table alias
2664 // field is a field definition as stored in the IDL
2665 // node comes from the method parameter, and may represent an entry in the SELECT list
2666 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2667 const jsonObject* node ) {
2668 growing_buffer* sql_buf = buffer_init( 32 );
2670 const char* field_transform = jsonObjectGetString(
2671 jsonObjectGetKeyConst( node, "transform" ) );
2672 const char* transform_subcolumn = jsonObjectGetString(
2673 jsonObjectGetKeyConst( node, "result_field" ) );
2675 if( transform_subcolumn ) {
2676 if( ! is_identifier( transform_subcolumn ) ) {
2677 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2678 modulename, transform_subcolumn );
2679 buffer_free( sql_buf );
2682 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2685 if( field_transform ) {
2687 if( ! is_identifier( field_transform ) ) {
2688 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2689 modulename, field_transform );
2690 buffer_free( sql_buf );
2694 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2695 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2696 field_transform, class_alias, osrfHashGet( field, "name" ));
2698 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2699 field_transform, class_alias, osrfHashGet( field, "name" ));
2702 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2705 if( array->type != JSON_ARRAY ) {
2706 osrfLogError( OSRF_LOG_MARK,
2707 "%s: Expected JSON_ARRAY for function params; found %s",
2708 modulename, json_type( array->type ) );
2709 buffer_free( sql_buf );
2712 int func_item_index = 0;
2713 jsonObject* func_item;
2714 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2716 char* val = jsonObjectToSimpleString( func_item );
2719 buffer_add( sql_buf, ",NULL" );
2720 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2721 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2722 OSRF_BUFFER_ADD( sql_buf, val );
2724 osrfLogError( OSRF_LOG_MARK,
2725 "%s: Error quoting key string [%s]", modulename, val );
2727 buffer_free( sql_buf );
2734 buffer_add( sql_buf, " )" );
2737 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2740 if( transform_subcolumn )
2741 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2743 return buffer_release( sql_buf );
2746 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2747 const jsonObject* node, const char* op ) {
2749 if( ! is_good_operator( op ) ) {
2750 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2754 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2755 if( ! field_transform )
2758 int extra_parens = 0; // boolean
2760 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2762 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2764 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2766 free( field_transform );
2770 } else if( value_obj->type == JSON_ARRAY ) {
2771 value = searchValueTransform( value_obj );
2773 osrfLogError( OSRF_LOG_MARK,
2774 "%s: Error building value transform for field transform", modulename );
2775 free( field_transform );
2778 } else if( value_obj->type == JSON_HASH ) {
2779 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2781 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2783 free( field_transform );
2787 } else if( value_obj->type == JSON_NUMBER ) {
2788 value = jsonNumberToDBString( field, value_obj );
2789 } else if( value_obj->type == JSON_NULL ) {
2790 osrfLogError( OSRF_LOG_MARK,
2791 "%s: Error building predicate for field transform: null value", modulename );
2792 free( field_transform );
2794 } else if( value_obj->type == JSON_BOOL ) {
2795 osrfLogError( OSRF_LOG_MARK,
2796 "%s: Error building predicate for field transform: boolean value", modulename );
2797 free( field_transform );
2800 if( !strcmp( get_primitive( field ), "number") ) {
2801 value = jsonNumberToDBString( field, value_obj );
2803 value = jsonObjectToSimpleString( value_obj );
2804 if( !dbi_conn_quote_string( dbhandle, &value )) {
2805 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2806 modulename, value );
2808 free( field_transform );
2814 const char* left_parens = "";
2815 const char* right_parens = "";
2817 if( extra_parens ) {
2822 const char* right_percent = "";
2823 const char* real_op = op;
2825 if( !strcasecmp( op, "startwith") ) {
2827 right_percent = "|| '%'";
2830 growing_buffer* sql_buf = buffer_init( 32 );
2834 "%s%s %s %s %s%s %s%s",
2846 free( field_transform );
2848 return buffer_release( sql_buf );
2851 static char* searchSimplePredicate( const char* op, const char* class_alias,
2852 osrfHash* field, const jsonObject* node ) {
2854 if( ! is_good_operator( op ) ) {
2855 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2861 // Get the value to which we are comparing the specified column
2862 if( node->type != JSON_NULL ) {
2863 if( node->type == JSON_NUMBER ) {
2864 val = jsonNumberToDBString( field, node );
2865 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2866 val = jsonNumberToDBString( field, node );
2868 val = jsonObjectToSimpleString( node );
2873 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2874 // Value is not numeric; enclose it in quotes
2875 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2876 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2883 // Compare to a null value
2884 val = strdup( "NULL" );
2885 if( strcmp( op, "=" ))
2891 growing_buffer* sql_buf = buffer_init( 32 );
2892 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2893 char* pred = buffer_release( sql_buf );
2900 static char* searchBETWEENPredicate( const char* class_alias,
2901 osrfHash* field, const jsonObject* node ) {
2903 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2904 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2906 if( NULL == y_node ) {
2907 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2910 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2911 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2918 if( !strcmp( get_primitive( field ), "number") ) {
2919 x_string = jsonNumberToDBString( field, x_node );
2920 y_string = jsonNumberToDBString( field, y_node );
2923 x_string = jsonObjectToSimpleString( x_node );
2924 y_string = jsonObjectToSimpleString( y_node );
2925 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2926 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2927 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2928 modulename, x_string, y_string );
2935 growing_buffer* sql_buf = buffer_init( 32 );
2936 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2937 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2941 return buffer_release( sql_buf );
2944 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2945 jsonObject* node, osrfMethodContext* ctx ) {
2948 if( node->type == JSON_ARRAY ) { // equality IN search
2949 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2950 } else if( node->type == JSON_HASH ) { // other search
2951 jsonIterator* pred_itr = jsonNewIterator( node );
2952 if( !jsonIteratorHasNext( pred_itr ) ) {
2953 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2954 modulename, osrfHashGet(field, "name" ));
2956 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2958 // Verify that there are no additional predicates
2959 if( jsonIteratorHasNext( pred_itr ) ) {
2960 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2961 modulename, osrfHashGet(field, "name" ));
2962 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2963 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2964 else if( !(strcasecmp( pred_itr->key,"in" ))
2965 || !(strcasecmp( pred_itr->key,"not in" )) )
2966 pred = searchINPredicate(
2967 class_info->alias, field, pred_node, pred_itr->key, ctx );
2968 else if( pred_node->type == JSON_ARRAY )
2969 pred = searchFunctionPredicate(
2970 class_info->alias, field, pred_node, pred_itr->key );
2971 else if( pred_node->type == JSON_HASH )
2972 pred = searchFieldTransformPredicate(
2973 class_info, field, pred_node, pred_itr->key );
2975 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2977 jsonIteratorFree( pred_itr );
2979 } else if( node->type == JSON_NULL ) { // IS NULL search
2980 growing_buffer* _p = buffer_init( 64 );
2983 "\"%s\".%s IS NULL",
2985 osrfHashGet( field, "name" )
2987 pred = buffer_release( _p );
2988 } else { // equality search
2989 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3008 field : call_number,
3024 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3026 const jsonObject* working_hash;
3027 jsonObject* freeable_hash = NULL;
3029 if( join_hash->type == JSON_HASH ) {
3030 working_hash = join_hash;
3031 } else if( join_hash->type == JSON_STRING ) {
3032 // turn it into a JSON_HASH by creating a wrapper
3033 // around a copy of the original
3034 const char* _tmp = jsonObjectGetString( join_hash );
3035 freeable_hash = jsonNewObjectType( JSON_HASH );
3036 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3037 working_hash = freeable_hash;
3041 "%s: JOIN failed; expected JSON object type not found",
3047 growing_buffer* join_buf = buffer_init( 128 );
3048 const char* leftclass = left_info->class_name;
3050 jsonObject* snode = NULL;
3051 jsonIterator* search_itr = jsonNewIterator( working_hash );
3053 while ( (snode = jsonIteratorNext( search_itr )) ) {
3054 const char* right_alias = search_itr->key;
3056 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3058 class = right_alias;
3060 const ClassInfo* right_info = add_joined_class( right_alias, class );
3064 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3068 jsonIteratorFree( search_itr );
3069 buffer_free( join_buf );
3071 jsonObjectFree( freeable_hash );
3074 osrfHash* links = right_info->links;
3075 const char* table = right_info->source_def;
3077 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3078 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3080 if( field && !fkey ) {
3081 // Look up the corresponding join column in the IDL.
3082 // The link must be defined in the child table,
3083 // and point to the right parent table.
3084 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3085 const char* reltype = NULL;
3086 const char* other_class = NULL;
3087 reltype = osrfHashGet( idl_link, "reltype" );
3088 if( reltype && strcmp( reltype, "has_many" ) )
3089 other_class = osrfHashGet( idl_link, "class" );
3090 if( other_class && !strcmp( other_class, leftclass ) )
3091 fkey = osrfHashGet( idl_link, "key" );
3095 "%s: JOIN failed. No link defined from %s.%s to %s",
3101 buffer_free( join_buf );
3103 jsonObjectFree( freeable_hash );
3104 jsonIteratorFree( search_itr );
3108 } else if( !field && fkey ) {
3109 // Look up the corresponding join column in the IDL.
3110 // The link must be defined in the child table,
3111 // and point to the right parent table.
3112 osrfHash* left_links = left_info->links;
3113 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3114 const char* reltype = NULL;
3115 const char* other_class = NULL;
3116 reltype = osrfHashGet( idl_link, "reltype" );
3117 if( reltype && strcmp( reltype, "has_many" ) )
3118 other_class = osrfHashGet( idl_link, "class" );
3119 if( other_class && !strcmp( other_class, class ) )
3120 field = osrfHashGet( idl_link, "key" );
3124 "%s: JOIN failed. No link defined from %s.%s to %s",
3130 buffer_free( join_buf );
3132 jsonObjectFree( freeable_hash );
3133 jsonIteratorFree( search_itr );
3137 } else if( !field && !fkey ) {
3138 osrfHash* left_links = left_info->links;
3140 // For each link defined for the left class:
3141 // see if the link references the joined class
3142 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3143 osrfHash* curr_link = NULL;
3144 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3145 const char* other_class = osrfHashGet( curr_link, "class" );
3146 if( other_class && !strcmp( other_class, class ) ) {
3148 // In the IDL, the parent class doesn't always know then names of the child
3149 // columns that are pointing to it, so don't use that end of the link
3150 const char* reltype = osrfHashGet( curr_link, "reltype" );
3151 if( reltype && strcmp( reltype, "has_many" ) ) {
3152 // Found a link between the classes
3153 fkey = osrfHashIteratorKey( itr );
3154 field = osrfHashGet( curr_link, "key" );
3159 osrfHashIteratorFree( itr );
3161 if( !field || !fkey ) {
3162 // Do another such search, with the classes reversed
3164 // For each link defined for the joined class:
3165 // see if the link references the left class
3166 osrfHashIterator* itr = osrfNewHashIterator( links );
3167 osrfHash* curr_link = NULL;
3168 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3169 const char* other_class = osrfHashGet( curr_link, "class" );
3170 if( other_class && !strcmp( other_class, leftclass ) ) {
3172 // In the IDL, the parent class doesn't know then names of the child
3173 // columns that are pointing to it, so don't use that end of the link
3174 const char* reltype = osrfHashGet( curr_link, "reltype" );
3175 if( reltype && strcmp( reltype, "has_many" ) ) {
3176 // Found a link between the classes
3177 field = osrfHashIteratorKey( itr );
3178 fkey = osrfHashGet( curr_link, "key" );
3183 osrfHashIteratorFree( itr );
3186 if( !field || !fkey ) {
3189 "%s: JOIN failed. No link defined between %s and %s",
3194 buffer_free( join_buf );
3196 jsonObjectFree( freeable_hash );
3197 jsonIteratorFree( search_itr );
3202 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3204 if( !strcasecmp( type,"left" )) {
3205 buffer_add( join_buf, " LEFT JOIN" );
3206 } else if( !strcasecmp( type,"right" )) {
3207 buffer_add( join_buf, " RIGHT JOIN" );
3208 } else if( !strcasecmp( type,"full" )) {
3209 buffer_add( join_buf, " FULL JOIN" );
3211 buffer_add( join_buf, " INNER JOIN" );
3214 buffer_add( join_buf, " INNER JOIN" );
3217 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3218 table, right_alias, right_alias, field, left_info->alias, fkey );
3220 // Add any other join conditions as specified by "filter"
3221 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3223 const char* filter_op = jsonObjectGetString(
3224 jsonObjectGetKeyConst( snode, "filter_op" ) );
3225 if( filter_op && !strcasecmp( "or",filter_op )) {
3226 buffer_add( join_buf, " OR " );
3228 buffer_add( join_buf, " AND " );
3231 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3233 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3234 OSRF_BUFFER_ADD( join_buf, jpred );
3239 "%s: JOIN failed. Invalid conditional expression.",
3242 jsonIteratorFree( search_itr );
3243 buffer_free( join_buf );
3245 jsonObjectFree( freeable_hash );
3250 buffer_add( join_buf, " ) " );
3252 // Recursively add a nested join, if one is present
3253 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3255 char* jpred = searchJOIN( join_filter, right_info );
3257 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3258 OSRF_BUFFER_ADD( join_buf, jpred );
3261 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3262 jsonIteratorFree( search_itr );
3263 buffer_free( join_buf );
3265 jsonObjectFree( freeable_hash );
3272 jsonObjectFree( freeable_hash );
3273 jsonIteratorFree( search_itr );
3275 return buffer_release( join_buf );
3280 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3281 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3282 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3284 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3286 search_hash is the JSON expression of the conditions.
3287 meta is the class definition from the IDL, for the relevant table.
3288 opjoin_type indicates whether multiple conditions, if present, should be
3289 connected by AND or OR.
3290 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3291 to pass it to other functions -- and all they do with it is to use the session
3292 and request members to send error messages back to the client.
3296 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3297 int opjoin_type, osrfMethodContext* ctx ) {
3301 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3302 "opjoin_type = %d, ctx addr = %p",
3305 class_info->class_def,
3310 growing_buffer* sql_buf = buffer_init( 128 );
3312 jsonObject* node = NULL;
3315 if( search_hash->type == JSON_ARRAY ) {
3316 if( 0 == search_hash->size ) {
3319 "%s: Invalid predicate structure: empty JSON array",
3322 buffer_free( sql_buf );
3326 unsigned long i = 0;
3327 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3331 if( opjoin_type == OR_OP_JOIN )
3332 buffer_add( sql_buf, " OR " );
3334 buffer_add( sql_buf, " AND " );
3337 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3339 buffer_free( sql_buf );
3343 buffer_fadd( sql_buf, "( %s )", subpred );
3347 } else if( search_hash->type == JSON_HASH ) {
3348 osrfLogDebug( OSRF_LOG_MARK,
3349 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3350 jsonIterator* search_itr = jsonNewIterator( search_hash );
3351 if( !jsonIteratorHasNext( search_itr ) ) {
3354 "%s: Invalid predicate structure: empty JSON object",
3357 jsonIteratorFree( search_itr );
3358 buffer_free( sql_buf );
3362 while( (node = jsonIteratorNext( search_itr )) ) {
3367 if( opjoin_type == OR_OP_JOIN )
3368 buffer_add( sql_buf, " OR " );
3370 buffer_add( sql_buf, " AND " );
3373 if( '+' == search_itr->key[ 0 ] ) {
3375 // This plus sign prefixes a class name or other table alias;
3376 // make sure the table alias is in scope
3377 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3378 if( ! alias_info ) {
3381 "%s: Invalid table alias \"%s\" in WHERE clause",
3385 jsonIteratorFree( search_itr );
3386 buffer_free( sql_buf );
3390 if( node->type == JSON_STRING ) {
3391 // It's the name of a column; make sure it belongs to the class
3392 const char* fieldname = jsonObjectGetString( node );
3393 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3396 "%s: Invalid column name \"%s\" in WHERE clause "
3397 "for table alias \"%s\"",
3402 jsonIteratorFree( search_itr );
3403 buffer_free( sql_buf );
3407 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3409 // It's something more complicated
3410 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3412 jsonIteratorFree( search_itr );
3413 buffer_free( sql_buf );
3417 buffer_fadd( sql_buf, "( %s )", subpred );
3420 } else if( '-' == search_itr->key[ 0 ] ) {
3421 if( !strcasecmp( "-or", search_itr->key )) {
3422 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3424 jsonIteratorFree( search_itr );
3425 buffer_free( sql_buf );
3429 buffer_fadd( sql_buf, "( %s )", subpred );
3431 } else if( !strcasecmp( "-and", search_itr->key )) {
3432 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3434 jsonIteratorFree( search_itr );
3435 buffer_free( sql_buf );
3439 buffer_fadd( sql_buf, "( %s )", subpred );
3441 } else if( !strcasecmp("-not",search_itr->key) ) {
3442 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3444 jsonIteratorFree( search_itr );
3445 buffer_free( sql_buf );
3449 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3451 } else if( !strcasecmp( "-exists", search_itr->key )) {
3452 char* subpred = buildQuery( ctx, node, SUBSELECT );
3454 jsonIteratorFree( search_itr );
3455 buffer_free( sql_buf );
3459 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3461 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3462 char* subpred = buildQuery( ctx, node, SUBSELECT );
3464 jsonIteratorFree( search_itr );
3465 buffer_free( sql_buf );
3469 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3471 } else { // Invalid "minus" operator
3474 "%s: Invalid operator \"%s\" in WHERE clause",
3478 jsonIteratorFree( search_itr );
3479 buffer_free( sql_buf );
3485 const char* class = class_info->class_name;
3486 osrfHash* fields = class_info->fields;
3487 osrfHash* field = osrfHashGet( fields, search_itr->key );
3490 const char* table = class_info->source_def;
3493 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3496 table ? table : "?",
3499 jsonIteratorFree( search_itr );
3500 buffer_free( sql_buf );
3504 char* subpred = searchPredicate( class_info, field, node, ctx );
3506 buffer_free( sql_buf );
3507 jsonIteratorFree( search_itr );
3511 buffer_add( sql_buf, subpred );
3515 jsonIteratorFree( search_itr );
3518 // ERROR ... only hash and array allowed at this level
3519 char* predicate_string = jsonObjectToJSON( search_hash );
3522 "%s: Invalid predicate structure: %s",
3526 buffer_free( sql_buf );
3527 free( predicate_string );
3531 return buffer_release( sql_buf );
3534 /* Build a JSON_ARRAY of field names for a given table alias
3536 static jsonObject* defaultSelectList( const char* table_alias ) {
3541 ClassInfo* class_info = search_all_alias( table_alias );
3542 if( ! class_info ) {
3545 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3552 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3553 osrfHash* field_def = NULL;
3554 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3555 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3556 const char* field_name = osrfHashIteratorKey( field_itr );
3557 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3558 jsonObjectPush( array, jsonNewObject( field_name ) );
3561 osrfHashIteratorFree( field_itr );
3566 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3567 // The jsonObject must be a JSON_HASH with an single entry for "union",
3568 // "intersect", or "except". The data associated with this key must be an
3569 // array of hashes, each hash being a query.
3570 // Also allowed but currently ignored: entries for "order_by" and "alias".
3571 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3573 if( ! combo || combo->type != JSON_HASH )
3574 return NULL; // should be impossible; validated by caller
3576 const jsonObject* query_array = NULL; // array of subordinate queries
3577 const char* op = NULL; // name of operator, e.g. UNION
3578 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3579 int op_count = 0; // for detecting conflicting operators
3580 int excepting = 0; // boolean
3581 int all = 0; // boolean
3582 jsonObject* order_obj = NULL;
3584 // Identify the elements in the hash
3585 jsonIterator* query_itr = jsonNewIterator( combo );
3586 jsonObject* curr_obj = NULL;
3587 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3588 if( ! strcmp( "union", query_itr->key ) ) {
3591 query_array = curr_obj;
3592 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3595 query_array = curr_obj;
3596 } else if( ! strcmp( "except", query_itr->key ) ) {
3600 query_array = curr_obj;
3601 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3604 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3607 order_obj = curr_obj;
3608 } else if( ! strcmp( "alias", query_itr->key ) ) {
3609 if( curr_obj->type != JSON_STRING ) {
3610 jsonIteratorFree( query_itr );
3613 alias = jsonObjectGetString( curr_obj );
3614 } else if( ! strcmp( "all", query_itr->key ) ) {
3615 if( obj_is_true( curr_obj ) )
3619 osrfAppSessionStatus(
3621 OSRF_STATUS_INTERNALSERVERERROR,
3622 "osrfMethodException",
3624 "Malformed query; unexpected entry in query object"
3628 "%s: Unexpected entry for \"%s\" in%squery",
3633 jsonIteratorFree( query_itr );
3637 jsonIteratorFree( query_itr );
3639 // More sanity checks
3640 if( ! query_array ) {
3642 osrfAppSessionStatus(
3644 OSRF_STATUS_INTERNALSERVERERROR,
3645 "osrfMethodException",
3647 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3651 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3654 return NULL; // should be impossible...
3655 } else if( op_count > 1 ) {
3657 osrfAppSessionStatus(
3659 OSRF_STATUS_INTERNALSERVERERROR,
3660 "osrfMethodException",
3662 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3666 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3670 } if( query_array->type != JSON_ARRAY ) {
3672 osrfAppSessionStatus(
3674 OSRF_STATUS_INTERNALSERVERERROR,
3675 "osrfMethodException",
3677 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3681 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3684 json_type( query_array->type )
3687 } if( query_array->size < 2 ) {
3689 osrfAppSessionStatus(
3691 OSRF_STATUS_INTERNALSERVERERROR,
3692 "osrfMethodException",
3694 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3698 "%s:%srequires multiple queries as operands",
3703 } else if( excepting && query_array->size > 2 ) {
3705 osrfAppSessionStatus(
3707 OSRF_STATUS_INTERNALSERVERERROR,
3708 "osrfMethodException",
3710 "EXCEPT operator has too many queries as operands"
3714 "%s:EXCEPT operator has too many queries as operands",
3718 } else if( order_obj && ! alias ) {
3720 osrfAppSessionStatus(
3722 OSRF_STATUS_INTERNALSERVERERROR,
3723 "osrfMethodException",
3725 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3729 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3735 // So far so good. Now build the SQL.
3736 growing_buffer* sql = buffer_init( 256 );
3738 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3739 // Add a layer of parentheses
3740 if( flags & SUBCOMBO )
3741 OSRF_BUFFER_ADD( sql, "( " );
3743 // Traverse the query array. Each entry should be a hash.
3744 int first = 1; // boolean
3746 jsonObject* query = NULL;
3747 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3748 if( query->type != JSON_HASH ) {
3750 osrfAppSessionStatus(
3752 OSRF_STATUS_INTERNALSERVERERROR,
3753 "osrfMethodException",
3755 "Malformed query under UNION, INTERSECT or EXCEPT"
3759 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3762 json_type( query->type )
3771 OSRF_BUFFER_ADD( sql, op );
3773 OSRF_BUFFER_ADD( sql, "ALL " );
3776 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3780 "%s: Error building query under%s",
3788 OSRF_BUFFER_ADD( sql, query_str );
3791 if( flags & SUBCOMBO )
3792 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3794 if( !(flags & SUBSELECT) )
3795 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3797 return buffer_release( sql );
3800 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3801 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3802 // or "except" to indicate the type of query.
3803 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3807 osrfAppSessionStatus(
3809 OSRF_STATUS_INTERNALSERVERERROR,
3810 "osrfMethodException",
3812 "Malformed query; no query object"
3814 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3816 } else if( query->type != JSON_HASH ) {
3818 osrfAppSessionStatus(
3820 OSRF_STATUS_INTERNALSERVERERROR,
3821 "osrfMethodException",
3823 "Malformed query object"
3827 "%s: Query object is %s instead of JSON_HASH",
3829 json_type( query->type )
3834 // Determine what kind of query it purports to be, and dispatch accordingly.
3835 if( jsonObjectGetKeyConst( query, "union" ) ||
3836 jsonObjectGetKeyConst( query, "intersect" ) ||
3837 jsonObjectGetKeyConst( query, "except" )) {
3838 return doCombo( ctx, query, flags );
3840 // It is presumably a SELECT query
3842 // Push a node onto the stack for the current query. Every level of
3843 // subquery gets its own QueryFrame on the Stack.
3846 // Build an SQL SELECT statement
3849 jsonObjectGetKey( query, "select" ),
3850 jsonObjectGetKeyConst( query, "from" ),
3851 jsonObjectGetKeyConst( query, "where" ),
3852 jsonObjectGetKeyConst( query, "having" ),
3853 jsonObjectGetKeyConst( query, "order_by" ),
3854 jsonObjectGetKeyConst( query, "limit" ),
3855 jsonObjectGetKeyConst( query, "offset" ),
3864 /* method context */ osrfMethodContext* ctx,
3866 /* SELECT */ jsonObject* selhash,
3867 /* FROM */ const jsonObject* join_hash,
3868 /* WHERE */ const jsonObject* search_hash,
3869 /* HAVING */ const jsonObject* having_hash,
3870 /* ORDER BY */ const jsonObject* order_hash,
3871 /* LIMIT */ const jsonObject* limit,
3872 /* OFFSET */ const jsonObject* offset,
3873 /* flags */ int flags
3875 const char* locale = osrf_message_get_last_locale();
3877 // general tmp objects
3878 const jsonObject* tmp_const;
3879 jsonObject* selclass = NULL;
3880 jsonObject* snode = NULL;
3881 jsonObject* onode = NULL;
3883 char* string = NULL;
3884 int from_function = 0;
3889 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3891 // punt if there's no FROM clause
3892 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3895 "%s: FROM clause is missing or empty",
3899 osrfAppSessionStatus(
3901 OSRF_STATUS_INTERNALSERVERERROR,
3902 "osrfMethodException",
3904 "FROM clause is missing or empty in JSON query"
3909 // the core search class
3910 const char* core_class = NULL;
3912 // get the core class -- the only key of the top level FROM clause, or a string
3913 if( join_hash->type == JSON_HASH ) {
3914 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3915 snode = jsonIteratorNext( tmp_itr );
3917 // Populate the current QueryFrame with information
3918 // about the core class
3919 if( add_query_core( NULL, tmp_itr->key ) ) {
3921 osrfAppSessionStatus(
3923 OSRF_STATUS_INTERNALSERVERERROR,
3924 "osrfMethodException",
3926 "Unable to look up core class"
3930 core_class = curr_query->core.class_name;
3933 jsonObject* extra = jsonIteratorNext( tmp_itr );
3935 jsonIteratorFree( tmp_itr );
3938 // There shouldn't be more than one entry in join_hash
3942 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3946 osrfAppSessionStatus(
3948 OSRF_STATUS_INTERNALSERVERERROR,
3949 "osrfMethodException",
3951 "Malformed FROM clause in JSON query"
3953 return NULL; // Malformed join_hash; extra entry
3955 } else if( join_hash->type == JSON_ARRAY ) {
3956 // We're selecting from a function, not from a table
3958 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3961 } else if( join_hash->type == JSON_STRING ) {
3962 // Populate the current QueryFrame with information
3963 // about the core class
3964 core_class = jsonObjectGetString( join_hash );
3966 if( add_query_core( NULL, core_class ) ) {
3968 osrfAppSessionStatus(
3970 OSRF_STATUS_INTERNALSERVERERROR,
3971 "osrfMethodException",
3973 "Unable to look up core class"
3981 "%s: FROM clause is unexpected JSON type: %s",
3983 json_type( join_hash->type )
3986 osrfAppSessionStatus(
3988 OSRF_STATUS_INTERNALSERVERERROR,
3989 "osrfMethodException",
3991 "Ill-formed FROM clause in JSON query"
3996 // Build the join clause, if any, while filling out the list
3997 // of joined classes in the current QueryFrame.
3998 char* join_clause = NULL;
3999 if( join_hash && ! from_function ) {
4001 join_clause = searchJOIN( join_hash, &curr_query->core );
4002 if( ! join_clause ) {
4004 osrfAppSessionStatus(
4006 OSRF_STATUS_INTERNALSERVERERROR,
4007 "osrfMethodException",
4009 "Unable to construct JOIN clause(s)"
4015 // For in case we don't get a select list
4016 jsonObject* defaultselhash = NULL;
4018 // if there is no select list, build a default select list ...
4019 if( !selhash && !from_function ) {
4020 jsonObject* default_list = defaultSelectList( core_class );
4021 if( ! default_list ) {
4023 osrfAppSessionStatus(
4025 OSRF_STATUS_INTERNALSERVERERROR,
4026 "osrfMethodException",
4028 "Unable to build default SELECT clause in JSON query"
4030 free( join_clause );
4035 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4036 jsonObjectSetKey( selhash, core_class, default_list );
4039 // The SELECT clause can be encoded only by a hash
4040 if( !from_function && selhash->type != JSON_HASH ) {
4043 "%s: Expected JSON_HASH for SELECT clause; found %s",
4045 json_type( selhash->type )
4049 osrfAppSessionStatus(
4051 OSRF_STATUS_INTERNALSERVERERROR,
4052 "osrfMethodException",
4054 "Malformed SELECT clause in JSON query"
4056 free( join_clause );
4060 // If you see a null or wild card specifier for the core class, or an
4061 // empty array, replace it with a default SELECT list
4062 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4064 int default_needed = 0; // boolean
4065 if( JSON_STRING == tmp_const->type
4066 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4068 else if( JSON_NULL == tmp_const->type )
4071 if( default_needed ) {
4072 // Build a default SELECT list
4073 jsonObject* default_list = defaultSelectList( core_class );
4074 if( ! default_list ) {
4076 osrfAppSessionStatus(
4078 OSRF_STATUS_INTERNALSERVERERROR,
4079 "osrfMethodException",
4081 "Can't build default SELECT clause in JSON query"
4083 free( join_clause );
4088 jsonObjectSetKey( selhash, core_class, default_list );
4092 // temp buffers for the SELECT list and GROUP BY clause
4093 growing_buffer* select_buf = buffer_init( 128 );
4094 growing_buffer* group_buf = buffer_init( 128 );
4096 int aggregate_found = 0; // boolean
4098 // Build a select list
4099 if( from_function ) // From a function we select everything
4100 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4103 // Build the SELECT list as SQL
4107 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4108 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4110 const char* cname = selclass_itr->key;
4112 // Make sure the target relation is in the FROM clause.
4114 // At this point join_hash is a step down from the join_hash we
4115 // received as a parameter. If the original was a JSON_STRING,
4116 // then json_hash is now NULL. If the original was a JSON_HASH,
4117 // then json_hash is now the first (and only) entry in it,
4118 // denoting the core class. We've already excluded the
4119 // possibility that the original was a JSON_ARRAY, because in
4120 // that case from_function would be non-NULL, and we wouldn't
4123 // If the current table alias isn't in scope, bail out
4124 ClassInfo* class_info = search_alias( cname );
4125 if( ! class_info ) {
4128 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4133 osrfAppSessionStatus(
4135 OSRF_STATUS_INTERNALSERVERERROR,
4136 "osrfMethodException",
4138 "Selected class not in FROM clause in JSON query"
4140 jsonIteratorFree( selclass_itr );
4141 buffer_free( select_buf );
4142 buffer_free( group_buf );
4143 if( defaultselhash )
4144 jsonObjectFree( defaultselhash );
4145 free( join_clause );
4149 if( selclass->type != JSON_ARRAY ) {
4152 "%s: Malformed SELECT list for class \"%s\"; not an array",
4157 osrfAppSessionStatus(
4159 OSRF_STATUS_INTERNALSERVERERROR,
4160 "osrfMethodException",
4162 "Selected class not in FROM clause in JSON query"
4165 jsonIteratorFree( selclass_itr );
4166 buffer_free( select_buf );
4167 buffer_free( group_buf );
4168 if( defaultselhash )
4169 jsonObjectFree( defaultselhash );
4170 free( join_clause );
4174 // Look up some attributes of the current class
4175 osrfHash* idlClass = class_info->class_def;
4176 osrfHash* class_field_set = class_info->fields;
4177 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4178 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4180 if( 0 == selclass->size ) {
4183 "%s: No columns selected from \"%s\"",
4189 // stitch together the column list for the current table alias...
4190 unsigned long field_idx = 0;
4191 jsonObject* selfield = NULL;
4192 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4194 // If we need a separator comma, add one
4198 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4201 // if the field specification is a string, add it to the list
4202 if( selfield->type == JSON_STRING ) {
4204 // Look up the field in the IDL
4205 const char* col_name = jsonObjectGetString( selfield );
4206 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4208 // No such field in current class
4211 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4217 osrfAppSessionStatus(
4219 OSRF_STATUS_INTERNALSERVERERROR,
4220 "osrfMethodException",
4222 "Selected column not defined in JSON query"
4224 jsonIteratorFree( selclass_itr );
4225 buffer_free( select_buf );
4226 buffer_free( group_buf );
4227 if( defaultselhash )
4228 jsonObjectFree( defaultselhash );
4229 free( join_clause );
4231 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4232 // Virtual field not allowed
4235 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4241 osrfAppSessionStatus(
4243 OSRF_STATUS_INTERNALSERVERERROR,
4244 "osrfMethodException",
4246 "Selected column may not be virtual in JSON query"
4248 jsonIteratorFree( selclass_itr );
4249 buffer_free( select_buf );
4250 buffer_free( group_buf );
4251 if( defaultselhash )
4252 jsonObjectFree( defaultselhash );
4253 free( join_clause );
4259 if( flags & DISABLE_I18N )
4262 i18n = osrfHashGet( field_def, "i18n" );
4264 if( str_is_true( i18n ) ) {
4265 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4266 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4267 class_tname, cname, col_name, class_pkey,
4268 cname, class_pkey, locale, col_name );
4270 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4271 cname, col_name, col_name );
4274 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4275 cname, col_name, col_name );
4278 // ... but it could be an object, in which case we check for a Field Transform
4279 } else if( selfield->type == JSON_HASH ) {
4281 const char* col_name = jsonObjectGetString(
4282 jsonObjectGetKeyConst( selfield, "column" ) );
4284 // Get the field definition from the IDL
4285 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4287 // No such field in current class
4290 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4296 osrfAppSessionStatus(
4298 OSRF_STATUS_INTERNALSERVERERROR,
4299 "osrfMethodException",
4301 "Selected column is not defined in JSON query"
4303 jsonIteratorFree( selclass_itr );
4304 buffer_free( select_buf );
4305 buffer_free( group_buf );
4306 if( defaultselhash )
4307 jsonObjectFree( defaultselhash );
4308 free( join_clause );
4310 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4311 // No such field in current class
4314 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4320 osrfAppSessionStatus(
4322 OSRF_STATUS_INTERNALSERVERERROR,
4323 "osrfMethodException",
4325 "Selected column is virtual in JSON query"
4327 jsonIteratorFree( selclass_itr );
4328 buffer_free( select_buf );
4329 buffer_free( group_buf );
4330 if( defaultselhash )
4331 jsonObjectFree( defaultselhash );
4332 free( join_clause );
4336 // Decide what to use as a column alias
4338 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4339 _alias = jsonObjectGetString( tmp_const );
4340 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4341 _alias = jsonObjectGetString( tmp_const );
4342 } else { // Use field name as the alias
4346 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4347 char* transform_str = searchFieldTransform(
4348 class_info->alias, field_def, selfield );
4349 if( transform_str ) {
4350 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4351 free( transform_str );
4354 osrfAppSessionStatus(
4356 OSRF_STATUS_INTERNALSERVERERROR,
4357 "osrfMethodException",
4359 "Unable to generate transform function in JSON query"
4361 jsonIteratorFree( selclass_itr );
4362 buffer_free( select_buf );
4363 buffer_free( group_buf );
4364 if( defaultselhash )
4365 jsonObjectFree( defaultselhash );
4366 free( join_clause );
4373 if( flags & DISABLE_I18N )
4376 i18n = osrfHashGet( field_def, "i18n" );
4378 if( str_is_true( i18n ) ) {
4379 buffer_fadd( select_buf,
4380 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4381 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4382 class_tname, cname, col_name, class_pkey, cname,
4383 class_pkey, locale, _alias );
4385 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4386 cname, col_name, _alias );
4389 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4390 cname, col_name, _alias );
4397 "%s: Selected item is unexpected JSON type: %s",
4399 json_type( selfield->type )
4402 osrfAppSessionStatus(
4404 OSRF_STATUS_INTERNALSERVERERROR,
4405 "osrfMethodException",
4407 "Ill-formed SELECT item in JSON query"
4409 jsonIteratorFree( selclass_itr );
4410 buffer_free( select_buf );
4411 buffer_free( group_buf );
4412 if( defaultselhash )
4413 jsonObjectFree( defaultselhash );
4414 free( join_clause );
4418 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4419 if( obj_is_true( agg_obj ) )
4420 aggregate_found = 1;
4422 // Append a comma (except for the first one)
4423 // and add the column to a GROUP BY clause
4427 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4429 buffer_fadd( group_buf, " %d", sel_pos );
4433 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4435 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( elfield, "aggregate");
4436 if ( ! obj_is_true( aggregate_obj ) ) {
4440 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4443 buffer_fadd(group_buf, " %d", sel_pos);
4446 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4450 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4453 _column = searchFieldTransform(class_info->alias, field, selfield);
4454 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4455 OSRF_BUFFER_ADD(group_buf, _column);
4456 _column = searchFieldTransform(class_info->alias, field, selfield);
4463 } // end while -- iterating across SELECT columns
4465 } // end while -- iterating across classes
4467 jsonIteratorFree( selclass_itr );
4470 char* col_list = buffer_release( select_buf );
4472 // Make sure the SELECT list isn't empty. This can happen, for example,
4473 // if we try to build a default SELECT clause from a non-core table.
4476 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4478 osrfAppSessionStatus(
4480 OSRF_STATUS_INTERNALSERVERERROR,
4481 "osrfMethodException",
4483 "SELECT list is empty"
4486 buffer_free( group_buf );
4487 if( defaultselhash )
4488 jsonObjectFree( defaultselhash );
4489 free( join_clause );
4495 table = searchValueTransform( join_hash );
4497 table = strdup( curr_query->core.source_def );
4501 osrfAppSessionStatus(
4503 OSRF_STATUS_INTERNALSERVERERROR,
4504 "osrfMethodException",
4506 "Unable to identify table for core class"
4509 buffer_free( group_buf );
4510 if( defaultselhash )
4511 jsonObjectFree( defaultselhash );
4512 free( join_clause );
4516 // Put it all together
4517 growing_buffer* sql_buf = buffer_init( 128 );
4518 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4522 // Append the join clause, if any
4524 buffer_add(sql_buf, join_clause );
4525 free( join_clause );
4528 char* order_by_list = NULL;
4529 char* having_buf = NULL;
4531 if( !from_function ) {
4533 // Build a WHERE clause, if there is one
4535 buffer_add( sql_buf, " WHERE " );
4537 // and it's on the WHERE clause
4538 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4541 osrfAppSessionStatus(
4543 OSRF_STATUS_INTERNALSERVERERROR,
4544 "osrfMethodException",
4546 "Severe query error in WHERE predicate -- see error log for more details"
4549 buffer_free( group_buf );
4550 buffer_free( sql_buf );
4551 if( defaultselhash )
4552 jsonObjectFree( defaultselhash );
4556 buffer_add( sql_buf, pred );
4560 // Build a HAVING clause, if there is one
4563 // and it's on the the WHERE clause
4564 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4566 if( ! having_buf ) {
4568 osrfAppSessionStatus(
4570 OSRF_STATUS_INTERNALSERVERERROR,
4571 "osrfMethodException",
4573 "Severe query error in HAVING predicate -- see error log for more details"
4576 buffer_free( group_buf );
4577 buffer_free( sql_buf );
4578 if( defaultselhash )
4579 jsonObjectFree( defaultselhash );
4584 // Build an ORDER BY clause, if there is one
4585 if( NULL == order_hash )
4586 ; // No ORDER BY? do nothing
4587 else if( JSON_ARRAY == order_hash->type ) {
4588 order_by_list = buildOrderByFromArray( ctx, order_hash );
4589 if( !order_by_list ) {
4591 buffer_free( group_buf );
4592 buffer_free( sql_buf );
4593 if( defaultselhash )
4594 jsonObjectFree( defaultselhash );
4597 } else if( JSON_HASH == order_hash->type ) {
4598 // This hash is keyed on class alias. Each class has either
4599 // an array of field names or a hash keyed on field name.
4600 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4601 jsonIterator* class_itr = jsonNewIterator( order_hash );
4602 while( (snode = jsonIteratorNext( class_itr )) ) {
4604 ClassInfo* order_class_info = search_alias( class_itr->key );
4605 if( ! order_class_info ) {
4606 osrfLogError( OSRF_LOG_MARK,
4607 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4608 modulename, class_itr->key );
4610 osrfAppSessionStatus(
4612 OSRF_STATUS_INTERNALSERVERERROR,
4613 "osrfMethodException",
4615 "Invalid class referenced in ORDER BY clause -- "
4616 "see error log for more details"
4618 jsonIteratorFree( class_itr );
4619 buffer_free( order_buf );
4621 buffer_free( group_buf );
4622 buffer_free( sql_buf );
4623 if( defaultselhash )
4624 jsonObjectFree( defaultselhash );
4628 osrfHash* field_list_def = order_class_info->fields;
4630 if( snode->type == JSON_HASH ) {
4632 // Hash is keyed on field names from the current class. For each field
4633 // there is another layer of hash to define the sorting details, if any,
4634 // or a string to indicate direction of sorting.
4635 jsonIterator* order_itr = jsonNewIterator( snode );
4636 while( (onode = jsonIteratorNext( order_itr )) ) {
4638 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4640 osrfLogError( OSRF_LOG_MARK,
4641 "%s: Invalid field \"%s\" in ORDER BY clause",
4642 modulename, order_itr->key );
4644 osrfAppSessionStatus(
4646 OSRF_STATUS_INTERNALSERVERERROR,
4647 "osrfMethodException",
4649 "Invalid field in ORDER BY clause -- "
4650 "see error log for more details"
4652 jsonIteratorFree( order_itr );
4653 jsonIteratorFree( class_itr );
4654 buffer_free( order_buf );
4656 buffer_free( group_buf );
4657 buffer_free( sql_buf );
4658 if( defaultselhash )
4659 jsonObjectFree( defaultselhash );
4661 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4662 osrfLogError( OSRF_LOG_MARK,
4663 "%s: Virtual field \"%s\" in ORDER BY clause",
4664 modulename, order_itr->key );
4666 osrfAppSessionStatus(
4668 OSRF_STATUS_INTERNALSERVERERROR,
4669 "osrfMethodException",
4671 "Virtual field in ORDER BY clause -- "
4672 "see error log for more details"
4674 jsonIteratorFree( order_itr );
4675 jsonIteratorFree( class_itr );
4676 buffer_free( order_buf );
4678 buffer_free( group_buf );
4679 buffer_free( sql_buf );
4680 if( defaultselhash )
4681 jsonObjectFree( defaultselhash );
4685 const char* direction = NULL;
4686 if( onode->type == JSON_HASH ) {
4687 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4688 string = searchFieldTransform(
4690 osrfHashGet( field_list_def, order_itr->key ),
4694 if( ctx ) osrfAppSessionStatus(
4696 OSRF_STATUS_INTERNALSERVERERROR,
4697 "osrfMethodException",
4699 "Severe query error in ORDER BY clause -- "
4700 "see error log for more details"
4702 jsonIteratorFree( order_itr );
4703 jsonIteratorFree( class_itr );
4705 buffer_free( group_buf );
4706 buffer_free( order_buf);
4707 buffer_free( sql_buf );
4708 if( defaultselhash )
4709 jsonObjectFree( defaultselhash );
4713 growing_buffer* field_buf = buffer_init( 16 );
4714 buffer_fadd( field_buf, "\"%s\".%s",
4715 class_itr->key, order_itr->key );
4716 string = buffer_release( field_buf );
4719 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4720 const char* dir = jsonObjectGetString( tmp_const );
4721 if(!strncasecmp( dir, "d", 1 )) {
4722 direction = " DESC";
4728 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4729 osrfLogError( OSRF_LOG_MARK,
4730 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4731 modulename, json_type( onode->type ) );
4733 osrfAppSessionStatus(
4735 OSRF_STATUS_INTERNALSERVERERROR,
4736 "osrfMethodException",
4738 "Malformed ORDER BY clause -- see error log for more details"
4740 jsonIteratorFree( order_itr );
4741 jsonIteratorFree( class_itr );
4743 buffer_free( group_buf );
4744 buffer_free( order_buf );
4745 buffer_free( sql_buf );
4746 if( defaultselhash )
4747 jsonObjectFree( defaultselhash );
4751 string = strdup( order_itr->key );
4752 const char* dir = jsonObjectGetString( onode );
4753 if( !strncasecmp( dir, "d", 1 )) {
4754 direction = " DESC";
4761 OSRF_BUFFER_ADD( order_buf, ", " );
4763 order_buf = buffer_init( 128 );
4765 OSRF_BUFFER_ADD( order_buf, string );
4769 OSRF_BUFFER_ADD( order_buf, direction );
4773 jsonIteratorFree( order_itr );
4775 } else if( snode->type == JSON_ARRAY ) {
4777 // Array is a list of fields from the current class
4778 unsigned long order_idx = 0;
4779 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4781 const char* _f = jsonObjectGetString( onode );
4783 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4785 osrfLogError( OSRF_LOG_MARK,
4786 "%s: Invalid field \"%s\" in ORDER BY clause",
4789 osrfAppSessionStatus(
4791 OSRF_STATUS_INTERNALSERVERERROR,
4792 "osrfMethodException",
4794 "Invalid field in ORDER BY clause -- "
4795 "see error log for more details"
4797 jsonIteratorFree( class_itr );
4798 buffer_free( order_buf );
4800 buffer_free( group_buf );
4801 buffer_free( sql_buf );
4802 if( defaultselhash )
4803 jsonObjectFree( defaultselhash );
4805 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4806 osrfLogError( OSRF_LOG_MARK,
4807 "%s: Virtual field \"%s\" in ORDER BY clause",
4810 osrfAppSessionStatus(
4812 OSRF_STATUS_INTERNALSERVERERROR,
4813 "osrfMethodException",
4815 "Virtual field in ORDER BY clause -- "
4816 "see error log for more details"
4818 jsonIteratorFree( class_itr );
4819 buffer_free( order_buf );
4821 buffer_free( group_buf );
4822 buffer_free( sql_buf );
4823 if( defaultselhash )
4824 jsonObjectFree( defaultselhash );
4829 OSRF_BUFFER_ADD( order_buf, ", " );
4831 order_buf = buffer_init( 128 );
4833 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4837 // IT'S THE OOOOOOOOOOOLD STYLE!
4839 osrfLogError( OSRF_LOG_MARK,
4840 "%s: Possible SQL injection attempt; direct order by is not allowed",
4843 osrfAppSessionStatus(
4845 OSRF_STATUS_INTERNALSERVERERROR,
4846 "osrfMethodException",
4848 "Severe query error -- see error log for more details"
4853 buffer_free( group_buf );
4854 buffer_free( order_buf );
4855 buffer_free( sql_buf );
4856 if( defaultselhash )
4857 jsonObjectFree( defaultselhash );
4858 jsonIteratorFree( class_itr );
4862 jsonIteratorFree( class_itr );
4864 order_by_list = buffer_release( order_buf );
4866 osrfLogError( OSRF_LOG_MARK,
4867 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4868 modulename, json_type( order_hash->type ) );
4870 osrfAppSessionStatus(
4872 OSRF_STATUS_INTERNALSERVERERROR,
4873 "osrfMethodException",
4875 "Malformed ORDER BY clause -- see error log for more details"
4878 buffer_free( group_buf );
4879 buffer_free( sql_buf );
4880 if( defaultselhash )
4881 jsonObjectFree( defaultselhash );
4886 string = buffer_release( group_buf );
4888 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4889 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4890 OSRF_BUFFER_ADD( sql_buf, string );
4895 if( having_buf && *having_buf ) {
4896 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4897 OSRF_BUFFER_ADD( sql_buf, having_buf );
4901 if( order_by_list ) {
4903 if( *order_by_list ) {
4904 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4905 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4908 free( order_by_list );
4912 const char* str = jsonObjectGetString( limit );
4913 if (str) { // limit could be JSON_NULL, etc.
4914 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4919 const char* str = jsonObjectGetString( offset );
4921 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4925 if( !(flags & SUBSELECT) )
4926 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4928 if( defaultselhash )
4929 jsonObjectFree( defaultselhash );
4931 return buffer_release( sql_buf );
4933 } // end of SELECT()
4936 @brief Build a list of ORDER BY expressions.
4937 @param ctx Pointer to the method context.
4938 @param order_array Pointer to a JSON_ARRAY of field specifications.
4939 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
4940 Each expression may be either a column reference or a function call whose first parameter
4941 is a column reference.
4943 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
4944 It may optionally include entries for "direction" and/or "transform".
4946 The calling code is responsible for freeing the returned string.
4948 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
4949 if( ! order_array ) {
4950 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
4953 osrfAppSessionStatus(
4955 OSRF_STATUS_INTERNALSERVERERROR,
4956 "osrfMethodException",
4958 "Logic error: ORDER BY clause expected, not found; "
4959 "see error log for more details"
4962 } else if( order_array->type != JSON_ARRAY ) {
4963 osrfLogError( OSRF_LOG_MARK,
4964 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
4966 osrfAppSessionStatus(
4968 OSRF_STATUS_INTERNALSERVERERROR,
4969 "osrfMethodException",
4971 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
4975 growing_buffer* order_buf = buffer_init( 128 );
4976 int first = 1; // boolean
4978 jsonObject* order_spec;
4979 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
4981 if( JSON_HASH != order_spec->type ) {
4982 osrfLogError( OSRF_LOG_MARK,
4983 "%s: Malformed field specification in ORDER BY clause; "
4984 "expected JSON_HASH, found %s",
4985 modulename, json_type( order_spec->type ) );
4987 osrfAppSessionStatus(
4989 OSRF_STATUS_INTERNALSERVERERROR,
4990 "osrfMethodException",
4992 "Malformed ORDER BY clause -- see error log for more details"
4994 buffer_free( order_buf );
4998 const char* class_alias =
4999 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5001 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5003 jsonObject* compare_to = jsonObjectGetKeyConst( order_spec, "compare" );
5005 if( !field || !class_alias ) {
5006 osrfLogError( OSRF_LOG_MARK,
5007 "%s: Missing class or field name in field specification of ORDER BY clause",
5010 osrfAppSessionStatus(
5012 OSRF_STATUS_INTERNALSERVERERROR,
5013 "osrfMethodException",
5015 "Malformed ORDER BY clause -- see error log for more details"
5017 buffer_free( order_buf );
5021 const ClassInfo* order_class_info = search_alias( class_alias );
5022 if( ! order_class_info ) {
5023 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5024 "not in FROM clause, skipping it", modulename, class_alias );
5028 // Add a separating comma, except at the beginning
5032 OSRF_BUFFER_ADD( order_buf, ", " );
5034 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5036 osrfLogError( OSRF_LOG_MARK,
5037 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5038 modulename, class_alias, field );
5040 osrfAppSessionStatus(
5042 OSRF_STATUS_INTERNALSERVERERROR,
5043 "osrfMethodException",
5045 "Invalid field referenced in ORDER BY clause -- "
5046 "see error log for more details"
5050 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5051 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5052 modulename, field );
5054 osrfAppSessionStatus(
5056 OSRF_STATUS_INTERNALSERVERERROR,
5057 "osrfMethodException",
5059 "Virtual field in ORDER BY clause -- see error log for more details"
5061 buffer_free( order_buf );
5065 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5066 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5067 if( ! transform_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 OSRF_BUFFER_ADD( order_buf, transform_str );
5082 free( transform_str );
5083 } else if( compare_to ) {
5084 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5085 if( ! compare_str ) {
5087 osrfAppSessionStatus(
5089 OSRF_STATUS_INTERNALSERVERERROR,
5090 "osrfMethodException",
5092 "Severe query error in ORDER BY clause -- "
5093 "see error log for more details"
5095 buffer_free( order_buf );
5099 buffer_fadd( order_buf, "(%s)", compare_str );
5100 free( compare_str );
5103 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5105 const char* direction =
5106 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5108 if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5109 OSRF_BUFFER_ADD( order_buf, " DESC" );
5111 OSRF_BUFFER_ADD( order_buf, " ASC" );
5115 return buffer_release( order_buf );
5119 @brief Build a SELECT statement.
5120 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5121 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5122 @param meta Pointer to the class metadata for the core class.
5123 @param ctx Pointer to the method context.
5124 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5126 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5127 "order_by", "limit", and "offset".
5129 The SELECT statements built here are distinct from those built for the json_query method.
5131 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5132 osrfHash* meta, osrfMethodContext* ctx ) {
5134 const char* locale = osrf_message_get_last_locale();
5136 osrfHash* fields = osrfHashGet( meta, "fields" );
5137 const char* core_class = osrfHashGet( meta, "classname" );
5139 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5141 jsonObject* selhash = NULL;
5142 jsonObject* defaultselhash = NULL;
5144 growing_buffer* sql_buf = buffer_init( 128 );
5145 growing_buffer* select_buf = buffer_init( 128 );
5147 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5148 defaultselhash = jsonNewObjectType( JSON_HASH );
5149 selhash = defaultselhash;
5152 // If there's no SELECT list for the core class, build one
5153 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5154 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5156 // Add every non-virtual field to the field list
5157 osrfHash* field_def = NULL;
5158 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5159 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5160 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5161 const char* field = osrfHashIteratorKey( field_itr );
5162 jsonObjectPush( field_list, jsonNewObject( field ) );
5165 osrfHashIteratorFree( field_itr );
5166 jsonObjectSetKey( selhash, core_class, field_list );
5169 // Build a list of columns for the SELECT clause
5171 const jsonObject* snode = NULL;
5172 jsonIterator* class_itr = jsonNewIterator( selhash );
5173 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5175 // If the class isn't in the IDL, ignore it
5176 const char* cname = class_itr->key;
5177 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5181 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5182 if( strcmp( core_class, class_itr->key )) {
5186 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5187 if( !found->size ) {
5188 jsonObjectFree( found );
5192 jsonObjectFree( found );
5195 const jsonObject* node = NULL;
5196 jsonIterator* select_itr = jsonNewIterator( snode );
5197 while( (node = jsonIteratorNext( select_itr )) ) {
5198 const char* item_str = jsonObjectGetString( node );
5199 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5200 char* fname = osrfHashGet( field, "name" );
5208 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5213 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5214 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5217 i18n = osrfHashGet( field, "i18n" );
5219 if( str_is_true( i18n ) ) {
5220 char* pkey = osrfHashGet( idlClass, "primarykey" );
5221 char* tname = osrfHashGet( idlClass, "tablename" );
5223 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5224 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5225 tname, cname, fname, pkey, cname, pkey, locale, fname );
5227 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5230 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5234 jsonIteratorFree( select_itr );
5237 jsonIteratorFree( class_itr );
5239 char* col_list = buffer_release( select_buf );
5240 char* table = oilsGetRelation( meta );
5242 table = strdup( "(null)" );
5244 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5248 // Clear the query stack (as a fail-safe precaution against possible
5249 // leftover garbage); then push the first query frame onto the stack.
5250 clear_query_stack();
5252 if( add_query_core( NULL, core_class ) ) {
5254 osrfAppSessionStatus(
5256 OSRF_STATUS_INTERNALSERVERERROR,
5257 "osrfMethodException",
5259 "Unable to build query frame for core class"
5261 buffer_free( sql_buf );
5262 if( defaultselhash )
5263 jsonObjectFree( defaultselhash );
5267 // Add the JOIN clauses, if any
5269 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5270 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5271 OSRF_BUFFER_ADD( sql_buf, join_clause );
5272 free( join_clause );
5275 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5276 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5278 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5280 // Add the conditions in the WHERE clause
5281 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5283 osrfAppSessionStatus(
5285 OSRF_STATUS_INTERNALSERVERERROR,
5286 "osrfMethodException",
5288 "Severe query error -- see error log for more details"
5290 buffer_free( sql_buf );
5291 if( defaultselhash )
5292 jsonObjectFree( defaultselhash );
5293 clear_query_stack();
5296 buffer_add( sql_buf, pred );
5300 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5301 if( rest_of_query ) {
5302 const jsonObject* order_by = NULL;
5303 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5305 char* order_by_list = NULL;
5307 if( JSON_ARRAY == order_by->type ) {
5308 order_by_list = buildOrderByFromArray( ctx, order_by );
5309 if( !order_by_list ) {
5310 buffer_free( sql_buf );
5311 if( defaultselhash )
5312 jsonObjectFree( defaultselhash );
5313 clear_query_stack();
5316 } else if( JSON_HASH == order_by->type ) {
5317 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5318 // and build a list of ORDER BY expressions.
5319 growing_buffer* order_buf = buffer_init( 128 );
5321 jsonIterator* class_itr = jsonNewIterator( order_by );
5322 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5324 ClassInfo* order_class_info = search_alias( class_itr->key );
5325 if( ! order_class_info )
5326 continue; // class not referenced by FROM clause? Ignore it.
5328 if( JSON_HASH == snode->type ) {
5330 // If the data for the current class is a JSON_HASH, then it is
5331 // keyed on field name.
5333 const jsonObject* onode = NULL;
5334 jsonIterator* order_itr = jsonNewIterator( snode );
5335 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5337 osrfHash* field_def = osrfHashGet(
5338 order_class_info->fields, order_itr->key );
5340 continue; // Field not defined in IDL? Ignore it.
5341 if( str_is_true( osrfHashGet( field_def, "virtual")))
5342 continue; // Field is virtual? Ignore it.
5344 char* field_str = NULL;
5345 char* direction = NULL;
5346 if( onode->type == JSON_HASH ) {
5347 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5348 field_str = searchFieldTransform(
5349 class_itr->key, field_def, onode );
5351 osrfAppSessionStatus(
5353 OSRF_STATUS_INTERNALSERVERERROR,
5354 "osrfMethodException",
5356 "Severe query error in ORDER BY clause -- "
5357 "see error log for more details"
5359 jsonIteratorFree( order_itr );
5360 jsonIteratorFree( class_itr );
5361 buffer_free( order_buf );
5362 buffer_free( sql_buf );
5363 if( defaultselhash )
5364 jsonObjectFree( defaultselhash );
5365 clear_query_stack();
5369 growing_buffer* field_buf = buffer_init( 16 );
5370 buffer_fadd( field_buf, "\"%s\".%s",
5371 class_itr->key, order_itr->key );
5372 field_str = buffer_release( field_buf );
5375 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5376 const char* dir = jsonObjectGetString( order_by );
5377 if(!strncasecmp( dir, "d", 1 )) {
5378 direction = " DESC";
5382 field_str = strdup( order_itr->key );
5383 const char* dir = jsonObjectGetString( onode );
5384 if( !strncasecmp( dir, "d", 1 )) {
5385 direction = " DESC";
5394 buffer_add( order_buf, ", " );
5397 buffer_add( order_buf, field_str );
5401 buffer_add( order_buf, direction );
5403 } // end while; looping over ORDER BY expressions
5405 jsonIteratorFree( order_itr );
5407 } else if( JSON_STRING == snode->type ) {
5408 // We expect a comma-separated list of sort fields.
5409 const char* str = jsonObjectGetString( snode );
5410 if( strchr( str, ';' )) {
5411 // No semicolons allowed. It is theoretically possible for a
5412 // legitimate semicolon to occur within quotes, but it's not likely
5413 // to occur in practice in the context of an ORDER BY list.
5414 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5415 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5417 osrfAppSessionStatus(
5419 OSRF_STATUS_INTERNALSERVERERROR,
5420 "osrfMethodException",
5422 "Possible attempt at SOL injection -- "
5423 "semicolon found in ORDER BY list"
5426 jsonIteratorFree( class_itr );
5427 buffer_free( order_buf );
5428 buffer_free( sql_buf );
5429 if( defaultselhash )
5430 jsonObjectFree( defaultselhash );
5431 clear_query_stack();
5434 buffer_add( order_buf, str );
5438 } // end while; looping over order_by classes
5440 jsonIteratorFree( class_itr );
5441 order_by_list = buffer_release( order_buf );
5444 osrfLogWarning( OSRF_LOG_MARK,
5445 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5446 "no ORDER BY generated" );
5449 if( order_by_list && *order_by_list ) {
5450 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5451 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5454 free( order_by_list );
5457 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5459 const char* str = jsonObjectGetString( limit );
5469 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5471 const char* str = jsonObjectGetString( offset );
5482 if( defaultselhash )
5483 jsonObjectFree( defaultselhash );
5484 clear_query_stack();
5486 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5487 return buffer_release( sql_buf );
5490 int doJSONSearch ( osrfMethodContext* ctx ) {
5491 if(osrfMethodVerifyContext( ctx )) {
5492 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5496 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5500 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5504 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5505 flags |= SELECT_DISTINCT;
5507 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5508 flags |= DISABLE_I18N;
5510 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5511 clear_query_stack(); // a possibly needless precaution
5512 char* sql = buildQuery( ctx, hash, flags );
5513 clear_query_stack();
5520 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5523 dbhandle = writehandle;
5525 dbi_result result = dbi_conn_query( dbhandle, sql );
5528 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5530 if( dbi_result_first_row( result )) {
5531 /* JSONify the result */
5532 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5535 jsonObject* return_val = oilsMakeJSONFromResult( result );
5536 osrfAppRespond( ctx, return_val );
5537 jsonObjectFree( return_val );
5538 } while( dbi_result_next_row( result ));
5541 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5544 osrfAppRespondComplete( ctx, NULL );
5546 /* clean up the query */
5547 dbi_result_free( result );
5552 int errnum = dbi_conn_error( dbhandle, &msg );
5553 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5554 modulename, sql, errnum, msg ? msg : "(No description available)" );
5555 osrfAppSessionStatus(
5557 OSRF_STATUS_INTERNALSERVERERROR,
5558 "osrfMethodException",
5560 "Severe query error -- see error log for more details"
5562 if( !oilsIsDBConnected( dbhandle ))
5563 osrfAppSessionPanic( ctx->session );
5570 // The last parameter, err, is used to report an error condition by updating an int owned by
5571 // the calling code.
5573 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5574 // It is the responsibility of the calling code to initialize *err before the
5575 // call, so that it will be able to make sense of the result.
5577 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5578 // redundant anyway.
5579 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5580 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5583 dbhandle = writehandle;
5585 char* core_class = osrfHashGet( class_meta, "classname" );
5586 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5588 char* pkey = osrfHashGet( class_meta, "primarykey" );
5590 if (!ctx->session->userData)
5591 (void) initSessionCache( ctx );
5593 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5594 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5595 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5597 int i_respond_directly = 0;
5598 int flesh_depth = 0;
5600 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5602 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5607 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5609 dbi_result result = dbi_conn_query( dbhandle, sql );
5610 if( NULL == result ) {
5612 int errnum = dbi_conn_error( dbhandle, &msg );
5613 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5614 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5615 msg ? msg : "(No description available)" );
5616 if( !oilsIsDBConnected( dbhandle ))
5617 osrfAppSessionPanic( ctx->session );
5618 osrfAppSessionStatus(
5620 OSRF_STATUS_INTERNALSERVERERROR,
5621 "osrfMethodException",
5623 "Severe query error -- see error log for more details"
5630 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5633 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5634 jsonObject* row_obj = NULL;
5636 // The following two steps are for verifyObjectPCRUD()'s benefit.
5637 // 1. get the flesh depth
5638 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5640 flesh_depth = (int) jsonObjectGetNumber( _tmp );
5641 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5642 flesh_depth = max_flesh_depth;
5645 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
5646 // over the whole life of this request. This means if we've already set
5647 // up a rs_size_req_%d, do nothing.
5648 // a. Incidentally, we can also use this opportunity to set i_respond_directly
5649 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
5650 if( !rs_size ) { // pointer null, so value not set in hash
5651 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
5652 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
5654 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
5655 unsigned long long result_count = dbi_result_get_numrows( result );
5656 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
5657 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
5660 if( dbi_result_first_row( result )) {
5662 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5663 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5664 // eliminate the duplicates.
5665 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5666 osrfHash* dedup = osrfNewHash();
5668 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5669 char* pkey_val = oilsFMGetString( row_obj, pkey );
5670 if( osrfHashGet( dedup, pkey_val ) ) {
5671 jsonObjectFree( row_obj );
5674 if( !enforce_pcrud || !need_to_verify ||
5675 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
5676 osrfHashSet( dedup, pkey_val, pkey_val );
5677 jsonObjectPush( res_list, row_obj );
5680 } while( dbi_result_next_row( result ));
5681 osrfHashFree( dedup );
5684 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5688 /* clean up the query */
5689 dbi_result_free( result );
5692 // If we're asked to flesh, and there's anything to flesh, then flesh it
5693 // (formerly we would skip fleshing if in pcrud mode, but now we support
5694 // fleshing even in PCRUD).
5695 if( res_list->size ) {
5696 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
5697 jsonObject* flesh_fields;
5698 jsonObject* flesh_blob = NULL;
5699 osrfStringArray* link_fields = NULL;
5700 osrfHash* links = NULL;
5704 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
5705 if( temp_blob && flesh_depth > 0 ) {
5707 flesh_blob = jsonObjectClone( temp_blob );
5708 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
5710 links = osrfHashGet( class_meta, "links" );
5712 // Make an osrfStringArray of the names of fields to be fleshed
5713 if( flesh_fields ) {
5714 if( flesh_fields->size == 1 ) {
5715 const char* _t = jsonObjectGetString(
5716 jsonObjectGetIndex( flesh_fields, 0 ) );
5717 if( !strcmp( _t, "*" ))
5718 link_fields = osrfHashKeys( links );
5721 if( !link_fields ) {
5723 link_fields = osrfNewStringArray( 1 );
5724 jsonIterator* _i = jsonNewIterator( flesh_fields );
5725 while ((_f = jsonIteratorNext( _i ))) {
5726 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5728 jsonIteratorFree( _i );
5731 want_flesh = link_fields ? 1 : 0;
5735 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5737 // Iterate over the JSON_ARRAY of rows
5739 unsigned long res_idx = 0;
5740 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5743 const char* link_field;
5745 // Iterate over the list of fleshable fields
5747 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5749 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5751 osrfHash* kid_link = osrfHashGet( links, link_field );
5753 continue; // Not a link field; skip it
5755 osrfHash* field = osrfHashGet( fields, link_field );
5757 continue; // Not a field at all; skip it (IDL is ill-formed)
5759 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5760 osrfHashGet( kid_link, "class" ));
5762 continue; // The class it links to doesn't exist; skip it
5764 const char* reltype = osrfHashGet( kid_link, "reltype" );
5766 continue; // No reltype; skip it (IDL is ill-formed)
5768 osrfHash* value_field = field;
5770 if( !strcmp( reltype, "has_many" )
5771 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5772 value_field = osrfHashGet(
5773 fields, osrfHashGet( class_meta, "primarykey" ) );
5776 int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
5777 // fleshing pcrud case: we require the controller in need_to_verify mode
5778 if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
5779 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
5783 (unsigned long) atoi( osrfHashGet(field, "array_position") ),
5785 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
5791 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5793 if( link_map->size > 0 ) {
5794 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5797 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5802 osrfHashGet( kid_link, "class" ),
5809 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5810 osrfHashGet( kid_link, "field" ),
5811 osrfHashGet( kid_link, "class" ),
5812 osrfHashGet( kid_link, "key" ),
5813 osrfHashGet( kid_link, "reltype" )
5816 const char* search_key = jsonObjectGetString(
5817 jsonObjectGetIndex( cur,
5818 atoi( osrfHashGet( value_field, "array_position" ) )
5823 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5827 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5829 // construct WHERE clause
5830 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5833 osrfHashGet( kid_link, "key" ),
5834 jsonNewObject( search_key )
5837 // construct the rest of the query, mostly
5838 // by copying pieces of the previous level of query
5839 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5840 jsonObjectSetKey( rest_of_query, "flesh",
5841 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5845 jsonObjectSetKey( rest_of_query, "flesh_fields",
5846 jsonObjectClone( flesh_blob ));
5848 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5849 jsonObjectSetKey( rest_of_query, "order_by",
5850 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5854 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5855 jsonObjectSetKey( rest_of_query, "select",
5856 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5860 // do the query, recursively, to expand the fleshable field
5861 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5862 where_clause, rest_of_query, err );
5864 jsonObjectFree( where_clause );
5865 jsonObjectFree( rest_of_query );
5868 osrfStringArrayFree( link_fields );
5869 jsonObjectFree( res_list );
5870 jsonObjectFree( flesh_blob );
5874 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5875 osrfHashGet( kid_link, "class" ), kids->size );
5877 // Traverse the result set
5878 jsonObject* X = NULL;
5879 if( link_map->size > 0 && kids->size > 0 ) {
5881 kids = jsonNewObjectType( JSON_ARRAY );
5883 jsonObject* _k_node;
5884 unsigned long res_idx = 0;
5885 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5891 (unsigned long) atoi(
5897 osrfHashGet( kid_link, "class" )
5901 osrfStringArrayGetString( link_map, 0 )
5909 } // end while loop traversing X
5912 if (kids->size > 0) {
5914 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5915 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
5917 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5918 osrfHashGet( kid_link, "field" ));
5921 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5922 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5927 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5929 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5930 osrfHashGet( kid_link, "field" ) );
5933 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5934 jsonObjectClone( kids )
5939 jsonObjectFree( kids );
5943 jsonObjectFree( kids );
5945 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5946 osrfHashGet( kid_link, "field" ) );
5947 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5949 } // end while loop traversing list of fleshable fields
5952 if( i_respond_directly ) {
5953 if ( *methodtype == 'i' ) {
5954 osrfAppRespond( ctx,
5955 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
5957 osrfAppRespond( ctx, cur );
5960 } // end while loop traversing res_list
5961 jsonObjectFree( flesh_blob );
5962 osrfStringArrayFree( link_fields );
5965 if( i_respond_directly ) {
5966 jsonObjectFree( res_list );
5967 return jsonNewObjectType( JSON_ARRAY );
5974 int doUpdate( osrfMethodContext* ctx ) {
5975 if( osrfMethodVerifyContext( ctx )) {
5976 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5981 timeout_needs_resetting = 1;
5983 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5985 jsonObject* target = NULL;
5987 target = jsonObjectGetIndex( ctx->params, 1 );
5989 target = jsonObjectGetIndex( ctx->params, 0 );
5991 if(!verifyObjectClass( ctx, target )) {
5992 osrfAppRespondComplete( ctx, NULL );
5996 if( getXactId( ctx ) == NULL ) {
5997 osrfAppSessionStatus(
5999 OSRF_STATUS_BADREQUEST,
6000 "osrfMethodException",
6002 "No active transaction -- required for UPDATE"
6004 osrfAppRespondComplete( ctx, NULL );
6008 // The following test is harmless but redundant. If a class is
6009 // readonly, we don't register an update method for it.
6010 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6011 osrfAppSessionStatus(
6013 OSRF_STATUS_BADREQUEST,
6014 "osrfMethodException",
6016 "Cannot UPDATE readonly class"
6018 osrfAppRespondComplete( ctx, NULL );
6022 const char* trans_id = getXactId( ctx );
6024 // Set the last_xact_id
6025 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6027 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6028 trans_id, target->classname, index );
6029 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6032 char* pkey = osrfHashGet( meta, "primarykey" );
6033 osrfHash* fields = osrfHashGet( meta, "fields" );
6035 char* id = oilsFMGetString( target, pkey );
6039 "%s updating %s object with %s = %s",
6041 osrfHashGet( meta, "fieldmapper" ),
6046 dbhandle = writehandle;
6047 growing_buffer* sql = buffer_init( 128 );
6048 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6051 osrfHash* field_def = NULL;
6052 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6053 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6055 // Skip virtual fields, and the primary key
6056 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6059 const char* field_name = osrfHashIteratorKey( field_itr );
6060 if( ! strcmp( field_name, pkey ) )
6063 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6065 int value_is_numeric = 0; // boolean
6067 if( field_object && field_object->classname ) {
6068 value = oilsFMGetString(
6070 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6072 } else if( field_object && JSON_BOOL == field_object->type ) {
6073 if( jsonBoolIsTrue( field_object ) )
6074 value = strdup( "t" );
6076 value = strdup( "f" );
6078 value = jsonObjectToSimpleString( field_object );
6079 if( field_object && JSON_NUMBER == field_object->type )
6080 value_is_numeric = 1;
6083 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6084 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6086 if( !field_object || field_object->type == JSON_NULL ) {
6087 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6088 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6092 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6093 buffer_fadd( sql, " %s = NULL", field_name );
6096 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6100 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6102 const char* numtype = get_datatype( field_def );
6103 if( !strncmp( numtype, "INT", 3 ) ) {
6104 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6105 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6106 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6108 // Must really be intended as a string, so quote it
6109 if( dbi_conn_quote_string( dbhandle, &value )) {
6110 buffer_fadd( sql, " %s = %s", field_name, value );
6112 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6113 modulename, value );
6114 osrfAppSessionStatus(
6116 OSRF_STATUS_INTERNALSERVERERROR,
6117 "osrfMethodException",
6119 "Error quoting string -- please see the error log for more details"
6123 osrfHashIteratorFree( field_itr );
6125 osrfAppRespondComplete( ctx, NULL );
6130 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6133 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6137 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6138 buffer_fadd( sql, " %s = %s", field_name, value );
6140 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6141 osrfAppSessionStatus(
6143 OSRF_STATUS_INTERNALSERVERERROR,
6144 "osrfMethodException",
6146 "Error quoting string -- please see the error log for more details"
6150 osrfHashIteratorFree( field_itr );
6152 osrfAppRespondComplete( ctx, NULL );
6161 osrfHashIteratorFree( field_itr );
6163 jsonObject* obj = jsonNewObject( id );
6165 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6166 dbi_conn_quote_string( dbhandle, &id );
6168 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6170 char* query = buffer_release( sql );
6171 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6173 dbi_result result = dbi_conn_query( dbhandle, query );
6178 jsonObjectFree( obj );
6179 obj = jsonNewObject( NULL );
6181 int errnum = dbi_conn_error( dbhandle, &msg );
6184 "%s ERROR updating %s object with %s = %s: %d %s",
6186 osrfHashGet( meta, "fieldmapper" ),
6190 msg ? msg : "(No description available)"
6192 osrfAppSessionStatus(
6194 OSRF_STATUS_INTERNALSERVERERROR,
6195 "osrfMethodException",
6197 "Error in updating a row -- please see the error log for more details"
6199 if( !oilsIsDBConnected( dbhandle ))
6200 osrfAppSessionPanic( ctx->session );
6203 dbi_result_free( result );
6206 osrfAppRespondComplete( ctx, obj );
6207 jsonObjectFree( obj );
6211 int doDelete( osrfMethodContext* ctx ) {
6212 if( osrfMethodVerifyContext( ctx )) {
6213 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6218 timeout_needs_resetting = 1;
6220 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6222 if( getXactId( ctx ) == NULL ) {
6223 osrfAppSessionStatus(
6225 OSRF_STATUS_BADREQUEST,
6226 "osrfMethodException",
6228 "No active transaction -- required for DELETE"
6230 osrfAppRespondComplete( ctx, NULL );
6234 // The following test is harmless but redundant. If a class is
6235 // readonly, we don't register a delete method for it.
6236 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6237 osrfAppSessionStatus(
6239 OSRF_STATUS_BADREQUEST,
6240 "osrfMethodException",
6242 "Cannot DELETE readonly class"
6244 osrfAppRespondComplete( ctx, NULL );
6248 dbhandle = writehandle;
6250 char* pkey = osrfHashGet( meta, "primarykey" );
6257 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6258 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6259 osrfAppRespondComplete( ctx, NULL );
6263 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6265 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6266 osrfAppRespondComplete( ctx, NULL );
6269 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6274 "%s deleting %s object with %s = %s",
6276 osrfHashGet( meta, "fieldmapper" ),
6281 jsonObject* obj = jsonNewObject( id );
6283 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6284 dbi_conn_quote_string( writehandle, &id );
6286 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6287 osrfHashGet( meta, "tablename" ), pkey, id );
6292 jsonObjectFree( obj );
6293 obj = jsonNewObject( NULL );
6295 int errnum = dbi_conn_error( writehandle, &msg );
6298 "%s ERROR deleting %s object with %s = %s: %d %s",
6300 osrfHashGet( meta, "fieldmapper" ),
6304 msg ? msg : "(No description available)"
6306 osrfAppSessionStatus(
6308 OSRF_STATUS_INTERNALSERVERERROR,
6309 "osrfMethodException",
6311 "Error in deleting a row -- please see the error log for more details"
6313 if( !oilsIsDBConnected( writehandle ))
6314 osrfAppSessionPanic( ctx->session );
6316 dbi_result_free( result );
6320 osrfAppRespondComplete( ctx, obj );
6321 jsonObjectFree( obj );
6326 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6327 @param result An iterator for a result set; we only look at the current row.
6328 @param @meta Pointer to the class metadata for the core class.
6329 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6331 If a column is not defined in the IDL, or if it has no array_position defined for it in
6332 the IDL, or if it is defined as virtual, ignore it.
6334 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6335 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6336 array_position in the IDL.
6338 A field defined in the IDL but not represented in the returned row will leave a hole
6339 in the JSON_ARRAY. In effect it will be treated as a null value.
6341 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6342 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6343 classname corresponding to the @a meta argument.
6345 The calling code is responsible for freeing the the resulting jsonObject by calling
6348 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6349 if( !( result && meta )) return NULL;
6351 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6352 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6353 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6355 osrfHash* fields = osrfHashGet( meta, "fields" );
6357 int columnIndex = 1;
6358 const char* columnName;
6360 /* cycle through the columns in the row returned from the database */
6361 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6363 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6365 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6367 /* determine the field type and storage attributes */
6368 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6369 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6371 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6372 // or if it has no sequence number there, or if it's virtual, skip it.
6373 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6376 if( str_is_true( osrfHashGet( _f, "virtual" )))
6377 continue; // skip this column: IDL says it's virtual
6379 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6380 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6381 continue; // since we assign sequence numbers dynamically as we load the IDL.
6383 fmIndex = atoi( pos );
6384 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6386 continue; // This field is not defined in the IDL
6389 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6390 // sequence number from the IDL (which is likely to be different from the sequence
6391 // of columns in the SELECT clause).
6392 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6393 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6398 case DBI_TYPE_INTEGER :
6400 if( attr & DBI_INTEGER_SIZE8 )
6401 jsonObjectSetIndex( object, fmIndex,
6402 jsonNewNumberObject(
6403 dbi_result_get_longlong_idx( result, columnIndex )));
6405 jsonObjectSetIndex( object, fmIndex,
6406 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6410 case DBI_TYPE_DECIMAL :
6411 jsonObjectSetIndex( object, fmIndex,
6412 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6415 case DBI_TYPE_STRING :
6420 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6425 case DBI_TYPE_DATETIME : {
6427 char dt_string[ 256 ] = "";
6430 // Fetch the date column as a time_t
6431 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6433 // Translate the time_t to a human-readable string
6434 if( !( attr & DBI_DATETIME_DATE )) {
6435 gmtime_r( &_tmp_dt, &gmdt );
6436 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6437 } else if( !( attr & DBI_DATETIME_TIME )) {
6438 localtime_r( &_tmp_dt, &gmdt );
6439 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6441 localtime_r( &_tmp_dt, &gmdt );
6442 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6445 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6449 case DBI_TYPE_BINARY :
6450 osrfLogError( OSRF_LOG_MARK,
6451 "Can't do binary at column %s : index %d", columnName, columnIndex );
6460 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6461 if( !result ) return NULL;
6463 jsonObject* object = jsonNewObject( NULL );
6466 char dt_string[ 256 ];
6470 int columnIndex = 1;
6472 unsigned short type;
6473 const char* columnName;
6475 /* cycle through the column list */
6476 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6478 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6480 fmIndex = -1; // reset the position
6482 /* determine the field type and storage attributes */
6483 type = dbi_result_get_field_type_idx( result, columnIndex );
6484 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6486 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6487 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6492 case DBI_TYPE_INTEGER :
6494 if( attr & DBI_INTEGER_SIZE8 )
6495 jsonObjectSetKey( object, columnName,
6496 jsonNewNumberObject( dbi_result_get_longlong_idx(
6497 result, columnIndex )) );
6499 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6500 dbi_result_get_int_idx( result, columnIndex )) );
6503 case DBI_TYPE_DECIMAL :
6504 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6505 dbi_result_get_double_idx( result, columnIndex )) );
6508 case DBI_TYPE_STRING :
6509 jsonObjectSetKey( object, columnName,
6510 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6513 case DBI_TYPE_DATETIME :
6515 memset( dt_string, '\0', sizeof( dt_string ));
6516 memset( &gmdt, '\0', sizeof( gmdt ));
6518 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6520 if( !( attr & DBI_DATETIME_DATE )) {
6521 gmtime_r( &_tmp_dt, &gmdt );
6522 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6523 } else if( !( attr & DBI_DATETIME_TIME )) {
6524 localtime_r( &_tmp_dt, &gmdt );
6525 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6527 localtime_r( &_tmp_dt, &gmdt );
6528 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6531 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6534 case DBI_TYPE_BINARY :
6535 osrfLogError( OSRF_LOG_MARK,
6536 "Can't do binary at column %s : index %d", columnName, columnIndex );
6540 } // end while loop traversing result
6545 // Interpret a string as true or false
6546 int str_is_true( const char* str ) {
6547 if( NULL == str || strcasecmp( str, "true" ) )
6553 // Interpret a jsonObject as true or false
6554 static int obj_is_true( const jsonObject* obj ) {
6557 else switch( obj->type )
6565 if( strcasecmp( obj->value.s, "true" ) )
6569 case JSON_NUMBER : // Support 1/0 for perl's sake
6570 if( jsonObjectGetNumber( obj ) == 1.0 )
6579 // Translate a numeric code into a text string identifying a type of
6580 // jsonObject. To be used for building error messages.
6581 static const char* json_type( int code ) {
6587 return "JSON_ARRAY";
6589 return "JSON_STRING";
6591 return "JSON_NUMBER";
6597 return "(unrecognized)";
6601 // Extract the "primitive" attribute from an IDL field definition.
6602 // If we haven't initialized the app, then we must be running in
6603 // some kind of testbed. In that case, default to "string".
6604 static const char* get_primitive( osrfHash* field ) {
6605 const char* s = osrfHashGet( field, "primitive" );
6607 if( child_initialized )
6610 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6612 osrfHashGet( field, "name" )
6620 // Extract the "datatype" attribute from an IDL field definition.
6621 // If we haven't initialized the app, then we must be running in
6622 // some kind of testbed. In that case, default to to NUMERIC,
6623 // since we look at the datatype only for numbers.
6624 static const char* get_datatype( osrfHash* field ) {
6625 const char* s = osrfHashGet( field, "datatype" );
6627 if( child_initialized )
6630 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6632 osrfHashGet( field, "name" )
6641 @brief Determine whether a string is potentially a valid SQL identifier.
6642 @param s The identifier to be tested.
6643 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6645 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6646 need to follow all the rules exactly, such as requiring that the first character not
6649 We allow leading and trailing white space. In between, we do not allow punctuation
6650 (except for underscores and dollar signs), control characters, or embedded white space.
6652 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6653 for the foreseeable future such quoted identifiers are not likely to be an issue.
6655 int is_identifier( const char* s) {
6659 // Skip leading white space
6660 while( isspace( (unsigned char) *s ) )
6664 return 0; // Nothing but white space? Not okay.
6666 // Check each character until we reach white space or
6667 // end-of-string. Letters, digits, underscores, and
6668 // dollar signs are okay. With the exception of periods
6669 // (as in schema.identifier), control characters and other
6670 // punctuation characters are not okay. Anything else
6671 // is okay -- it could for example be part of a multibyte
6672 // UTF8 character such as a letter with diacritical marks,
6673 // and those are allowed.
6675 if( isalnum( (unsigned char) *s )
6679 ; // Fine; keep going
6680 else if( ispunct( (unsigned char) *s )
6681 || iscntrl( (unsigned char) *s ) )
6684 } while( *s && ! isspace( (unsigned char) *s ) );
6686 // If we found any white space in the above loop,
6687 // the rest had better be all white space.
6689 while( isspace( (unsigned char) *s ) )
6693 return 0; // White space was embedded within non-white space
6699 @brief Determine whether to accept a character string as a comparison operator.
6700 @param op The candidate comparison operator.
6701 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6703 We don't validate the operator for real. We just make sure that it doesn't contain
6704 any semicolons or white space (with special exceptions for a few specific operators).
6705 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6706 space but it's still not a valid operator, then the database will complain.
6708 Another approach would be to compare the string against a short list of approved operators.
6709 We don't do that because we want to allow custom operators like ">100*", which at this
6710 writing would be difficult or impossible to express otherwise in a JSON query.
6712 int is_good_operator( const char* op ) {
6713 if( !op ) return 0; // Sanity check
6717 if( isspace( (unsigned char) *s ) ) {
6718 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6719 // and IS NOT DISTINCT FROM.
6720 if( !strcasecmp( op, "similar to" ) )
6722 else if( !strcasecmp( op, "is distinct from" ) )
6724 else if( !strcasecmp( op, "is not distinct from" ) )
6729 else if( ';' == *s )
6737 @name Query Frame Management
6739 The following machinery supports a stack of query frames for use by SELECT().
6741 A query frame caches information about one level of a SELECT query. When we enter
6742 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6744 The query frame stores information about the core class, and about any joined classes
6747 The main purpose is to map table aliases to classes and tables, so that a query can
6748 join to the same table more than once. A secondary goal is to reduce the number of
6749 lookups in the IDL by caching the results.
6753 #define STATIC_CLASS_INFO_COUNT 3
6755 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6758 @brief Allocate a ClassInfo as raw memory.
6759 @return Pointer to the newly allocated ClassInfo.
6761 Except for the in_use flag, which is used only by the allocation and deallocation
6762 logic, we don't initialize the ClassInfo here.
6764 static ClassInfo* allocate_class_info( void ) {
6765 // In order to reduce the number of mallocs and frees, we return a static
6766 // instance of ClassInfo, if we can find one that we're not already using.
6767 // We rely on the fact that the compiler will implicitly initialize the
6768 // static instances so that in_use == 0.
6771 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6772 if( ! static_class_info[ i ].in_use ) {
6773 static_class_info[ i ].in_use = 1;
6774 return static_class_info + i;
6778 // The static ones are all in use. Malloc one.
6780 return safe_malloc( sizeof( ClassInfo ) );
6784 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6785 @param info Pointer to the ClassInfo to be cleared.
6787 static void clear_class_info( ClassInfo* info ) {
6792 // Free any malloc'd strings
6794 if( info->alias != info->alias_store )
6795 free( info->alias );
6797 if( info->class_name != info->class_name_store )
6798 free( info->class_name );
6800 free( info->source_def );
6802 info->alias = info->class_name = info->source_def = NULL;
6807 @brief Free a ClassInfo and everything it owns.
6808 @param info Pointer to the ClassInfo to be freed.
6810 static void free_class_info( ClassInfo* info ) {
6815 clear_class_info( info );
6817 // If it's one of the static instances, just mark it as not in use
6820 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6821 if( info == static_class_info + i ) {
6822 static_class_info[ i ].in_use = 0;
6827 // Otherwise it must have been malloc'd, so free it
6833 @brief Populate an already-allocated ClassInfo.
6834 @param info Pointer to the ClassInfo to be populated.
6835 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6837 @param class Name of the class.
6838 @return Zero if successful, or 1 if not.
6840 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6841 the relevant portions of the IDL for the specified class.
6843 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6846 osrfLogError( OSRF_LOG_MARK,
6847 "%s ERROR: No ClassInfo available to populate", modulename );
6848 info->alias = info->class_name = info->source_def = NULL;
6849 info->class_def = info->fields = info->links = NULL;
6854 osrfLogError( OSRF_LOG_MARK,
6855 "%s ERROR: No class name provided for lookup", modulename );
6856 info->alias = info->class_name = info->source_def = NULL;
6857 info->class_def = info->fields = info->links = NULL;
6861 // Alias defaults to class name if not supplied
6862 if( ! alias || ! alias[ 0 ] )
6865 // Look up class info in the IDL
6866 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6868 osrfLogError( OSRF_LOG_MARK,
6869 "%s ERROR: Class %s not defined in IDL", modulename, class );
6870 info->alias = info->class_name = info->source_def = NULL;
6871 info->class_def = info->fields = info->links = NULL;
6873 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6874 osrfLogError( OSRF_LOG_MARK,
6875 "%s ERROR: Class %s is defined as virtual", modulename, class );
6876 info->alias = info->class_name = info->source_def = NULL;
6877 info->class_def = info->fields = info->links = NULL;
6881 osrfHash* links = osrfHashGet( class_def, "links" );
6883 osrfLogError( OSRF_LOG_MARK,
6884 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6885 info->alias = info->class_name = info->source_def = NULL;
6886 info->class_def = info->fields = info->links = NULL;
6890 osrfHash* fields = osrfHashGet( class_def, "fields" );
6892 osrfLogError( OSRF_LOG_MARK,
6893 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6894 info->alias = info->class_name = info->source_def = NULL;
6895 info->class_def = info->fields = info->links = NULL;
6899 char* source_def = oilsGetRelation( class_def );
6903 // We got everything we need, so populate the ClassInfo
6904 if( strlen( alias ) > ALIAS_STORE_SIZE )
6905 info->alias = strdup( alias );
6907 strcpy( info->alias_store, alias );
6908 info->alias = info->alias_store;
6911 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6912 info->class_name = strdup( class );
6914 strcpy( info->class_name_store, class );
6915 info->class_name = info->class_name_store;
6918 info->source_def = source_def;
6920 info->class_def = class_def;
6921 info->links = links;
6922 info->fields = fields;
6927 #define STATIC_FRAME_COUNT 3
6929 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6932 @brief Allocate a QueryFrame as raw memory.
6933 @return Pointer to the newly allocated QueryFrame.
6935 Except for the in_use flag, which is used only by the allocation and deallocation
6936 logic, we don't initialize the QueryFrame here.
6938 static QueryFrame* allocate_frame( void ) {
6939 // In order to reduce the number of mallocs and frees, we return a static
6940 // instance of QueryFrame, if we can find one that we're not already using.
6941 // We rely on the fact that the compiler will implicitly initialize the
6942 // static instances so that in_use == 0.
6945 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6946 if( ! static_frame[ i ].in_use ) {
6947 static_frame[ i ].in_use = 1;
6948 return static_frame + i;
6952 // The static ones are all in use. Malloc one.
6954 return safe_malloc( sizeof( QueryFrame ) );
6958 @brief Free a QueryFrame, and all the memory it owns.
6959 @param frame Pointer to the QueryFrame to be freed.
6961 static void free_query_frame( QueryFrame* frame ) {
6966 clear_class_info( &frame->core );
6968 // Free the join list
6970 ClassInfo* info = frame->join_list;
6973 free_class_info( info );
6977 frame->join_list = NULL;
6980 // If the frame is a static instance, just mark it as unused
6982 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6983 if( frame == static_frame + i ) {
6984 static_frame[ i ].in_use = 0;
6989 // Otherwise it must have been malloc'd, so free it
6995 @brief Search a given QueryFrame for a specified alias.
6996 @param frame Pointer to the QueryFrame to be searched.
6997 @param target The alias for which to search.
6998 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7000 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7001 if( ! frame || ! target ) {
7005 ClassInfo* found_class = NULL;
7007 if( !strcmp( target, frame->core.alias ) )
7008 return &(frame->core);
7010 ClassInfo* curr_class = frame->join_list;
7011 while( curr_class ) {
7012 if( strcmp( target, curr_class->alias ) )
7013 curr_class = curr_class->next;
7015 found_class = curr_class;
7025 @brief Push a new (blank) QueryFrame onto the stack.
7027 static void push_query_frame( void ) {
7028 QueryFrame* frame = allocate_frame();
7029 frame->join_list = NULL;
7030 frame->next = curr_query;
7032 // Initialize the ClassInfo for the core class
7033 ClassInfo* core = &frame->core;
7034 core->alias = core->class_name = core->source_def = NULL;
7035 core->class_def = core->fields = core->links = NULL;
7041 @brief Pop a QueryFrame off the stack and destroy it.
7043 static void pop_query_frame( void ) {
7048 QueryFrame* popped = curr_query;
7049 curr_query = popped->next;
7051 free_query_frame( popped );
7055 @brief Populate the ClassInfo for the core class.
7056 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7057 class name as an alias.
7058 @param class_name Name of the core class.
7059 @return Zero if successful, or 1 if not.
7061 Populate the ClassInfo of the core class with copies of the alias and class name, and
7062 with pointers to the relevant portions of the IDL for the core class.
7064 static int add_query_core( const char* alias, const char* class_name ) {
7067 if( ! curr_query ) {
7068 osrfLogError( OSRF_LOG_MARK,
7069 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7071 } else if( curr_query->core.alias ) {
7072 osrfLogError( OSRF_LOG_MARK,
7073 "%s ERROR: Core class %s already populated as %s",
7074 modulename, curr_query->core.class_name, curr_query->core.alias );
7078 build_class_info( &curr_query->core, alias, class_name );
7079 if( curr_query->core.alias )
7082 osrfLogError( OSRF_LOG_MARK,
7083 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7089 @brief Search the current QueryFrame for a specified alias.
7090 @param target The alias for which to search.
7091 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7093 static inline ClassInfo* search_alias( const char* target ) {
7094 return search_alias_in_frame( curr_query, target );
7098 @brief Search all levels of query for a specified alias, starting with the current query.
7099 @param target The alias for which to search.
7100 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7102 static ClassInfo* search_all_alias( const char* target ) {
7103 ClassInfo* found_class = NULL;
7104 QueryFrame* curr_frame = curr_query;
7106 while( curr_frame ) {
7107 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7110 curr_frame = curr_frame->next;
7117 @brief Add a class to the list of classes joined to the current query.
7118 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7119 the class name as an alias.
7120 @param classname The name of the class to be added.
7121 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7123 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7125 if( ! classname || ! *classname ) { // sanity check
7126 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7133 const ClassInfo* conflict = search_alias( alias );
7135 osrfLogError( OSRF_LOG_MARK,
7136 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7137 modulename, alias, conflict->class_name );
7141 ClassInfo* info = allocate_class_info();
7143 if( build_class_info( info, alias, classname ) ) {
7144 free_class_info( info );
7148 // Add the new ClassInfo to the join list of the current QueryFrame
7149 info->next = curr_query->join_list;
7150 curr_query->join_list = info;
7156 @brief Destroy all nodes on the query stack.
7158 static void clear_query_stack( void ) {
7164 @brief Implement the set_audit_info method.
7165 @param ctx Pointer to the method context.
7166 @return Zero if successful, or -1 if not.
7168 Issue a SAVEPOINT to the database server.
7173 - workstation id (int)
7175 If user id is not provided the authkey will be used.
7176 For PCRUD the authkey is always used, even if a user is provided.
7178 int setAuditInfo( osrfMethodContext* ctx ) {
7179 if(osrfMethodVerifyContext( ctx )) {
7180 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
7184 // Get the user id from the parameters
7185 const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7187 if( enforce_pcrud || !user_id ) {
7188 timeout_needs_resetting = 1;
7189 const jsonObject* user = verifyUserPCRUD( ctx );
7192 osrfAppRespondComplete( ctx, NULL );
7196 // Not PCRUD and have a user_id?
7197 int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7198 osrfAppRespondComplete( ctx, NULL );
7203 @brief Save a audit info
7204 @param ctx Pointer to the method context.
7205 @param user_id User ID to write as a string
7206 @param ws_id Workstation ID to write as a string
7208 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7209 if( ctx && ctx->session ) {
7210 osrfAppSession* session = ctx->session;
7212 osrfHash* cache = session->userData;
7214 // If the session doesn't already have a hash, create one. Make sure
7215 // that the application session frees the hash when it terminates.
7216 if( NULL == cache ) {
7217 session->userData = cache = osrfNewHash();
7218 osrfHashSetCallback( cache, &sessionDataFree );
7219 ctx->session->userDataFree = &userDataFree;
7222 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7224 osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7226 int errnum = dbi_conn_error( writehandle, &msg );
7229 "%s: Error setting auditor information: %d %s",
7232 msg ? msg : "(No description available)"
7234 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7235 "osrfMethodException", ctx->request, "Error setting auditor info" );
7236 if( !oilsIsDBConnected( writehandle ))
7237 osrfAppSessionPanic( ctx->session );
7240 dbi_result_free( result );