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 const jsonObject* verifyUserPCRUDfull( osrfMethodContext*, int );
123 static int verifyObjectPCRUD( osrfMethodContext*, osrfHash*, const jsonObject*, int );
124 static const char* org_tree_root( osrfMethodContext* ctx );
125 static jsonObject* single_hash( const char* key, const char* value );
127 static int child_initialized = 0; /* boolean */
129 static dbi_conn writehandle; /* our MASTER db connection */
130 static dbi_conn dbhandle; /* our CURRENT db connection */
131 //static osrfHash * readHandles;
133 // The following points to the top of a stack of QueryFrames. It's a little
134 // confusing because the top level of the query is at the bottom of the stack.
135 static QueryFrame* curr_query = NULL;
137 static dbi_conn writehandle; /* our MASTER db connection */
138 static dbi_conn dbhandle; /* our CURRENT db connection */
139 //static osrfHash * readHandles;
141 static int max_flesh_depth = 100;
143 static int perm_at_threshold = 5;
144 static int enforce_pcrud = 0; // Boolean
145 static char* modulename = NULL;
147 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id);
149 static char* _sanitize_savepoint_name( const char* sp );
152 @brief Connect to the database.
153 @return A database connection if successful, or NULL if not.
155 dbi_conn oilsConnectDB( const char* mod_name ) {
157 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
158 if( dbi_initialize( NULL ) == -1 ) {
159 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
162 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
164 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
165 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
166 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
167 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
168 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
169 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
171 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
172 dbi_conn handle = dbi_conn_new( driver );
175 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
178 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
180 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
181 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
183 if( host ) dbi_conn_set_option( handle, "host", host );
184 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
185 if( user ) dbi_conn_set_option( handle, "username", user );
186 if( pw ) dbi_conn_set_option( handle, "password", pw );
187 if( db ) dbi_conn_set_option( handle, "dbname", db );
195 if( dbi_conn_connect( handle ) < 0 ) {
197 if( dbi_conn_connect( handle ) < 0 ) {
199 dbi_conn_error( handle, &msg );
200 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
201 msg ? msg : "(No description available)" );
206 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
212 @brief Select some options.
213 @param module_name: Name of the server.
214 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
216 This source file is used (at this writing) to implement three different servers:
217 - open-ils.reporter-store
221 These servers behave mostly the same, but they implement different combinations of
222 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
224 Here we use the server name in messages to identify which kind of server issued them.
225 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
227 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
229 module_name = "open-ils.cstore"; // bulletproofing with a default
234 modulename = strdup( module_name );
235 enforce_pcrud = do_pcrud;
236 max_flesh_depth = flesh_depth;
240 @brief Install a database connection.
241 @param conn Pointer to a database connection.
243 In some contexts, @a conn may merely provide a driver so that we can process strings
244 properly, without providing an open database connection.
246 void oilsSetDBConnection( dbi_conn conn ) {
247 dbhandle = writehandle = conn;
251 @brief Determine whether a database connection is alive.
252 @param handle Handle for a database connection.
253 @return 1 if the connection is alive, or zero if it isn't.
255 int oilsIsDBConnected( dbi_conn handle ) {
256 // Do an innocuous SELECT. If it succeeds, the database connection is still good.
257 dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
259 dbi_result_free( result );
262 // This is a terrible, horrible, no good, very bad kludge.
263 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
264 // but because (due to a previous error) the database is ignoring all commands,
265 // even innocuous SELECTs, until the current transaction is rolled back. The only
266 // known way to detect this condition via the dbi library is by looking at the error
267 // message. This approach will break if the language or wording of the message ever
269 // Note: the dbi_conn_ping function purports to determine whether the database
270 // connection is live, but at this writing this function is unreliable and useless.
271 static const char* ok_msg = "ERROR: current transaction is aborted, commands "
272 "ignored until end of transaction block\n";
274 dbi_conn_error( handle, &msg );
275 // Newer versions of dbi_conn_error return codes within the error msg.
276 // E.g. 3624914: ERROR: current transaction is aborted, commands ignored until end of transaction block
277 // Substring test should work regardless.
278 const char* substr = strstr(msg, ok_msg);
279 if( substr == NULL ) {
280 osrfLogError( OSRF_LOG_MARK, "Database connection isn't working : %s", msg );
283 return 1; // ignoring SELECT due to previous error; that's okay
288 @brief Get a table name, view name, or subquery for use in a FROM clause.
289 @param class Pointer to the IDL class entry.
290 @return A table name, a view name, or a subquery in parentheses.
292 In some cases the IDL defines a class, not with a table name or a view name, but with
293 a SELECT statement, which may be used as a subquery.
295 char* oilsGetRelation( osrfHash* classdef ) {
297 char* source_def = NULL;
298 const char* tabledef = osrfHashGet( classdef, "tablename" );
301 source_def = strdup( tabledef ); // Return the name of a table or view
303 tabledef = osrfHashGet( classdef, "source_definition" );
305 // Return a subquery, enclosed in parentheses
306 source_def = safe_malloc( strlen( tabledef ) + 3 );
307 source_def[ 0 ] = '(';
308 strcpy( source_def + 1, tabledef );
309 strcat( source_def, ")" );
311 // Not found: return an error
312 const char* classname = osrfHashGet( classdef, "classname" );
317 "%s ERROR No tablename or source_definition for class \"%s\"",
328 @brief Add datatypes from the database to the fields in the IDL.
329 @param handle Handle for a database connection
330 @return Zero if successful, or 1 upon error.
332 For each relevant class in the IDL: ask the database for the datatype of every field.
333 In particular, determine which fields are text fields and which fields are numeric
334 fields, so that we know whether to enclose their values in quotes.
336 int oilsExtendIDL( dbi_conn handle ) {
337 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
338 osrfHash* class = NULL;
339 growing_buffer* query_buf = buffer_init( 64 );
340 int results_found = 0; // boolean
342 // For each class in the IDL...
343 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
344 const char* classname = osrfHashIteratorKey( class_itr );
345 osrfHash* fields = osrfHashGet( class, "fields" );
347 // If the class is virtual, ignore it
348 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
349 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
353 char* tabledef = oilsGetRelation( class );
355 continue; // No such relation -- a query of it would be doomed to failure
357 buffer_reset( query_buf );
358 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
362 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
363 modulename, OSRF_BUFFER_C_STR( query_buf ) );
365 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
370 const char* columnName;
371 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
373 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
376 /* fetch the fieldmapper index */
377 osrfHash* _f = osrfHashGet(fields, columnName);
380 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
382 /* determine the field type and storage attributes */
384 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
386 case DBI_TYPE_INTEGER : {
388 if( !osrfHashGet(_f, "primitive") )
389 osrfHashSet(_f, "number", "primitive");
391 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
392 if( attr & DBI_INTEGER_SIZE8 )
393 osrfHashSet( _f, "INT8", "datatype" );
395 osrfHashSet( _f, "INT", "datatype" );
398 case DBI_TYPE_DECIMAL :
399 if( !osrfHashGet( _f, "primitive" ))
400 osrfHashSet( _f, "number", "primitive" );
402 osrfHashSet( _f, "NUMERIC", "datatype" );
405 case DBI_TYPE_STRING :
406 if( !osrfHashGet( _f, "primitive" ))
407 osrfHashSet( _f, "string", "primitive" );
409 osrfHashSet( _f,"TEXT", "datatype" );
412 case DBI_TYPE_DATETIME :
413 if( !osrfHashGet( _f, "primitive" ))
414 osrfHashSet( _f, "string", "primitive" );
416 osrfHashSet( _f, "TIMESTAMP", "datatype" );
419 case DBI_TYPE_BINARY :
420 if( !osrfHashGet( _f, "primitive" ))
421 osrfHashSet( _f, "string", "primitive" );
423 osrfHashSet( _f, "BYTEA", "datatype" );
428 "Setting [%s] to primitive [%s] and datatype [%s]...",
430 osrfHashGet( _f, "primitive" ),
431 osrfHashGet( _f, "datatype" )
435 } // end while loop for traversing columns of result
436 dbi_result_free( result );
439 int errnum = dbi_conn_error( handle, &msg );
440 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
441 errnum, msg ? msg : "(No description available)" );
442 // We don't check the database connection here. It's routine to get failures at
443 // this point; we routinely try to query tables that don't exist, because they
444 // are defined in the IDL but not in the database.
446 } // end for each class in IDL
448 buffer_free( query_buf );
449 osrfHashIteratorFree( class_itr );
450 child_initialized = 1;
452 if( !results_found ) {
453 osrfLogError( OSRF_LOG_MARK,
454 "No results found for any class -- bad database connection?" );
456 } else if( ! oilsIsDBConnected( handle )) {
457 osrfLogError( OSRF_LOG_MARK,
458 "Unable to extend IDL: database connection isn't working" );
466 @brief Free an osrfHash that stores a transaction ID.
467 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
469 This function is a callback, to be called by the application session when it ends.
470 The application session stores the osrfHash via an opaque pointer.
472 If the osrfHash contains an entry for the key "xact_id", it means that an
473 uncommitted transaction is pending. Roll it back.
475 void userDataFree( void* blob ) {
476 osrfHash* hash = (osrfHash*) blob;
477 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
478 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
480 int errnum = dbi_conn_error( writehandle, &msg );
481 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
482 errnum, msg ? msg : "(No description available)" );
486 if( !dbi_conn_query( writehandle, "SELECT auditor.clear_audit_info();" ) ) {
488 int errnum = dbi_conn_error( writehandle, &msg );
489 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform audit info clearing: %d %s",
490 errnum, msg ? msg : "(No description available)" );
494 osrfHashFree( hash );
498 @name Managing session data
499 @brief Maintain data stored via the userData pointer of the application session.
501 Currently, session-level data is stored in an osrfHash. Other arrangements are
502 possible, and some would be more efficient. The application session calls a
503 callback function to free userData before terminating.
505 Currently, the only data we store at the session level is the transaction id. By this
506 means we can ensure that any pending transactions are rolled back before the application
512 @brief Free an item in the application session's userData.
513 @param key The name of a key for an osrfHash.
514 @param item An opaque pointer to the item associated with the key.
516 We store an osrfHash as userData with the application session, and arrange (by
517 installing userDataFree() as a different callback) for the session to free that
518 osrfHash before terminating.
520 This function is a callback for freeing items in the osrfHash. Currently we store
522 - Transaction id of a pending transaction; a character string. Key: "xact_id".
523 - Authkey; a character string. Key: "authkey".
524 - User object from the authentication server; a jsonObject. Key: "user_login".
526 If we ever store anything else in userData, we will need to revisit this function so
527 that it will free whatever else needs freeing.
529 static void sessionDataFree( char* key, void* item ) {
530 if( !strcmp( key, "xact_id" ) || !strcmp( key, "authkey" ) || !strncmp( key, "rs_size_", 8) )
532 else if( !strcmp( key, "user_login" ) )
533 jsonObjectFree( (jsonObject*) item );
534 else if( !strcmp( key, "pcache" ) )
535 osrfHashFree( (osrfHash*) item );
538 static void pcacheFree( char* key, void* item ) {
539 osrfStringArrayFree( (osrfStringArray*) item );
543 @brief Initialize session cache.
544 @param ctx Pointer to the method context.
546 Create a cache for the session by making the session's userData member point
547 to an osrfHash instance.
549 static osrfHash* initSessionCache( osrfMethodContext* ctx ) {
550 ctx->session->userData = osrfNewHash();
551 osrfHashSetCallback( (osrfHash*) ctx->session->userData, &sessionDataFree );
552 ctx->session->userDataFree = &userDataFree;
553 return ctx->session->userData;
557 @brief Save a transaction id.
558 @param ctx Pointer to the method context.
560 Save the session_id of the current application session as a transaction id.
562 static void setXactId( osrfMethodContext* ctx ) {
563 if( ctx && ctx->session ) {
564 osrfAppSession* session = ctx->session;
566 osrfHash* cache = session->userData;
568 // If the session doesn't already have a hash, create one. Make sure
569 // that the application session frees the hash when it terminates.
571 cache = initSessionCache( ctx );
573 // Save the transaction id in the hash, with the key "xact_id"
574 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
579 @brief Get the transaction ID for the current transaction, if any.
580 @param ctx Pointer to the method context.
581 @return Pointer to the transaction ID.
583 The return value points to an internal buffer, and will become invalid upon issuing
584 a commit or rollback.
586 static inline const char* getXactId( osrfMethodContext* ctx ) {
587 if( ctx && ctx->session && ctx->session->userData )
588 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
594 @brief Clear the current transaction id.
595 @param ctx Pointer to the method context.
597 static inline void clearXactId( osrfMethodContext* ctx ) {
598 if( ctx && ctx->session && ctx->session->userData )
599 osrfHashRemove( ctx->session->userData, "xact_id" );
604 @brief Stash the location for a particular perm in the sessionData cache
605 @param ctx Pointer to the method context.
606 @param perm Name of the permission we're looking at
607 @param array StringArray of perm location ids
609 static void setPermLocationCache( osrfMethodContext* ctx, const char* perm, osrfStringArray* locations ) {
610 if( ctx && ctx->session ) {
611 osrfAppSession* session = ctx->session;
613 osrfHash* cache = session->userData;
615 // If the session doesn't already have a hash, create one. Make sure
616 // that the application session frees the hash when it terminates.
618 cache = initSessionCache( ctx );
620 osrfHash* pcache = osrfHashGet(cache, "pcache");
622 if( NULL == pcache ) {
623 pcache = osrfNewHash();
624 osrfHashSetCallback( pcache, &pcacheFree );
625 osrfHashSet( cache, pcache, "pcache" );
628 if( perm && locations )
629 osrfHashSet( pcache, locations, strdup(perm) );
634 @brief Grab stashed location for a particular perm in the sessionData cache
635 @param ctx Pointer to the method context.
636 @param perm Name of the permission we're looking at
638 static osrfStringArray* getPermLocationCache( osrfMethodContext* ctx, const char* perm ) {
639 if( ctx && ctx->session ) {
640 osrfAppSession* session = ctx->session;
641 osrfHash* cache = session->userData;
643 osrfHash* pcache = osrfHashGet(cache, "pcache");
645 return osrfHashGet( pcache, perm );
654 @brief Save the user's login in the userData for the current application session.
655 @param ctx Pointer to the method context.
656 @param user_login Pointer to the user login object to be cached (we cache the original,
659 If @a user_login is NULL, remove the user login if one is already cached.
661 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
662 if( ctx && ctx->session ) {
663 osrfAppSession* session = ctx->session;
665 osrfHash* cache = session->userData;
667 // If the session doesn't already have a hash, create one. Make sure
668 // that the application session frees the hash when it terminates.
670 cache = initSessionCache( ctx );
673 osrfHashSet( cache, user_login, "user_login" );
675 osrfHashRemove( cache, "user_login" );
680 @brief Get the user login object for the current application session, if any.
681 @param ctx Pointer to the method context.
682 @return Pointer to the user login object if found; otherwise NULL.
684 The user login object was returned from the authentication server, and then cached so
685 we don't have to call the authentication server again for the same user.
687 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
688 if( ctx && ctx->session && ctx->session->userData )
689 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
695 @brief Save a copy of an authkey in the userData of the current application session.
696 @param ctx Pointer to the method context.
697 @param authkey The authkey to be saved.
699 If @a authkey is NULL, remove the authkey if one is already cached.
701 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
702 if( ctx && ctx->session && authkey ) {
703 osrfAppSession* session = ctx->session;
704 osrfHash* cache = session->userData;
706 // If the session doesn't already have a hash, create one. Make sure
707 // that the application session frees the hash when it terminates.
709 cache = initSessionCache( ctx );
711 // Save the transaction id in the hash, with the key "xact_id"
712 if( authkey && *authkey )
713 osrfHashSet( cache, strdup( authkey ), "authkey" );
715 osrfHashRemove( cache, "authkey" );
720 @brief Reset the login timeout.
721 @param authkey The authentication key for the current login session.
722 @param now The current time.
723 @return Zero if successful, or 1 if not.
725 Tell the authentication server to reset the timeout so that the login session won't
726 expire for a while longer.
728 We could dispense with the @a now parameter by calling time(). But we just called
729 time() in order to decide whether to reset the timeout, so we might as well reuse
730 the result instead of calling time() again.
732 static int reset_timeout( const char* authkey, time_t now ) {
733 jsonObject* auth_object = jsonNewObject( authkey );
735 // Ask the authentication server to reset the timeout. It returns an event
736 // indicating success or failure.
737 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
738 "open-ils.auth.session.reset_timeout", auth_object );
739 jsonObjectFree( auth_object );
741 if( !result || result->type != JSON_HASH ) {
742 osrfLogError( OSRF_LOG_MARK,
743 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
744 jsonObjectFree( result );
745 return 1; // Not the right sort of object returned
748 const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
749 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
750 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
751 jsonObjectFree( result );
752 return 1; // Return code from method not available
755 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
756 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
758 desc = "(No reason available)"; // failsafe; shouldn't happen
759 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
760 jsonObjectFree( result );
764 // Revise our local proxy for the timeout deadline
765 // by a smallish fraction of the timeout interval
766 const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
768 timeout = "1"; // failsafe; shouldn't happen
769 time_next_reset = now + atoi( timeout ) / 15;
771 jsonObjectFree( result );
772 return 0; // Successfully reset timeout
776 @brief Get the authkey string for the current application session, if any.
777 @param ctx Pointer to the method context.
778 @return Pointer to the cached authkey if found; otherwise NULL.
780 If present, the authkey string was cached from a previous method call.
782 static const char* getAuthkey( osrfMethodContext* ctx ) {
783 if( ctx && ctx->session && ctx->session->userData ) {
784 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
785 // LFW recent changes mean the userData hash gets set up earlier, but
786 // doesn't necessarily have an authkey yet
790 // Possibly reset the authentication timeout to keep the login alive. We do so
791 // no more than once per method call, and not at all if it has been only a short
792 // time since the last reset.
794 // Here we reset explicitly, if at all. We also implicitly reset the timeout
795 // whenever we call the "open-ils.auth.session.retrieve" method.
796 if( timeout_needs_resetting ) {
797 time_t now = time( NULL );
798 if( now >= time_next_reset && reset_timeout( authkey, now ) )
799 authkey = NULL; // timeout has apparently expired already
802 timeout_needs_resetting = 0;
810 @brief Implement the transaction.begin method.
811 @param ctx Pointer to the method context.
812 @return Zero if successful, or -1 upon error.
814 Start a transaction. Save a transaction ID for future reference.
817 - authkey (PCRUD only)
819 Return to client: Transaction ID
821 int beginTransaction( osrfMethodContext* ctx ) {
822 if(osrfMethodVerifyContext( ctx )) {
823 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
827 if( enforce_pcrud ) {
828 timeout_needs_resetting = 1;
829 const jsonObject* user = verifyUserPCRUD( ctx );
834 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
837 int errnum = dbi_conn_error( writehandle, &msg );
838 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
839 modulename, errnum, msg ? msg : "(No description available)" );
840 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
841 "osrfMethodException", ctx->request, "Error starting transaction" );
842 if( !oilsIsDBConnected( writehandle ))
843 osrfAppSessionPanic( ctx->session );
846 dbi_result_free( result );
848 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
849 osrfAppRespondComplete( ctx, ret );
850 jsonObjectFree( ret );
856 @brief Implement the savepoint.set method.
857 @param ctx Pointer to the method context.
858 @return Zero if successful, or -1 if not.
860 Issue a SAVEPOINT to the database server.
863 - authkey (PCRUD only)
866 Return to client: Savepoint name
868 int setSavepoint( osrfMethodContext* ctx ) {
869 if(osrfMethodVerifyContext( ctx )) {
870 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
875 if( enforce_pcrud ) {
877 timeout_needs_resetting = 1;
878 const jsonObject* user = verifyUserPCRUD( ctx );
883 // Verify that a transaction is pending
884 const char* trans_id = getXactId( ctx );
885 if( NULL == trans_id ) {
886 osrfAppSessionStatus(
888 OSRF_STATUS_INTERNALSERVERERROR,
889 "osrfMethodException",
891 "No active transaction -- required for savepoints"
896 // Get the savepoint name from the method params
897 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
900 osrfLogWarning(OSRF_LOG_MARK, "savepoint.set called with no name");
904 char *safeSpName = _sanitize_savepoint_name( spName );
906 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", safeSpName );
910 int errnum = dbi_conn_error( writehandle, &msg );
913 "%s: Error creating savepoint %s in transaction %s: %d %s",
918 msg ? msg : "(No description available)"
920 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
921 "osrfMethodException", ctx->request, "Error creating savepoint" );
922 if( !oilsIsDBConnected( writehandle ))
923 osrfAppSessionPanic( ctx->session );
926 dbi_result_free( result );
927 jsonObject* ret = jsonNewObject( spName );
928 osrfAppRespondComplete( ctx, ret );
929 jsonObjectFree( ret );
935 @brief Implement the savepoint.release method.
936 @param ctx Pointer to the method context.
937 @return Zero if successful, or -1 if not.
939 Issue a RELEASE SAVEPOINT to the database server.
942 - authkey (PCRUD only)
945 Return to client: Savepoint name
947 int releaseSavepoint( osrfMethodContext* ctx ) {
948 if(osrfMethodVerifyContext( ctx )) {
949 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
954 if( enforce_pcrud ) {
956 timeout_needs_resetting = 1;
957 const jsonObject* user = verifyUserPCRUD( ctx );
962 // Verify that a transaction is pending
963 const char* trans_id = getXactId( ctx );
964 if( NULL == trans_id ) {
965 osrfAppSessionStatus(
967 OSRF_STATUS_INTERNALSERVERERROR,
968 "osrfMethodException",
970 "No active transaction -- required for savepoints"
975 // Get the savepoint name from the method params
976 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
979 osrfLogWarning(OSRF_LOG_MARK, "savepoint.release called with no name");
983 char *safeSpName = _sanitize_savepoint_name( spName );
985 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", safeSpName );
989 int errnum = dbi_conn_error( writehandle, &msg );
992 "%s: Error releasing savepoint %s in transaction %s: %d %s",
997 msg ? msg : "(No description available)"
999 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1000 "osrfMethodException", ctx->request, "Error releasing savepoint" );
1001 if( !oilsIsDBConnected( writehandle ))
1002 osrfAppSessionPanic( ctx->session );
1005 dbi_result_free( result );
1006 jsonObject* ret = jsonNewObject( spName );
1007 osrfAppRespondComplete( ctx, ret );
1008 jsonObjectFree( ret );
1014 @brief Implement the savepoint.rollback method.
1015 @param ctx Pointer to the method context.
1016 @return Zero if successful, or -1 if not.
1018 Issue a ROLLBACK TO SAVEPOINT to the database server.
1021 - authkey (PCRUD only)
1024 Return to client: Savepoint name
1026 int rollbackSavepoint( osrfMethodContext* ctx ) {
1027 if(osrfMethodVerifyContext( ctx )) {
1028 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1033 if( enforce_pcrud ) {
1035 timeout_needs_resetting = 1;
1036 const jsonObject* user = verifyUserPCRUD( ctx );
1041 // Verify that a transaction is pending
1042 const char* trans_id = getXactId( ctx );
1043 if( NULL == trans_id ) {
1044 osrfAppSessionStatus(
1046 OSRF_STATUS_INTERNALSERVERERROR,
1047 "osrfMethodException",
1049 "No active transaction -- required for savepoints"
1054 // Get the savepoint name from the method params
1055 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1058 osrfLogWarning(OSRF_LOG_MARK, "savepoint.rollback called with no name");
1062 char *safeSpName = _sanitize_savepoint_name( spName );
1064 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", safeSpName );
1068 int errnum = dbi_conn_error( writehandle, &msg );
1071 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
1076 msg ? msg : "(No description available)"
1078 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1079 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1080 if( !oilsIsDBConnected( writehandle ))
1081 osrfAppSessionPanic( ctx->session );
1084 dbi_result_free( result );
1085 jsonObject* ret = jsonNewObject( spName );
1086 osrfAppRespondComplete( ctx, ret );
1087 jsonObjectFree( ret );
1093 @brief Implement the transaction.commit method.
1094 @param ctx Pointer to the method context.
1095 @return Zero if successful, or -1 if not.
1097 Issue a COMMIT to the database server.
1100 - authkey (PCRUD only)
1102 Return to client: Transaction ID.
1104 int commitTransaction( osrfMethodContext* ctx ) {
1105 if(osrfMethodVerifyContext( ctx )) {
1106 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1110 if( enforce_pcrud ) {
1111 timeout_needs_resetting = 1;
1112 const jsonObject* user = verifyUserPCRUD( ctx );
1117 // Verify that a transaction is pending
1118 const char* trans_id = getXactId( ctx );
1119 if( NULL == trans_id ) {
1120 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1121 "osrfMethodException", ctx->request, "No active transaction to commit" );
1125 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1128 int errnum = dbi_conn_error( writehandle, &msg );
1129 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1130 modulename, errnum, msg ? msg : "(No description available)" );
1131 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1132 "osrfMethodException", ctx->request, "Error committing transaction" );
1133 if( !oilsIsDBConnected( writehandle ))
1134 osrfAppSessionPanic( ctx->session );
1137 dbi_result_free( result );
1138 jsonObject* ret = jsonNewObject( trans_id );
1139 osrfAppRespondComplete( ctx, ret );
1140 jsonObjectFree( ret );
1147 @brief Implement the transaction.rollback method.
1148 @param ctx Pointer to the method context.
1149 @return Zero if successful, or -1 if not.
1151 Issue a ROLLBACK to the database server.
1154 - authkey (PCRUD only)
1156 Return to client: Transaction ID
1158 int rollbackTransaction( osrfMethodContext* ctx ) {
1159 if( osrfMethodVerifyContext( ctx )) {
1160 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1164 if( enforce_pcrud ) {
1165 timeout_needs_resetting = 1;
1166 const jsonObject* user = verifyUserPCRUD( ctx );
1171 // Verify that a transaction is pending
1172 const char* trans_id = getXactId( ctx );
1173 if( NULL == trans_id ) {
1174 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1175 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1179 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1182 int errnum = dbi_conn_error( writehandle, &msg );
1183 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1184 modulename, errnum, msg ? msg : "(No description available)" );
1185 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1186 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1187 if( !oilsIsDBConnected( writehandle ))
1188 osrfAppSessionPanic( ctx->session );
1191 dbi_result_free( result );
1192 jsonObject* ret = jsonNewObject( trans_id );
1193 osrfAppRespondComplete( ctx, ret );
1194 jsonObjectFree( ret );
1201 @brief Implement the "search" method.
1202 @param ctx Pointer to the method context.
1203 @return Zero if successful, or -1 if not.
1206 - authkey (PCRUD only)
1207 - WHERE clause, as jsonObject
1208 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1210 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1211 Optionally flesh linked fields.
1213 int doSearch( osrfMethodContext* ctx ) {
1214 if( osrfMethodVerifyContext( ctx )) {
1215 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1220 timeout_needs_resetting = 1;
1222 jsonObject* where_clause;
1223 jsonObject* rest_of_query;
1225 if( enforce_pcrud ) {
1226 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1227 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1229 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1230 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1233 if( !where_clause ) {
1234 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1238 // Get the class metadata
1239 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1240 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1244 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1246 osrfAppRespondComplete( ctx, NULL );
1250 // doFieldmapperSearch() now takes care of our responding for us
1251 // // Return each row to the client
1252 // jsonObject* cur = 0;
1253 // unsigned long res_idx = 0;
1255 // while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1256 // // We used to discard based on perms here, but now that's
1257 // // inside doFieldmapperSearch()
1258 // osrfAppRespond( ctx, cur );
1261 jsonObjectFree( obj );
1263 osrfAppRespondComplete( ctx, NULL );
1268 @brief Implement the "id_list" method.
1269 @param ctx Pointer to the method context.
1270 @param err Pointer through which to return an error code.
1271 @return Zero if successful, or -1 if not.
1274 - authkey (PCRUD only)
1275 - WHERE clause, as jsonObject
1276 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1278 Return to client: The primary key values for all rows of the relevant class that
1279 satisfy a specified WHERE clause.
1281 This method relies on the assumption that every class has a primary key consisting of
1284 int doIdList( osrfMethodContext* ctx ) {
1285 if( osrfMethodVerifyContext( ctx )) {
1286 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1291 timeout_needs_resetting = 1;
1293 jsonObject* where_clause;
1294 jsonObject* rest_of_query;
1296 // We use the where clause without change. But we need to massage the rest of the
1297 // query, so we work with a copy of it instead of modifying the original.
1299 if( enforce_pcrud ) {
1300 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1301 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1303 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1304 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1307 if( !where_clause ) {
1308 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1312 // Eliminate certain SQL clauses, if present.
1313 if( rest_of_query ) {
1314 jsonObjectRemoveKey( rest_of_query, "select" );
1315 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1316 jsonObjectRemoveKey( rest_of_query, "flesh" );
1317 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1319 rest_of_query = jsonNewObjectType( JSON_HASH );
1322 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1324 // Get the class metadata
1325 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1326 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1328 // Build a SELECT list containing just the primary key,
1329 // i.e. like { "classname":["keyname"] }
1330 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1332 // Load array with name of primary key
1333 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1334 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1335 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1337 jsonObjectSetKey( rest_of_query, "select", select_clause );
1342 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1344 jsonObjectFree( rest_of_query );
1346 osrfAppRespondComplete( ctx, NULL );
1350 // Return each primary key value to the client
1352 unsigned long res_idx = 0;
1353 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1354 // We used to discard based on perms here, but now that's
1355 // inside doFieldmapperSearch()
1356 osrfAppRespond( ctx,
1357 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1360 jsonObjectFree( obj );
1361 osrfAppRespondComplete( ctx, NULL );
1366 @brief Verify that we have a valid class reference.
1367 @param ctx Pointer to the method context.
1368 @param param Pointer to the method parameters.
1369 @return 1 if the class reference is valid, or zero if it isn't.
1371 The class of the method params must match the class to which the method id devoted.
1372 For PCRUD there are additional restrictions.
1374 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1376 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1377 osrfHash* class = osrfHashGet( method_meta, "class" );
1379 // Compare the method's class to the parameters' class
1380 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1382 // Oops -- they don't match. Complain.
1383 growing_buffer* msg = buffer_init( 128 );
1386 "%s: %s method for type %s was passed a %s",
1388 osrfHashGet( method_meta, "methodtype" ),
1389 osrfHashGet( class, "classname" ),
1390 param->classname ? param->classname : "(null)"
1393 char* m = buffer_release( msg );
1394 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1402 return verifyObjectPCRUD( ctx, class, param, 1 );
1408 @brief (PCRUD only) Verify that the user is properly logged in.
1409 @param ctx Pointer to the method context.
1410 @return If the user is logged in, a pointer to the user object from the authentication
1411 server; otherwise NULL.
1413 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1414 return verifyUserPCRUDfull( ctx, 0 );
1417 static const jsonObject* verifyUserPCRUDfull( osrfMethodContext* ctx, int anon_ok ) {
1419 // Get the authkey (the first method parameter)
1420 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1422 jsonObject* user = NULL;
1424 // If we are /not/ in anonymous mode
1425 if( strcmp( "ANONYMOUS", auth ) ) {
1426 // See if we have the same authkey, and a user object,
1427 // locally cached from a previous call
1428 const char* cached_authkey = getAuthkey( ctx );
1429 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1430 const jsonObject* cached_user = getUserLogin( ctx );
1435 // We have no matching authentication data in the cache. Authenticate from scratch.
1436 jsonObject* auth_object = jsonNewObject( auth );
1438 // Fetch the user object from the authentication server
1439 user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve", auth_object );
1440 jsonObjectFree( auth_object );
1442 if( !user->classname || strcmp(user->classname, "au" )) {
1444 growing_buffer* msg = buffer_init( 128 );
1447 "%s: permacrud received a bad auth token: %s",
1452 char* m = buffer_release( msg );
1453 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1457 jsonObjectFree( user );
1459 } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
1460 // Failed to set audit information - But note that write_audit_info already set error information.
1461 jsonObjectFree( user );
1466 } else if ( anon_ok ) { // we /are/ (attempting to be) anonymous
1467 user = jsonNewObjectType(JSON_ARRAY);
1468 jsonObjectSetClass( user, "aou" );
1469 oilsFMSetString(user, "id", "-1");
1472 setUserLogin( ctx, user );
1473 setAuthkey( ctx, auth );
1475 // Allow ourselves up to a second before we have to reset the login timeout.
1476 // It would be nice to use some fraction of the timeout interval enforced by the
1477 // authentication server, but that value is not readily available at this point.
1478 // Instead, we use a conservative default interval.
1479 time_next_reset = time( NULL ) + 1;
1485 @brief For PCRUD: Determine whether the current user may access the current row.
1486 @param ctx Pointer to the method context.
1487 @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1488 @param obj Pointer to the row being potentially accessed.
1489 @return 1 if access is permitted, or 0 if it isn't.
1491 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1493 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1495 dbhandle = writehandle;
1497 // Figure out what class and method are involved
1498 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1499 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1502 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1503 if (rs_size_from_hash) {
1504 rs_size = *rs_size_from_hash;
1505 osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1509 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1510 // contexts we will do another lookup of the current row, even if we already have a
1511 // previously fetched row image, because the row image in hand may not include the
1512 // foreign key(s) that we need.
1514 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1515 // but they aren't implemented yet.
1518 if( *method_type == 's' || *method_type == 'i' ) {
1519 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1521 } else if( *method_type == 'u' || *method_type == 'd' ) {
1522 fetch = 1; // MUST go to the db for the object for update and delete
1525 // In retrieve or search ONLY we allow anon. Later perm checks will fail as they should,
1526 // in the face of a fake user but required permissions.
1528 if( *method_type == 'r' )
1531 // Get the appropriate permacrud entry from the IDL, depending on method type
1532 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1534 // No permacrud for this method type on this class
1536 growing_buffer* msg = buffer_init( 128 );
1539 "%s: %s on class %s has no permacrud IDL entry",
1541 osrfHashGet( method_metadata, "methodtype" ),
1542 osrfHashGet( class, "classname" )
1545 char* m = buffer_release( msg );
1546 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1547 "osrfMethodException", ctx->request, m );
1554 // Get the user id, and make sure the user is logged in
1555 const jsonObject* user = verifyUserPCRUDfull( ctx, anon_ok );
1557 return 0; // Not logged in or anon? No access.
1559 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1561 // Get a list of permissions from the permacrud entry.
1562 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1563 if( permission->size == 0 ) {
1566 "No permissions required for this action (class %s), passing through",
1567 osrfHashGet(class, "classname")
1572 // But, if there are perms and the user is anonymous ... FAIL
1576 // Build a list of org units that own the row. This is fairly convoluted because there
1577 // are several different ways that an org unit may own the row, as defined by the
1580 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1581 // identifying an owning org_unit..
1582 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1584 // Foreign context adds a layer of indirection. The row points to some other row that
1585 // an org unit may own. The "jump" attribute, if present, adds another layer of
1587 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1589 // The following string array stores the list of org units. (We don't have a thingie
1590 // for storing lists of integers, so we fake it with a list of strings.)
1591 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1593 const char* context_org = NULL;
1594 const char* pkey = NULL;
1595 jsonObject *param = NULL;
1596 const char* perm = NULL;
1600 const char* pkey_value = NULL;
1601 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1602 // If the global_required attribute is present and true, then the only owning
1603 // org unit is the root org unit, i.e. the one with no parent.
1604 osrfLogDebug( OSRF_LOG_MARK,
1605 "global-level permissions required, fetching top of the org tree" );
1607 // no need to check perms for org tree root retrieval
1608 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1609 // check for perm at top of org tree
1610 const char* org_tree_root_id = org_tree_root( ctx );
1611 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1613 if( org_tree_root_id ) {
1614 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1615 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1617 osrfStringArrayFree( context_org_array );
1622 // If the global_required attribute is absent or false, then we look for
1623 // local and/or foreign context. In order to find the relevant foreign
1624 // keys, we must either read the relevant row from the database, or look at
1625 // the image of the row that we already have in memory.
1627 // Even if we have an image of the row in memory, that image may not include the
1628 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1629 // of the row to make sure that we have what we need.
1631 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1632 "fetching context org ids" );
1634 pkey = osrfHashGet( class, "primarykey" );
1637 // There is no primary key, so we can't do a fresh lookup. Use the row
1638 // image that we already have. If it doesn't have everything we need, too bad.
1640 param = jsonObjectClone( obj );
1641 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1642 } else if( obj->classname ) {
1643 pkey_value = oilsFMGetStringConst( obj, pkey );
1645 param = jsonObjectClone( obj );
1646 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1649 pkey_value = jsonObjectGetString( obj );
1651 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1652 "of %s and retrieving from the database", pkey_value );
1656 // Fetch the row so that we can look at the foreign key(s)
1657 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1658 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1659 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1660 jsonObjectFree( _tmp_params );
1661 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1663 param = jsonObjectExtractIndex( _list, 0 );
1664 jsonObjectFree( _list );
1670 // The row doesn't exist. Complain, and deny access.
1671 osrfLogDebug( OSRF_LOG_MARK,
1672 "Object not found in the database with primary key %s of %s",
1675 growing_buffer* msg = buffer_init( 128 );
1678 "%s: no object found with primary key %s of %s",
1684 char* m = buffer_release( msg );
1685 osrfAppSessionStatus(
1687 OSRF_STATUS_INTERNALSERVERERROR,
1688 "osrfMethodException",
1697 if( local_context && local_context->size > 0 ) {
1698 // The IDL provides a list of column names for the foreign keys denoting
1699 // local context, i.e. columns identifying owing org units directly. Look up
1700 // the value of each one, and if it isn't null, add it to the list of org units.
1701 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1702 local_context->size );
1704 const char* lcontext = NULL;
1705 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1706 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1707 if( fkey_value ) { // if not null
1708 osrfStringArrayAdd( context_org_array, fkey_value );
1711 "adding class-local field %s (value: %s) to the context org list",
1713 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1719 if( foreign_context ) {
1720 unsigned long class_count = osrfHashGetCount( foreign_context );
1721 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1723 if( class_count > 0 ) {
1725 // The IDL provides a list of foreign key columns pointing to rows that
1726 // an org unit may own. Follow each link, identify the owning org unit,
1727 // and add it to the list.
1728 osrfHash* fcontext = NULL;
1729 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1730 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1731 // For each class to which a foreign key points:
1732 const char* class_name = osrfHashIteratorKey( class_itr );
1733 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1737 "%d foreign context fields(s) specified for class %s",
1738 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1742 // Get the name of the key field in the foreign table
1743 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1745 // Get the value of the foreign key pointing to the foreign table
1746 char* foreign_pkey_value =
1747 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1748 if( !foreign_pkey_value )
1749 continue; // Foreign key value is null; skip it
1751 // Look up the row to which the foreign key points
1752 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1754 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1755 jsonObject* _list = doFieldmapperSearch(
1756 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1757 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1759 jsonObject* _fparam = NULL;
1760 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1761 _fparam = jsonObjectExtractIndex( _list, 0 );
1763 jsonObjectFree( _tmp_params );
1764 jsonObjectFree( _list );
1766 // At this point _fparam either points to the row identified by the
1767 // foreign key, or it's NULL (no such row found).
1769 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1771 const char* bad_class = NULL; // For noting failed lookups
1773 bad_class = class_name; // Referenced row not found
1774 else if( jump_list ) {
1775 // Follow a chain of rows, linked by foreign keys, to find an owner
1776 const char* flink = NULL;
1778 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1779 // For each entry in the jump list. Each entry (i.e. flink) is
1780 // the name of a foreign key column in the current row.
1782 // From the IDL, get the linkage information for the next jump
1783 osrfHash* foreign_link_hash =
1784 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1786 // Get the class metadata for the class
1787 // to which the foreign key points
1788 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1789 osrfHashGet( foreign_link_hash, "class" ));
1791 // Get the name of the referenced key of that class
1792 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1794 // Get the value of the foreign key pointing to that class
1795 free( foreign_pkey_value );
1796 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1797 if( !foreign_pkey_value )
1798 break; // Foreign key is null; quit looking
1800 // Build a WHERE clause for the lookup
1801 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1804 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1805 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1806 _tmp_params, NULL, &err );
1807 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1809 // Get the resulting row
1810 jsonObjectFree( _fparam );
1811 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1812 _fparam = jsonObjectExtractIndex( _list, 0 );
1814 // Referenced row not found
1816 bad_class = osrfHashGet( foreign_link_hash, "class" );
1819 jsonObjectFree( _tmp_params );
1820 jsonObjectFree( _list );
1826 // We had a foreign key pointing to such-and-such a row, but then
1827 // we couldn't fetch that row. The data in the database are in an
1828 // inconsistent state; the database itself may even be corrupted.
1829 growing_buffer* msg = buffer_init( 128 );
1832 "%s: no object of class %s found with primary key %s of %s",
1836 foreign_pkey_value ? foreign_pkey_value : "(null)"
1839 char* m = buffer_release( msg );
1840 osrfAppSessionStatus(
1842 OSRF_STATUS_INTERNALSERVERERROR,
1843 "osrfMethodException",
1849 osrfHashIteratorFree( class_itr );
1850 free( foreign_pkey_value );
1851 jsonObjectFree( param );
1856 free( foreign_pkey_value );
1859 // Examine each context column of the foreign row,
1860 // and add its value to the list of org units.
1862 const char* foreign_field = NULL;
1863 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1864 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1865 osrfStringArrayAdd( context_org_array,
1866 oilsFMGetStringConst( _fparam, foreign_field ));
1867 osrfLogDebug( OSRF_LOG_MARK,
1868 "adding foreign class %s field %s (value: %s) "
1869 "to the context org list",
1872 osrfStringArrayGetString(
1873 context_org_array, context_org_array->size - 1 )
1877 jsonObjectFree( _fparam );
1881 osrfHashIteratorFree( class_itr );
1886 // If there is an owning_user attached to the action, we allow that user and users with
1887 // object perms on the object. CREATE can't use this. We only do this when there is no
1888 // context org for this action, and when we're not ignoring object perms.
1890 *method_type != 'c' &&
1891 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
1892 context_org_array->size == 0
1894 char* owning_user_field = osrfHashGet( pcrud, "owning_user" );
1895 if (owning_user_field) {
1897 if (!param) { // We didn't get it during the context lookup
1898 pkey = osrfHashGet( class, "primarykey" );
1901 // There is no primary key, so we can't do a fresh lookup. Use the row
1902 // image that we already have. If it doesn't have everything we need, too bad.
1904 param = jsonObjectClone( obj );
1905 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1906 } else if( obj->classname ) {
1907 pkey_value = oilsFMGetStringConst( obj, pkey );
1909 param = jsonObjectClone( obj );
1910 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1913 pkey_value = jsonObjectGetString( obj );
1915 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1916 "of %s and retrieving from the database", pkey_value );
1920 // Fetch the row so that we can look at the foreign key(s)
1921 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1922 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1923 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1924 jsonObjectFree( _tmp_params );
1925 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1927 param = jsonObjectExtractIndex( _list, 0 );
1928 jsonObjectFree( _list );
1933 // The row doesn't exist. Complain, and deny access.
1934 osrfLogDebug( OSRF_LOG_MARK,
1935 "Object not found in the database with primary key %s of %s",
1938 growing_buffer* msg = buffer_init( 128 );
1941 "%s: no object found with primary key %s of %s",
1947 char* m = buffer_release( msg );
1948 osrfAppSessionStatus(
1950 OSRF_STATUS_INTERNALSERVERERROR,
1951 "osrfMethodException",
1960 int ownerid = atoi( oilsFMGetStringConst( param, owning_user_field ) );
1962 // Allow the owner to do whatever
1963 if (ownerid == userid)
1967 while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
1972 "Checking object permission [%s] for user %d "
1973 "on object %s (class %s)",
1977 osrfHashGet( class, "classname" )
1980 result = dbi_conn_queryf(
1982 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s') AS has_perm;",
1985 osrfHashGet( class, "classname" ),
1992 "Received a result for object permission [%s] "
1993 "for user %d on object %s (class %s)",
1997 osrfHashGet( class, "classname" )
2000 if( dbi_result_first_row( result )) {
2001 jsonObject* return_val = oilsMakeJSONFromResult( result );
2002 const char* has_perm = jsonObjectGetString(
2003 jsonObjectGetKeyConst( return_val, "has_perm" ));
2007 "Status of object permission [%s] for user %d "
2008 "on object %s (class %s) is %s",
2012 osrfHashGet(class, "classname"),
2016 if( *has_perm == 't' )
2018 jsonObjectFree( return_val );
2021 dbi_result_free( result );
2026 int errnum = dbi_conn_error( writehandle, &msg );
2027 osrfLogWarning( OSRF_LOG_MARK,
2028 "Unable to call check object permissions: %d, %s",
2029 errnum, msg ? msg : "(No description available)" );
2030 if( !oilsIsDBConnected( writehandle ))
2031 osrfAppSessionPanic( ctx->session );
2038 // For every combination of permission and context org unit: call a stored procedure
2039 // to determine if the user has this permission in the context of this org unit.
2040 // If the answer is yes at any point, then we're done, and the user has permission.
2041 // In other words permissions are additive.
2043 while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
2046 osrfStringArray* pcache = NULL;
2047 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
2048 pcache = getPermLocationCache(ctx, perm);
2051 pcache = osrfNewStringArray(0);
2053 result = dbi_conn_queryf(
2055 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
2063 "Received a result for permission [%s] for user %d",
2068 if( dbi_result_first_row( result )) {
2070 jsonObject* return_val = oilsMakeJSONFromResult( result );
2071 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
2072 jsonObjectFree( return_val );
2073 } while( dbi_result_next_row( result ));
2075 setPermLocationCache(ctx, perm, pcache);
2078 dbi_result_free( result );
2084 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
2086 if (rs_size > perm_at_threshold) {
2087 if (osrfStringArrayContains( pcache, context_org )) {
2095 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
2097 !str_is_true( osrfHashGet(pcrud, "global_required") ) ||
2098 osrfHashGet(pcrud, "owning_user")
2103 "Checking object permission [%s] for user %d "
2104 "on object %s (class %s) at org %d",
2108 osrfHashGet( class, "classname" ),
2112 result = dbi_conn_queryf(
2114 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
2117 osrfHashGet( class, "classname" ),
2125 "Received a result for object permission [%s] "
2126 "for user %d on object %s (class %s) at org %d",
2130 osrfHashGet( class, "classname" ),
2134 if( dbi_result_first_row( result )) {
2135 jsonObject* return_val = oilsMakeJSONFromResult( result );
2136 const char* has_perm = jsonObjectGetString(
2137 jsonObjectGetKeyConst( return_val, "has_perm" ));
2141 "Status of object permission [%s] for user %d "
2142 "on object %s (class %s) at org %d is %s",
2146 osrfHashGet(class, "classname"),
2151 if( *has_perm == 't' )
2153 jsonObjectFree( return_val );
2156 dbi_result_free( result );
2161 int errnum = dbi_conn_error( writehandle, &msg );
2162 osrfLogWarning( OSRF_LOG_MARK,
2163 "Unable to call check object permissions: %d, %s",
2164 errnum, msg ? msg : "(No description available)" );
2165 if( !oilsIsDBConnected( writehandle ))
2166 osrfAppSessionPanic( ctx->session );
2170 if (rs_size > perm_at_threshold) break;
2172 osrfLogDebug( OSRF_LOG_MARK,
2173 "Checking non-object permission [%s] for user %d at org %d",
2174 perm, userid, atoi(context_org) );
2175 result = dbi_conn_queryf(
2177 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
2184 osrfLogDebug( OSRF_LOG_MARK,
2185 "Received a result for permission [%s] for user %d at org %d",
2186 perm, userid, atoi( context_org ));
2187 if( dbi_result_first_row( result )) {
2188 jsonObject* return_val = oilsMakeJSONFromResult( result );
2189 const char* has_perm = jsonObjectGetString(
2190 jsonObjectGetKeyConst( return_val, "has_perm" ));
2191 osrfLogDebug( OSRF_LOG_MARK,
2192 "Status of permission [%s] for user %d at org %d is [%s]",
2193 perm, userid, atoi( context_org ), has_perm );
2194 if( *has_perm == 't' )
2196 jsonObjectFree( return_val );
2199 dbi_result_free( result );
2204 int errnum = dbi_conn_error( writehandle, &msg );
2205 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
2206 errnum, msg ? msg : "(No description available)" );
2207 if( !oilsIsDBConnected( writehandle ))
2208 osrfAppSessionPanic( ctx->session );
2217 osrfStringArrayFree( context_org_array );
2223 @brief Look up the root of the org_unit tree.
2224 @param ctx Pointer to the method context.
2225 @return The id of the root org unit, as a character string.
2227 Query actor.org_unit where parent_ou is null, and return the id as a string.
2229 This function assumes that there is only one root org unit, i.e. that we
2230 have a single tree, not a forest.
2232 The calling code is responsible for freeing the returned string.
2234 static const char* org_tree_root( osrfMethodContext* ctx ) {
2236 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
2237 static time_t last_lookup_time = 0;
2238 time_t current_time = time( NULL );
2240 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2241 // We successfully looked this up less than an hour ago.
2242 // It's not likely to have changed since then.
2243 return strdup( cached_root_id );
2245 last_lookup_time = current_time;
2248 jsonObject* where_clause = single_hash( "parent_ou", NULL );
2249 jsonObject* result = doFieldmapperSearch(
2250 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2251 jsonObjectFree( where_clause );
2253 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2256 jsonObjectFree( result );
2258 growing_buffer* msg = buffer_init( 128 );
2259 OSRF_BUFFER_ADD( msg, modulename );
2260 OSRF_BUFFER_ADD( msg,
2261 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2263 char* m = buffer_release( msg );
2264 osrfAppSessionStatus( ctx->session,
2265 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2268 cached_root_id[ 0 ] = '\0';
2272 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2273 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2275 strcpy( cached_root_id, root_org_unit_id );
2276 jsonObjectFree( result );
2277 return cached_root_id;
2281 @brief Create a JSON_HASH with a single key/value pair.
2282 @param key The key of the key/value pair.
2283 @param value the value of the key/value pair.
2284 @return Pointer to a newly created jsonObject of type JSON_HASH.
2286 The value of the key/value is either a string or (if @a value is NULL) a null.
2288 static jsonObject* single_hash( const char* key, const char* value ) {
2290 if( ! key ) key = "";
2292 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2293 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2298 int doCreate( osrfMethodContext* ctx ) {
2299 if(osrfMethodVerifyContext( ctx )) {
2300 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2305 timeout_needs_resetting = 1;
2307 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2308 jsonObject* target = NULL;
2309 jsonObject* options = NULL;
2311 if( enforce_pcrud ) {
2312 target = jsonObjectGetIndex( ctx->params, 1 );
2313 options = jsonObjectGetIndex( ctx->params, 2 );
2315 target = jsonObjectGetIndex( ctx->params, 0 );
2316 options = jsonObjectGetIndex( ctx->params, 1 );
2319 if( !verifyObjectClass( ctx, target )) {
2320 osrfAppRespondComplete( ctx, NULL );
2324 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2326 const char* trans_id = getXactId( ctx );
2328 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2330 osrfAppSessionStatus(
2332 OSRF_STATUS_BADREQUEST,
2333 "osrfMethodException",
2335 "No active transaction -- required for CREATE"
2337 osrfAppRespondComplete( ctx, NULL );
2341 // The following test is harmless but redundant. If a class is
2342 // readonly, we don't register a create method for it.
2343 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2344 osrfAppSessionStatus(
2346 OSRF_STATUS_BADREQUEST,
2347 "osrfMethodException",
2349 "Cannot INSERT readonly class"
2351 osrfAppRespondComplete( ctx, NULL );
2355 // Set the last_xact_id
2356 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2358 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2359 trans_id, target->classname, index);
2360 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2363 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2365 dbhandle = writehandle;
2367 osrfHash* fields = osrfHashGet( meta, "fields" );
2368 char* pkey = osrfHashGet( meta, "primarykey" );
2369 char* seq = osrfHashGet( meta, "sequence" );
2371 growing_buffer* table_buf = buffer_init( 128 );
2372 growing_buffer* col_buf = buffer_init( 128 );
2373 growing_buffer* val_buf = buffer_init( 128 );
2375 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2376 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2377 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2378 buffer_add( val_buf,"VALUES (" );
2382 osrfHash* field = NULL;
2383 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2384 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2386 const char* field_name = osrfHashIteratorKey( field_itr );
2388 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2391 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2394 if( field_object && field_object->classname ) {
2395 value = oilsFMGetString(
2397 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2399 } else if( field_object && JSON_BOOL == field_object->type ) {
2400 if( jsonBoolIsTrue( field_object ) )
2401 value = strdup( "t" );
2403 value = strdup( "f" );
2405 value = jsonObjectToSimpleString( field_object );
2411 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2412 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2415 buffer_add( col_buf, field_name );
2417 if( !field_object || field_object->type == JSON_NULL ) {
2418 buffer_add( val_buf, "DEFAULT" );
2420 } else if( !strcmp( get_primitive( field ), "number" )) {
2421 const char* numtype = get_datatype( field );
2422 if( !strcmp( numtype, "INT8" )) {
2423 buffer_fadd( val_buf, "%lld", atoll( value ));
2425 } else if( !strcmp( numtype, "INT" )) {
2426 buffer_fadd( val_buf, "%d", atoi( value ));
2428 } else if( !strcmp( numtype, "NUMERIC" )) {
2429 buffer_fadd( val_buf, "%f", atof( value ));
2432 if( dbi_conn_quote_string( writehandle, &value )) {
2433 OSRF_BUFFER_ADD( val_buf, value );
2436 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2437 osrfAppSessionStatus(
2439 OSRF_STATUS_INTERNALSERVERERROR,
2440 "osrfMethodException",
2442 "Error quoting string -- please see the error log for more details"
2445 buffer_free( table_buf );
2446 buffer_free( col_buf );
2447 buffer_free( val_buf );
2448 osrfAppRespondComplete( ctx, NULL );
2456 osrfHashIteratorFree( field_itr );
2458 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2459 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2461 char* table_str = buffer_release( table_buf );
2462 char* col_str = buffer_release( col_buf );
2463 char* val_str = buffer_release( val_buf );
2464 growing_buffer* sql = buffer_init( 128 );
2465 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2470 char* query = buffer_release( sql );
2472 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2474 jsonObject* obj = NULL;
2477 dbi_result result = dbi_conn_query( writehandle, query );
2479 obj = jsonNewObject( NULL );
2481 int errnum = dbi_conn_error( writehandle, &msg );
2484 "%s ERROR inserting %s object using query [%s]: %d %s",
2486 osrfHashGet(meta, "fieldmapper"),
2489 msg ? msg : "(No description available)"
2491 osrfAppSessionStatus(
2493 OSRF_STATUS_INTERNALSERVERERROR,
2494 "osrfMethodException",
2496 "INSERT error -- please see the error log for more details"
2498 if( !oilsIsDBConnected( writehandle ))
2499 osrfAppSessionPanic( ctx->session );
2502 dbi_result_free( result );
2504 char* id = oilsFMGetString( target, pkey );
2506 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2507 growing_buffer* _id = buffer_init( 10 );
2508 buffer_fadd( _id, "%lld", new_id );
2509 id = buffer_release( _id );
2512 // Find quietness specification, if present
2513 const char* quiet_str = NULL;
2515 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2517 quiet_str = jsonObjectGetString( quiet_obj );
2520 if( str_is_true( quiet_str )) { // if quietness is specified
2521 obj = jsonNewObject( id );
2525 // Fetch the row that we just inserted, so that we can return it to the client
2526 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2527 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2530 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2534 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2536 jsonObjectFree( list );
2537 jsonObjectFree( where_clause );
2544 osrfAppRespondComplete( ctx, obj );
2545 jsonObjectFree( obj );
2550 @brief Implement the retrieve method.
2551 @param ctx Pointer to the method context.
2552 @param err Pointer through which to return an error code.
2553 @return If successful, a pointer to the result to be returned to the client;
2556 From the method's class, fetch a row with a specified value in the primary key. This
2557 method relies on the database design convention that a primary key consists of a single
2561 - authkey (PCRUD only)
2562 - value of the primary key for the desired row, for building the WHERE clause
2563 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2565 Return to client: One row from the query.
2567 int doRetrieve( osrfMethodContext* ctx ) {
2568 if(osrfMethodVerifyContext( ctx )) {
2569 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2574 timeout_needs_resetting = 1;
2579 if( enforce_pcrud ) {
2584 // Get the class metadata
2585 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2587 // Get the value of the primary key, from a method parameter
2588 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2592 "%s retrieving %s object with primary key value of %s",
2594 osrfHashGet( class_def, "fieldmapper" ),
2595 jsonObjectGetString( id_obj )
2598 // Build a WHERE clause based on the key value
2599 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2602 osrfHashGet( class_def, "primarykey" ), // name of key column
2603 jsonObjectClone( id_obj ) // value of key column
2606 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2610 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2612 jsonObjectFree( where_clause );
2614 osrfAppRespondComplete( ctx, NULL );
2618 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2619 jsonObjectFree( list );
2621 if( enforce_pcrud ) {
2622 // no result, skip this entirely
2623 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2624 jsonObjectFree( obj );
2626 growing_buffer* msg = buffer_init( 128 );
2627 OSRF_BUFFER_ADD( msg, modulename );
2628 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2630 char* m = buffer_release( msg );
2631 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2635 osrfAppRespondComplete( ctx, NULL );
2640 // doFieldmapperSearch() now does the responding for us
2641 //osrfAppRespondComplete( ctx, obj );
2642 osrfAppRespondComplete( ctx, NULL );
2644 jsonObjectFree( obj );
2649 @brief Translate a numeric value to a string representation for the database.
2650 @param field Pointer to the IDL field definition.
2651 @param value Pointer to a jsonObject holding the value of a field.
2652 @return Pointer to a newly allocated string.
2654 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2655 its contents are numeric. A non-numeric string is likely to result in invalid SQL.
2657 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2659 The calling code is responsible for freeing the resulting string by calling free().
2661 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2662 growing_buffer* val_buf = buffer_init( 32 );
2664 // If the value is a number and the DB field is numeric, no quotes needed
2665 if( value->type == JSON_NUMBER && !strcmp( get_primitive( field ), "number") ) {
2666 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2668 // Presumably this was really intended to be a string, so quote it
2669 char* str = jsonObjectToSimpleString( value );
2670 if( dbi_conn_quote_string( dbhandle, &str )) {
2671 OSRF_BUFFER_ADD( val_buf, str );
2674 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2676 buffer_free( val_buf );
2681 return buffer_release( val_buf );
2684 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2685 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2686 growing_buffer* sql_buf = buffer_init( 32 );
2692 osrfHashGet( field, "name" )
2696 buffer_add( sql_buf, "IN (" );
2697 } else if( !strcasecmp( op,"not in" )) {
2698 buffer_add( sql_buf, "NOT IN (" );
2700 buffer_add( sql_buf, "IN (" );
2703 if( node->type == JSON_HASH ) {
2704 // subquery predicate
2705 char* subpred = buildQuery( ctx, node, SUBSELECT );
2707 buffer_free( sql_buf );
2711 buffer_add( sql_buf, subpred );
2714 } else if( node->type == JSON_ARRAY ) {
2715 // literal value list
2716 int in_item_index = 0;
2717 int in_item_first = 1;
2718 const jsonObject* in_item;
2719 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2724 buffer_add( sql_buf, ", " );
2727 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2728 osrfLogError( OSRF_LOG_MARK,
2729 "%s: Expected string or number within IN list; found %s",
2730 modulename, json_type( in_item->type ) );
2731 buffer_free( sql_buf );
2735 // Append the literal value -- quoted if not a number
2736 if( JSON_NUMBER == in_item->type ) {
2737 char* val = jsonNumberToDBString( field, in_item );
2738 OSRF_BUFFER_ADD( sql_buf, val );
2741 } else if( !strcmp( get_primitive( field ), "number" )) {
2742 char* val = jsonNumberToDBString( field, in_item );
2743 OSRF_BUFFER_ADD( sql_buf, val );
2747 char* key_string = jsonObjectToSimpleString( in_item );
2748 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2749 OSRF_BUFFER_ADD( sql_buf, key_string );
2752 osrfLogError( OSRF_LOG_MARK,
2753 "%s: Error quoting key string [%s]", modulename, key_string );
2755 buffer_free( sql_buf );
2761 if( in_item_first ) {
2762 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2763 buffer_free( sql_buf );
2767 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2768 modulename, json_type( node->type ));
2769 buffer_free( sql_buf );
2773 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2775 return buffer_release( sql_buf );
2778 // Receive a JSON_ARRAY representing a function call. The first
2779 // entry in the array is the function name. The rest are parameters.
2780 static char* searchValueTransform( const jsonObject* array ) {
2782 if( array->size < 1 ) {
2783 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2787 // Get the function name
2788 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2789 if( func_item->type != JSON_STRING ) {
2790 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2791 modulename, json_type( func_item->type ));
2795 growing_buffer* sql_buf = buffer_init( 32 );
2797 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2798 OSRF_BUFFER_ADD( sql_buf, "( " );
2800 // Get the parameters
2801 int func_item_index = 1; // We already grabbed the zeroth entry
2802 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2804 // Add a separator comma, if we need one
2805 if( func_item_index > 2 )
2806 buffer_add( sql_buf, ", " );
2808 // Add the current parameter
2809 if( func_item->type == JSON_NULL ) {
2810 buffer_add( sql_buf, "NULL" );
2812 if( func_item->type == JSON_BOOL ) {
2813 if( jsonBoolIsTrue(func_item) ) {
2814 buffer_add( sql_buf, "TRUE" );
2816 buffer_add( sql_buf, "FALSE" );
2819 char* val = jsonObjectToSimpleString( func_item );
2820 if( dbi_conn_quote_string( dbhandle, &val )) {
2821 OSRF_BUFFER_ADD( sql_buf, val );
2824 osrfLogError( OSRF_LOG_MARK,
2825 "%s: Error quoting key string [%s]", modulename, val );
2826 buffer_free( sql_buf );
2834 buffer_add( sql_buf, " )" );
2836 return buffer_release( sql_buf );
2839 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2840 const jsonObject* node, const char* op ) {
2842 if( ! is_good_operator( op ) ) {
2843 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2847 char* val = searchValueTransform( node );
2851 const char* right_percent = "";
2852 const char* real_op = op;
2854 if( !strcasecmp( op, "startwith") ) {
2856 right_percent = "|| '%'";
2859 growing_buffer* sql_buf = buffer_init( 32 );
2862 "\"%s\".%s %s %s%s",
2864 osrfHashGet( field, "name" ),
2872 return buffer_release( sql_buf );
2875 // class_alias is a class name or other table alias
2876 // field is a field definition as stored in the IDL
2877 // node comes from the method parameter, and may represent an entry in the SELECT list
2878 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2879 const jsonObject* node ) {
2880 growing_buffer* sql_buf = buffer_init( 32 );
2882 const char* field_transform = jsonObjectGetString(
2883 jsonObjectGetKeyConst( node, "transform" ) );
2884 const char* transform_subcolumn = jsonObjectGetString(
2885 jsonObjectGetKeyConst( node, "result_field" ) );
2887 if( transform_subcolumn ) {
2888 if( ! is_identifier( transform_subcolumn ) ) {
2889 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2890 modulename, transform_subcolumn );
2891 buffer_free( sql_buf );
2894 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2897 if( field_transform ) {
2899 if( ! is_identifier( field_transform ) ) {
2900 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2901 modulename, field_transform );
2902 buffer_free( sql_buf );
2906 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2907 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2908 field_transform, class_alias, osrfHashGet( field, "name" ));
2910 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2911 field_transform, class_alias, osrfHashGet( field, "name" ));
2914 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2917 if( array->type != JSON_ARRAY ) {
2918 osrfLogError( OSRF_LOG_MARK,
2919 "%s: Expected JSON_ARRAY for function params; found %s",
2920 modulename, json_type( array->type ) );
2921 buffer_free( sql_buf );
2924 int func_item_index = 0;
2925 jsonObject* func_item;
2926 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2928 char* val = jsonObjectToSimpleString( func_item );
2931 buffer_add( sql_buf, ",NULL" );
2932 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2933 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2934 OSRF_BUFFER_ADD( sql_buf, val );
2936 osrfLogError( OSRF_LOG_MARK,
2937 "%s: Error quoting key string [%s]", modulename, val );
2939 buffer_free( sql_buf );
2946 buffer_add( sql_buf, " )" );
2949 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2952 if( transform_subcolumn )
2953 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2955 return buffer_release( sql_buf );
2958 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2959 const jsonObject* node, const char* op ) {
2961 if( ! is_good_operator( op ) ) {
2962 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2966 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2967 if( ! field_transform )
2970 int extra_parens = 0; // boolean
2972 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2974 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2976 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2978 free( field_transform );
2982 } else if( value_obj->type == JSON_ARRAY ) {
2983 value = searchValueTransform( value_obj );
2985 osrfLogError( OSRF_LOG_MARK,
2986 "%s: Error building value transform for field transform", modulename );
2987 free( field_transform );
2990 } else if( value_obj->type == JSON_HASH ) {
2991 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2993 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2995 free( field_transform );
2999 } else if( value_obj->type == JSON_NUMBER ) {
3000 value = jsonNumberToDBString( field, value_obj );
3001 } else if( value_obj->type == JSON_NULL ) {
3002 osrfLogError( OSRF_LOG_MARK,
3003 "%s: Error building predicate for field transform: null value", modulename );
3004 free( field_transform );
3006 } else if( value_obj->type == JSON_BOOL ) {
3007 osrfLogError( OSRF_LOG_MARK,
3008 "%s: Error building predicate for field transform: boolean value", modulename );
3009 free( field_transform );
3012 if( !strcmp( get_primitive( field ), "number") ) {
3013 value = jsonNumberToDBString( field, value_obj );
3015 value = jsonObjectToSimpleString( value_obj );
3016 if( !dbi_conn_quote_string( dbhandle, &value )) {
3017 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3018 modulename, value );
3020 free( field_transform );
3026 const char* left_parens = "";
3027 const char* right_parens = "";
3029 if( extra_parens ) {
3034 const char* right_percent = "";
3035 const char* real_op = op;
3037 if( !strcasecmp( op, "startwith") ) {
3039 right_percent = "|| '%'";
3042 growing_buffer* sql_buf = buffer_init( 32 );
3046 "%s%s %s %s %s%s %s%s",
3058 free( field_transform );
3060 return buffer_release( sql_buf );
3063 static char* searchSimplePredicate( const char* op, const char* class_alias,
3064 osrfHash* field, const jsonObject* node ) {
3066 if( ! is_good_operator( op ) ) {
3067 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
3073 // Get the value to which we are comparing the specified column
3074 if( node->type != JSON_NULL ) {
3075 if( node->type == JSON_NUMBER ) {
3076 val = jsonNumberToDBString( field, node );
3077 } else if( !strcmp( get_primitive( field ), "number" ) ) {
3078 val = jsonNumberToDBString( field, node );
3080 val = jsonObjectToSimpleString( node );
3085 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
3086 // Value is not numeric; enclose it in quotes
3087 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
3088 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3095 // Compare to a null value
3096 val = strdup( "NULL" );
3097 if( strcmp( op, "=" ))
3103 const char* right_percent = "";
3104 const char* real_op = op;
3106 if( !strcasecmp( op, "startwith") ) {
3108 right_percent = "|| '%'";
3111 growing_buffer* sql_buf = buffer_init( 32 );
3112 buffer_fadd( sql_buf, "\"%s\".%s %s %s%s", class_alias, osrfHashGet(field, "name"), real_op, val, right_percent );
3113 char* pred = buffer_release( sql_buf );
3120 static char* searchBETWEENPredicate( const char* class_alias,
3121 osrfHash* field, const jsonObject* node ) {
3123 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
3124 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
3126 if( NULL == y_node ) {
3127 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
3130 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
3131 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
3138 if( !strcmp( get_primitive( field ), "number") ) {
3139 x_string = jsonNumberToDBString( field, x_node );
3140 y_string = jsonNumberToDBString( field, y_node );
3143 x_string = jsonObjectToSimpleString( x_node );
3144 y_string = jsonObjectToSimpleString( y_node );
3145 if( !(dbi_conn_quote_string( dbhandle, &x_string )
3146 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
3147 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
3148 modulename, x_string, y_string );
3155 growing_buffer* sql_buf = buffer_init( 32 );
3156 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
3157 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
3161 return buffer_release( sql_buf );
3164 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
3165 jsonObject* node, osrfMethodContext* ctx ) {
3168 if( node->type == JSON_ARRAY ) { // equality IN search
3169 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
3170 } else if( node->type == JSON_HASH ) { // other search
3171 jsonIterator* pred_itr = jsonNewIterator( node );
3172 if( !jsonIteratorHasNext( pred_itr ) ) {
3173 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
3174 modulename, osrfHashGet(field, "name" ));
3176 jsonObject* pred_node = jsonIteratorNext( pred_itr );
3178 // Verify that there are no additional predicates
3179 if( jsonIteratorHasNext( pred_itr ) ) {
3180 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
3181 modulename, osrfHashGet(field, "name" ));
3182 } else if( !(strcasecmp( pred_itr->key,"between" )) )
3183 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
3184 else if( !(strcasecmp( pred_itr->key,"in" ))
3185 || !(strcasecmp( pred_itr->key,"not in" )) )
3186 pred = searchINPredicate(
3187 class_info->alias, field, pred_node, pred_itr->key, ctx );
3188 else if( pred_node->type == JSON_ARRAY )
3189 pred = searchFunctionPredicate(
3190 class_info->alias, field, pred_node, pred_itr->key );
3191 else if( pred_node->type == JSON_HASH )
3192 pred = searchFieldTransformPredicate(
3193 class_info, field, pred_node, pred_itr->key );
3195 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
3197 jsonIteratorFree( pred_itr );
3199 } else if( node->type == JSON_NULL ) { // IS NULL search
3200 growing_buffer* _p = buffer_init( 64 );
3203 "\"%s\".%s IS NULL",
3205 osrfHashGet( field, "name" )
3207 pred = buffer_release( _p );
3208 } else { // equality search
3209 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3228 field : call_number,
3244 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3246 const jsonObject* working_hash;
3247 jsonObject* freeable_hash = NULL;
3249 if( join_hash->type == JSON_HASH ) {
3250 working_hash = join_hash;
3251 } else if( join_hash->type == JSON_STRING ) {
3252 // turn it into a JSON_HASH by creating a wrapper
3253 // around a copy of the original
3254 const char* _tmp = jsonObjectGetString( join_hash );
3255 freeable_hash = jsonNewObjectType( JSON_HASH );
3256 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3257 working_hash = freeable_hash;
3261 "%s: JOIN failed; expected JSON object type not found",
3267 growing_buffer* join_buf = buffer_init( 128 );
3268 const char* leftclass = left_info->class_name;
3270 jsonObject* snode = NULL;
3271 jsonIterator* search_itr = jsonNewIterator( working_hash );
3273 while ( (snode = jsonIteratorNext( search_itr )) ) {
3274 const char* right_alias = search_itr->key;
3276 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3278 class = right_alias;
3280 const ClassInfo* right_info = add_joined_class( right_alias, class );
3284 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3288 jsonIteratorFree( search_itr );
3289 buffer_free( join_buf );
3291 jsonObjectFree( freeable_hash );
3294 osrfHash* links = right_info->links;
3295 const char* table = right_info->source_def;
3297 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3298 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3300 if( field && !fkey ) {
3301 // Look up the corresponding join column in the IDL.
3302 // The link must be defined in the child table,
3303 // and point to the right parent table.
3304 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3305 const char* reltype = NULL;
3306 const char* other_class = NULL;
3307 reltype = osrfHashGet( idl_link, "reltype" );
3308 if( reltype && strcmp( reltype, "has_many" ) )
3309 other_class = osrfHashGet( idl_link, "class" );
3310 if( other_class && !strcmp( other_class, leftclass ) )
3311 fkey = osrfHashGet( idl_link, "key" );
3315 "%s: JOIN failed. No link defined from %s.%s to %s",
3321 buffer_free( join_buf );
3323 jsonObjectFree( freeable_hash );
3324 jsonIteratorFree( search_itr );
3328 } else if( !field && fkey ) {
3329 // Look up the corresponding join column in the IDL.
3330 // The link must be defined in the child table,
3331 // and point to the right parent table.
3332 osrfHash* left_links = left_info->links;
3333 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3334 const char* reltype = NULL;
3335 const char* other_class = NULL;
3336 reltype = osrfHashGet( idl_link, "reltype" );
3337 if( reltype && strcmp( reltype, "has_many" ) )
3338 other_class = osrfHashGet( idl_link, "class" );
3339 if( other_class && !strcmp( other_class, class ) )
3340 field = osrfHashGet( idl_link, "key" );
3344 "%s: JOIN failed. No link defined from %s.%s to %s",
3350 buffer_free( join_buf );
3352 jsonObjectFree( freeable_hash );
3353 jsonIteratorFree( search_itr );
3357 } else if( !field && !fkey ) {
3358 osrfHash* left_links = left_info->links;
3360 // For each link defined for the left class:
3361 // see if the link references the joined class
3362 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3363 osrfHash* curr_link = NULL;
3364 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3365 const char* other_class = osrfHashGet( curr_link, "class" );
3366 if( other_class && !strcmp( other_class, class ) ) {
3368 // In the IDL, the parent class doesn't always know then names of the child
3369 // columns that are pointing to it, so don't use that end of the link
3370 const char* reltype = osrfHashGet( curr_link, "reltype" );
3371 if( reltype && strcmp( reltype, "has_many" ) ) {
3372 // Found a link between the classes
3373 fkey = osrfHashIteratorKey( itr );
3374 field = osrfHashGet( curr_link, "key" );
3379 osrfHashIteratorFree( itr );
3381 if( !field || !fkey ) {
3382 // Do another such search, with the classes reversed
3384 // For each link defined for the joined class:
3385 // see if the link references the left class
3386 osrfHashIterator* itr = osrfNewHashIterator( links );
3387 osrfHash* curr_link = NULL;
3388 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3389 const char* other_class = osrfHashGet( curr_link, "class" );
3390 if( other_class && !strcmp( other_class, leftclass ) ) {
3392 // In the IDL, the parent class doesn't know then names of the child
3393 // columns that are pointing to it, so don't use that end of the link
3394 const char* reltype = osrfHashGet( curr_link, "reltype" );
3395 if( reltype && strcmp( reltype, "has_many" ) ) {
3396 // Found a link between the classes
3397 field = osrfHashIteratorKey( itr );
3398 fkey = osrfHashGet( curr_link, "key" );
3403 osrfHashIteratorFree( itr );
3406 if( !field || !fkey ) {
3409 "%s: JOIN failed. No link defined between %s and %s",
3414 buffer_free( join_buf );
3416 jsonObjectFree( freeable_hash );
3417 jsonIteratorFree( search_itr );
3422 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3424 if( !strcasecmp( type,"left" )) {
3425 buffer_add( join_buf, " LEFT JOIN" );
3426 } else if( !strcasecmp( type,"right" )) {
3427 buffer_add( join_buf, " RIGHT JOIN" );
3428 } else if( !strcasecmp( type,"full" )) {
3429 buffer_add( join_buf, " FULL JOIN" );
3431 buffer_add( join_buf, " INNER JOIN" );
3434 buffer_add( join_buf, " INNER JOIN" );
3437 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3438 table, right_alias, right_alias, field, left_info->alias, fkey );
3440 // Add any other join conditions as specified by "filter"
3441 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3443 const char* filter_op = jsonObjectGetString(
3444 jsonObjectGetKeyConst( snode, "filter_op" ) );
3445 if( filter_op && !strcasecmp( "or",filter_op )) {
3446 buffer_add( join_buf, " OR " );
3448 buffer_add( join_buf, " AND " );
3451 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3453 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3454 OSRF_BUFFER_ADD( join_buf, jpred );
3459 "%s: JOIN failed. Invalid conditional expression.",
3462 jsonIteratorFree( search_itr );
3463 buffer_free( join_buf );
3465 jsonObjectFree( freeable_hash );
3470 buffer_add( join_buf, " ) " );
3472 // Recursively add a nested join, if one is present
3473 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3475 char* jpred = searchJOIN( join_filter, right_info );
3477 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3478 OSRF_BUFFER_ADD( join_buf, jpred );
3481 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3482 jsonIteratorFree( search_itr );
3483 buffer_free( join_buf );
3485 jsonObjectFree( freeable_hash );
3492 jsonObjectFree( freeable_hash );
3493 jsonIteratorFree( search_itr );
3495 return buffer_release( join_buf );
3500 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3501 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3502 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3504 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3506 search_hash is the JSON expression of the conditions.
3507 meta is the class definition from the IDL, for the relevant table.
3508 opjoin_type indicates whether multiple conditions, if present, should be
3509 connected by AND or OR.
3510 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3511 to pass it to other functions -- and all they do with it is to use the session
3512 and request members to send error messages back to the client.
3516 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3517 int opjoin_type, osrfMethodContext* ctx ) {
3521 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3522 "opjoin_type = %d, ctx addr = %p",
3525 class_info->class_def,
3530 growing_buffer* sql_buf = buffer_init( 128 );
3532 jsonObject* node = NULL;
3535 if( search_hash->type == JSON_ARRAY ) {
3536 if( 0 == search_hash->size ) {
3539 "%s: Invalid predicate structure: empty JSON array",
3542 buffer_free( sql_buf );
3546 unsigned long i = 0;
3547 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3551 if( opjoin_type == OR_OP_JOIN )
3552 buffer_add( sql_buf, " OR " );
3554 buffer_add( sql_buf, " AND " );
3557 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3559 buffer_free( sql_buf );
3563 buffer_fadd( sql_buf, "( %s )", subpred );
3567 } else if( search_hash->type == JSON_HASH ) {
3568 osrfLogDebug( OSRF_LOG_MARK,
3569 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3570 jsonIterator* search_itr = jsonNewIterator( search_hash );
3571 if( !jsonIteratorHasNext( search_itr ) ) {
3574 "%s: Invalid predicate structure: empty JSON object",
3577 jsonIteratorFree( search_itr );
3578 buffer_free( sql_buf );
3582 while( (node = jsonIteratorNext( search_itr )) ) {
3587 if( opjoin_type == OR_OP_JOIN )
3588 buffer_add( sql_buf, " OR " );
3590 buffer_add( sql_buf, " AND " );
3593 if( '+' == search_itr->key[ 0 ] ) {
3595 // This plus sign prefixes a class name or other table alias;
3596 // make sure the table alias is in scope
3597 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3598 if( ! alias_info ) {
3601 "%s: Invalid table alias \"%s\" in WHERE clause",
3605 jsonIteratorFree( search_itr );
3606 buffer_free( sql_buf );
3610 if( node->type == JSON_STRING ) {
3611 // It's the name of a column; make sure it belongs to the class
3612 const char* fieldname = jsonObjectGetString( node );
3613 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3616 "%s: Invalid column name \"%s\" in WHERE clause "
3617 "for table alias \"%s\"",
3622 jsonIteratorFree( search_itr );
3623 buffer_free( sql_buf );
3627 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3629 // It's something more complicated
3630 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3632 jsonIteratorFree( search_itr );
3633 buffer_free( sql_buf );
3637 buffer_fadd( sql_buf, "( %s )", subpred );
3640 } else if( '-' == search_itr->key[ 0 ] ) {
3641 if( !strcasecmp( "-or", search_itr->key )) {
3642 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3644 jsonIteratorFree( search_itr );
3645 buffer_free( sql_buf );
3649 buffer_fadd( sql_buf, "( %s )", subpred );
3651 } else if( !strcasecmp( "-and", search_itr->key )) {
3652 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3654 jsonIteratorFree( search_itr );
3655 buffer_free( sql_buf );
3659 buffer_fadd( sql_buf, "( %s )", subpred );
3661 } else if( !strcasecmp("-not",search_itr->key) ) {
3662 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3664 jsonIteratorFree( search_itr );
3665 buffer_free( sql_buf );
3669 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3671 } else if( !strcasecmp( "-exists", search_itr->key )) {
3672 char* subpred = buildQuery( ctx, node, SUBSELECT );
3674 jsonIteratorFree( search_itr );
3675 buffer_free( sql_buf );
3679 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3681 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3682 char* subpred = buildQuery( ctx, node, SUBSELECT );
3684 jsonIteratorFree( search_itr );
3685 buffer_free( sql_buf );
3689 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3691 } else { // Invalid "minus" operator
3694 "%s: Invalid operator \"%s\" in WHERE clause",
3698 jsonIteratorFree( search_itr );
3699 buffer_free( sql_buf );
3705 const char* class = class_info->class_name;
3706 osrfHash* fields = class_info->fields;
3707 osrfHash* field = osrfHashGet( fields, search_itr->key );
3710 const char* table = class_info->source_def;
3713 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3716 table ? table : "?",
3719 jsonIteratorFree( search_itr );
3720 buffer_free( sql_buf );
3724 char* subpred = searchPredicate( class_info, field, node, ctx );
3726 buffer_free( sql_buf );
3727 jsonIteratorFree( search_itr );
3731 buffer_add( sql_buf, subpred );
3735 jsonIteratorFree( search_itr );
3738 // ERROR ... only hash and array allowed at this level
3739 char* predicate_string = jsonObjectToJSON( search_hash );
3742 "%s: Invalid predicate structure: %s",
3746 buffer_free( sql_buf );
3747 free( predicate_string );
3751 return buffer_release( sql_buf );
3754 /* Build a JSON_ARRAY of field names for a given table alias
3756 static jsonObject* defaultSelectList( const char* table_alias ) {
3761 ClassInfo* class_info = search_all_alias( table_alias );
3762 if( ! class_info ) {
3765 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3772 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3773 osrfHash* field_def = NULL;
3774 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3775 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3776 const char* field_name = osrfHashIteratorKey( field_itr );
3777 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3778 jsonObjectPush( array, jsonNewObject( field_name ) );
3781 osrfHashIteratorFree( field_itr );
3786 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3787 // The jsonObject must be a JSON_HASH with an single entry for "union",
3788 // "intersect", or "except". The data associated with this key must be an
3789 // array of hashes, each hash being a query.
3790 // Also allowed but currently ignored: entries for "order_by" and "alias".
3791 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3793 if( ! combo || combo->type != JSON_HASH )
3794 return NULL; // should be impossible; validated by caller
3796 const jsonObject* query_array = NULL; // array of subordinate queries
3797 const char* op = NULL; // name of operator, e.g. UNION
3798 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3799 int op_count = 0; // for detecting conflicting operators
3800 int excepting = 0; // boolean
3801 int all = 0; // boolean
3802 jsonObject* order_obj = NULL;
3804 // Identify the elements in the hash
3805 jsonIterator* query_itr = jsonNewIterator( combo );
3806 jsonObject* curr_obj = NULL;
3807 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3808 if( ! strcmp( "union", query_itr->key ) ) {
3811 query_array = curr_obj;
3812 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3815 query_array = curr_obj;
3816 } else if( ! strcmp( "except", query_itr->key ) ) {
3820 query_array = curr_obj;
3821 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3824 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3827 order_obj = curr_obj;
3828 } else if( ! strcmp( "alias", query_itr->key ) ) {
3829 if( curr_obj->type != JSON_STRING ) {
3830 jsonIteratorFree( query_itr );
3833 alias = jsonObjectGetString( curr_obj );
3834 } else if( ! strcmp( "all", query_itr->key ) ) {
3835 if( obj_is_true( curr_obj ) )
3839 osrfAppSessionStatus(
3841 OSRF_STATUS_INTERNALSERVERERROR,
3842 "osrfMethodException",
3844 "Malformed query; unexpected entry in query object"
3848 "%s: Unexpected entry for \"%s\" in%squery",
3853 jsonIteratorFree( query_itr );
3857 jsonIteratorFree( query_itr );
3859 // More sanity checks
3860 if( ! query_array ) {
3862 osrfAppSessionStatus(
3864 OSRF_STATUS_INTERNALSERVERERROR,
3865 "osrfMethodException",
3867 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3871 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3874 return NULL; // should be impossible...
3875 } else if( op_count > 1 ) {
3877 osrfAppSessionStatus(
3879 OSRF_STATUS_INTERNALSERVERERROR,
3880 "osrfMethodException",
3882 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3886 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3890 } if( query_array->type != JSON_ARRAY ) {
3892 osrfAppSessionStatus(
3894 OSRF_STATUS_INTERNALSERVERERROR,
3895 "osrfMethodException",
3897 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3901 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3904 json_type( query_array->type )
3907 } if( query_array->size < 2 ) {
3909 osrfAppSessionStatus(
3911 OSRF_STATUS_INTERNALSERVERERROR,
3912 "osrfMethodException",
3914 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3918 "%s:%srequires multiple queries as operands",
3923 } else if( excepting && query_array->size > 2 ) {
3925 osrfAppSessionStatus(
3927 OSRF_STATUS_INTERNALSERVERERROR,
3928 "osrfMethodException",
3930 "EXCEPT operator has too many queries as operands"
3934 "%s:EXCEPT operator has too many queries as operands",
3938 } else if( order_obj && ! alias ) {
3940 osrfAppSessionStatus(
3942 OSRF_STATUS_INTERNALSERVERERROR,
3943 "osrfMethodException",
3945 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3949 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3955 // So far so good. Now build the SQL.
3956 growing_buffer* sql = buffer_init( 256 );
3958 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3959 // Add a layer of parentheses
3960 if( flags & SUBCOMBO )
3961 OSRF_BUFFER_ADD( sql, "( " );
3963 // Traverse the query array. Each entry should be a hash.
3964 int first = 1; // boolean
3966 jsonObject* query = NULL;
3967 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3968 if( query->type != JSON_HASH ) {
3970 osrfAppSessionStatus(
3972 OSRF_STATUS_INTERNALSERVERERROR,
3973 "osrfMethodException",
3975 "Malformed query under UNION, INTERSECT or EXCEPT"
3979 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3982 json_type( query->type )
3991 OSRF_BUFFER_ADD( sql, op );
3993 OSRF_BUFFER_ADD( sql, "ALL " );
3996 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
4000 "%s: Error building query under%s",
4008 OSRF_BUFFER_ADD( sql, query_str );
4011 if( flags & SUBCOMBO )
4012 OSRF_BUFFER_ADD_CHAR( sql, ')' );
4014 if( !(flags & SUBSELECT) )
4015 OSRF_BUFFER_ADD_CHAR( sql, ';' );
4017 return buffer_release( sql );
4020 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
4021 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
4022 // or "except" to indicate the type of query.
4023 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
4027 osrfAppSessionStatus(
4029 OSRF_STATUS_INTERNALSERVERERROR,
4030 "osrfMethodException",
4032 "Malformed query; no query object"
4034 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
4036 } else if( query->type != JSON_HASH ) {
4038 osrfAppSessionStatus(
4040 OSRF_STATUS_INTERNALSERVERERROR,
4041 "osrfMethodException",
4043 "Malformed query object"
4047 "%s: Query object is %s instead of JSON_HASH",
4049 json_type( query->type )
4054 // Determine what kind of query it purports to be, and dispatch accordingly.
4055 if( jsonObjectGetKeyConst( query, "union" ) ||
4056 jsonObjectGetKeyConst( query, "intersect" ) ||
4057 jsonObjectGetKeyConst( query, "except" )) {
4058 return doCombo( ctx, query, flags );
4060 // It is presumably a SELECT query
4062 // Push a node onto the stack for the current query. Every level of
4063 // subquery gets its own QueryFrame on the Stack.
4066 // Build an SQL SELECT statement
4069 jsonObjectGetKey( query, "select" ),
4070 jsonObjectGetKeyConst( query, "from" ),
4071 jsonObjectGetKeyConst( query, "where" ),
4072 jsonObjectGetKeyConst( query, "having" ),
4073 jsonObjectGetKeyConst( query, "order_by" ),
4074 jsonObjectGetKeyConst( query, "limit" ),
4075 jsonObjectGetKeyConst( query, "offset" ),
4084 /* method context */ osrfMethodContext* ctx,
4086 /* SELECT */ jsonObject* selhash,
4087 /* FROM */ const jsonObject* join_hash,
4088 /* WHERE */ const jsonObject* search_hash,
4089 /* HAVING */ const jsonObject* having_hash,
4090 /* ORDER BY */ const jsonObject* order_hash,
4091 /* LIMIT */ const jsonObject* limit,
4092 /* OFFSET */ const jsonObject* offset,
4093 /* flags */ int flags
4095 const char* locale = osrf_message_get_last_locale();
4097 // general tmp objects
4098 const jsonObject* tmp_const;
4099 jsonObject* selclass = NULL;
4100 jsonObject* snode = NULL;
4101 jsonObject* onode = NULL;
4103 char* string = NULL;
4104 int from_function = 0;
4109 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
4111 // punt if there's no FROM clause
4112 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
4115 "%s: FROM clause is missing or empty",
4119 osrfAppSessionStatus(
4121 OSRF_STATUS_INTERNALSERVERERROR,
4122 "osrfMethodException",
4124 "FROM clause is missing or empty in JSON query"
4129 // the core search class
4130 const char* core_class = NULL;
4132 // get the core class -- the only key of the top level FROM clause, or a string
4133 if( join_hash->type == JSON_HASH ) {
4134 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
4135 snode = jsonIteratorNext( tmp_itr );
4137 // Populate the current QueryFrame with information
4138 // about the core class
4139 if( add_query_core( NULL, tmp_itr->key ) ) {
4141 osrfAppSessionStatus(
4143 OSRF_STATUS_INTERNALSERVERERROR,
4144 "osrfMethodException",
4146 "Unable to look up core class"
4150 core_class = curr_query->core.class_name;
4153 jsonObject* extra = jsonIteratorNext( tmp_itr );
4155 jsonIteratorFree( tmp_itr );
4158 // There shouldn't be more than one entry in join_hash
4162 "%s: Malformed FROM clause: extra entry in JSON_HASH",
4166 osrfAppSessionStatus(
4168 OSRF_STATUS_INTERNALSERVERERROR,
4169 "osrfMethodException",
4171 "Malformed FROM clause in JSON query"
4173 return NULL; // Malformed join_hash; extra entry
4175 } else if( join_hash->type == JSON_ARRAY ) {
4176 // We're selecting from a function, not from a table
4178 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
4181 } else if( join_hash->type == JSON_STRING ) {
4182 // Populate the current QueryFrame with information
4183 // about the core class
4184 core_class = jsonObjectGetString( join_hash );
4186 if( add_query_core( NULL, core_class ) ) {
4188 osrfAppSessionStatus(
4190 OSRF_STATUS_INTERNALSERVERERROR,
4191 "osrfMethodException",
4193 "Unable to look up core class"
4201 "%s: FROM clause is unexpected JSON type: %s",
4203 json_type( join_hash->type )
4206 osrfAppSessionStatus(
4208 OSRF_STATUS_INTERNALSERVERERROR,
4209 "osrfMethodException",
4211 "Ill-formed FROM clause in JSON query"
4216 // Build the join clause, if any, while filling out the list
4217 // of joined classes in the current QueryFrame.
4218 char* join_clause = NULL;
4219 if( join_hash && ! from_function ) {
4221 join_clause = searchJOIN( join_hash, &curr_query->core );
4222 if( ! join_clause ) {
4224 osrfAppSessionStatus(
4226 OSRF_STATUS_INTERNALSERVERERROR,
4227 "osrfMethodException",
4229 "Unable to construct JOIN clause(s)"
4235 // For in case we don't get a select list
4236 jsonObject* defaultselhash = NULL;
4238 // if there is no select list, build a default select list ...
4239 if( !selhash && !from_function ) {
4240 jsonObject* default_list = defaultSelectList( core_class );
4241 if( ! default_list ) {
4243 osrfAppSessionStatus(
4245 OSRF_STATUS_INTERNALSERVERERROR,
4246 "osrfMethodException",
4248 "Unable to build default SELECT clause in JSON query"
4250 free( join_clause );
4255 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4256 jsonObjectSetKey( selhash, core_class, default_list );
4259 // The SELECT clause can be encoded only by a hash
4260 if( !from_function && selhash->type != JSON_HASH ) {
4263 "%s: Expected JSON_HASH for SELECT clause; found %s",
4265 json_type( selhash->type )
4269 osrfAppSessionStatus(
4271 OSRF_STATUS_INTERNALSERVERERROR,
4272 "osrfMethodException",
4274 "Malformed SELECT clause in JSON query"
4276 free( join_clause );
4280 // If you see a null or wild card specifier for the core class, or an
4281 // empty array, replace it with a default SELECT list
4282 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4284 int default_needed = 0; // boolean
4285 if( JSON_STRING == tmp_const->type
4286 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4288 else if( JSON_NULL == tmp_const->type )
4291 if( default_needed ) {
4292 // Build a default SELECT list
4293 jsonObject* default_list = defaultSelectList( core_class );
4294 if( ! default_list ) {
4296 osrfAppSessionStatus(
4298 OSRF_STATUS_INTERNALSERVERERROR,
4299 "osrfMethodException",
4301 "Can't build default SELECT clause in JSON query"
4303 free( join_clause );
4308 jsonObjectSetKey( selhash, core_class, default_list );
4312 // temp buffers for the SELECT list and GROUP BY clause
4313 growing_buffer* select_buf = buffer_init( 128 );
4314 growing_buffer* group_buf = buffer_init( 128 );
4316 int aggregate_found = 0; // boolean
4318 // Build a select list
4319 if( from_function ) // From a function we select everything
4320 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4323 // Build the SELECT list as SQL
4327 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4328 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4330 const char* cname = selclass_itr->key;
4332 // Make sure the target relation is in the FROM clause.
4334 // At this point join_hash is a step down from the join_hash we
4335 // received as a parameter. If the original was a JSON_STRING,
4336 // then json_hash is now NULL. If the original was a JSON_HASH,
4337 // then json_hash is now the first (and only) entry in it,
4338 // denoting the core class. We've already excluded the
4339 // possibility that the original was a JSON_ARRAY, because in
4340 // that case from_function would be non-NULL, and we wouldn't
4343 // If the current table alias isn't in scope, bail out
4344 ClassInfo* class_info = search_alias( cname );
4345 if( ! class_info ) {
4348 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4353 osrfAppSessionStatus(
4355 OSRF_STATUS_INTERNALSERVERERROR,
4356 "osrfMethodException",
4358 "Selected class not in FROM clause in JSON query"
4360 jsonIteratorFree( selclass_itr );
4361 buffer_free( select_buf );
4362 buffer_free( group_buf );
4363 if( defaultselhash )
4364 jsonObjectFree( defaultselhash );
4365 free( join_clause );
4369 if( selclass->type != JSON_ARRAY ) {
4372 "%s: Malformed SELECT list for class \"%s\"; not an array",
4377 osrfAppSessionStatus(
4379 OSRF_STATUS_INTERNALSERVERERROR,
4380 "osrfMethodException",
4382 "Selected class not in FROM clause in JSON query"
4385 jsonIteratorFree( selclass_itr );
4386 buffer_free( select_buf );
4387 buffer_free( group_buf );
4388 if( defaultselhash )
4389 jsonObjectFree( defaultselhash );
4390 free( join_clause );
4394 // Look up some attributes of the current class
4395 osrfHash* idlClass = class_info->class_def;
4396 osrfHash* class_field_set = class_info->fields;
4397 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4398 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4400 if( 0 == selclass->size ) {
4403 "%s: No columns selected from \"%s\"",
4409 // stitch together the column list for the current table alias...
4410 unsigned long field_idx = 0;
4411 jsonObject* selfield = NULL;
4412 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4414 // If we need a separator comma, add one
4418 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4421 // if the field specification is a string, add it to the list
4422 if( selfield->type == JSON_STRING ) {
4424 // Look up the field in the IDL
4425 const char* col_name = jsonObjectGetString( selfield );
4426 osrfHash* field_def = NULL;
4428 if (!osrfStringArrayContains(
4430 osrfHashGet( class_field_set, col_name ),
4431 "suppress_controller"),
4434 field_def = osrfHashGet( class_field_set, col_name );
4437 // No such field in current class
4440 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4446 osrfAppSessionStatus(
4448 OSRF_STATUS_INTERNALSERVERERROR,
4449 "osrfMethodException",
4451 "Selected column not defined in JSON query"
4453 jsonIteratorFree( selclass_itr );
4454 buffer_free( select_buf );
4455 buffer_free( group_buf );
4456 if( defaultselhash )
4457 jsonObjectFree( defaultselhash );
4458 free( join_clause );
4460 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4461 // Virtual field not allowed
4464 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4470 osrfAppSessionStatus(
4472 OSRF_STATUS_INTERNALSERVERERROR,
4473 "osrfMethodException",
4475 "Selected column may not be virtual in JSON query"
4477 jsonIteratorFree( selclass_itr );
4478 buffer_free( select_buf );
4479 buffer_free( group_buf );
4480 if( defaultselhash )
4481 jsonObjectFree( defaultselhash );
4482 free( join_clause );
4488 if( flags & DISABLE_I18N )
4491 i18n = osrfHashGet( field_def, "i18n" );
4493 if( str_is_true( i18n ) ) {
4494 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4495 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4496 class_tname, cname, col_name, class_pkey,
4497 cname, class_pkey, locale, col_name );
4499 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4500 cname, col_name, col_name );
4503 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4504 cname, col_name, col_name );
4507 // ... but it could be an object, in which case we check for a Field Transform
4508 } else if( selfield->type == JSON_HASH ) {
4510 const char* col_name = jsonObjectGetString(
4511 jsonObjectGetKeyConst( selfield, "column" ) );
4513 // Get the field definition from the IDL
4514 osrfHash* field_def = NULL;
4515 if (!osrfStringArrayContains(
4517 osrfHashGet( class_field_set, col_name ),
4518 "suppress_controller"),
4521 field_def = osrfHashGet( class_field_set, col_name );
4525 // No such field in current class
4528 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4534 osrfAppSessionStatus(
4536 OSRF_STATUS_INTERNALSERVERERROR,
4537 "osrfMethodException",
4539 "Selected column is not defined in JSON query"
4541 jsonIteratorFree( selclass_itr );
4542 buffer_free( select_buf );
4543 buffer_free( group_buf );
4544 if( defaultselhash )
4545 jsonObjectFree( defaultselhash );
4546 free( join_clause );
4548 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4549 // No such field in current class
4552 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4558 osrfAppSessionStatus(
4560 OSRF_STATUS_INTERNALSERVERERROR,
4561 "osrfMethodException",
4563 "Selected column is virtual in JSON query"
4565 jsonIteratorFree( selclass_itr );
4566 buffer_free( select_buf );
4567 buffer_free( group_buf );
4568 if( defaultselhash )
4569 jsonObjectFree( defaultselhash );
4570 free( join_clause );
4574 // Decide what to use as a column alias
4576 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4577 _alias = jsonObjectGetString( tmp_const );
4578 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4579 _alias = jsonObjectGetString( tmp_const );
4580 } else { // Use field name as the alias
4584 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4585 char* transform_str = searchFieldTransform(
4586 class_info->alias, field_def, selfield );
4587 if( transform_str ) {
4588 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4589 free( transform_str );
4592 osrfAppSessionStatus(
4594 OSRF_STATUS_INTERNALSERVERERROR,
4595 "osrfMethodException",
4597 "Unable to generate transform function in JSON query"
4599 jsonIteratorFree( selclass_itr );
4600 buffer_free( select_buf );
4601 buffer_free( group_buf );
4602 if( defaultselhash )
4603 jsonObjectFree( defaultselhash );
4604 free( join_clause );
4611 if( flags & DISABLE_I18N )
4614 i18n = osrfHashGet( field_def, "i18n" );
4616 if( str_is_true( i18n ) ) {
4617 buffer_fadd( select_buf,
4618 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4619 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4620 class_tname, cname, col_name, class_pkey, cname,
4621 class_pkey, locale, _alias );
4623 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4624 cname, col_name, _alias );
4627 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4628 cname, col_name, _alias );
4635 "%s: Selected item is unexpected JSON type: %s",
4637 json_type( selfield->type )
4640 osrfAppSessionStatus(
4642 OSRF_STATUS_INTERNALSERVERERROR,
4643 "osrfMethodException",
4645 "Ill-formed SELECT item in JSON query"
4647 jsonIteratorFree( selclass_itr );
4648 buffer_free( select_buf );
4649 buffer_free( group_buf );
4650 if( defaultselhash )
4651 jsonObjectFree( defaultselhash );
4652 free( join_clause );
4656 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4657 if( obj_is_true( agg_obj ) )
4658 aggregate_found = 1;
4660 // Append a comma (except for the first one)
4661 // and add the column to a GROUP BY clause
4665 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4667 buffer_fadd( group_buf, " %d", sel_pos );
4671 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4673 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( selfield, "aggregate");
4674 if ( ! obj_is_true( aggregate_obj ) ) {
4678 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4681 buffer_fadd(group_buf, " %d", sel_pos);
4684 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4688 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4691 _column = searchFieldTransform(class_info->alias, field, selfield);
4692 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4693 OSRF_BUFFER_ADD(group_buf, _column);
4694 _column = searchFieldTransform(class_info->alias, field, selfield);
4701 } // end while -- iterating across SELECT columns
4703 } // end while -- iterating across classes
4705 jsonIteratorFree( selclass_itr );
4708 char* col_list = buffer_release( select_buf );
4710 // Make sure the SELECT list isn't empty. This can happen, for example,
4711 // if we try to build a default SELECT clause from a non-core table.
4714 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4716 osrfAppSessionStatus(
4718 OSRF_STATUS_INTERNALSERVERERROR,
4719 "osrfMethodException",
4721 "SELECT list is empty"
4724 buffer_free( group_buf );
4725 if( defaultselhash )
4726 jsonObjectFree( defaultselhash );
4727 free( join_clause );
4733 table = searchValueTransform( join_hash );
4735 table = strdup( curr_query->core.source_def );
4739 osrfAppSessionStatus(
4741 OSRF_STATUS_INTERNALSERVERERROR,
4742 "osrfMethodException",
4744 "Unable to identify table for core class"
4747 buffer_free( group_buf );
4748 if( defaultselhash )
4749 jsonObjectFree( defaultselhash );
4750 free( join_clause );
4754 // Put it all together
4755 growing_buffer* sql_buf = buffer_init( 128 );
4756 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4760 // Append the join clause, if any
4762 buffer_add(sql_buf, join_clause );
4763 free( join_clause );
4766 char* order_by_list = NULL;
4767 char* having_buf = NULL;
4769 if( !from_function ) {
4771 // Build a WHERE clause, if there is one
4773 buffer_add( sql_buf, " WHERE " );
4775 // and it's on the WHERE clause
4776 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4779 osrfAppSessionStatus(
4781 OSRF_STATUS_INTERNALSERVERERROR,
4782 "osrfMethodException",
4784 "Severe query error in WHERE predicate -- see error log for more details"
4787 buffer_free( group_buf );
4788 buffer_free( sql_buf );
4789 if( defaultselhash )
4790 jsonObjectFree( defaultselhash );
4794 buffer_add( sql_buf, pred );
4798 // Build a HAVING clause, if there is one
4801 // and it's on the the WHERE clause
4802 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4804 if( ! having_buf ) {
4806 osrfAppSessionStatus(
4808 OSRF_STATUS_INTERNALSERVERERROR,
4809 "osrfMethodException",
4811 "Severe query error in HAVING predicate -- see error log for more details"
4814 buffer_free( group_buf );
4815 buffer_free( sql_buf );
4816 if( defaultselhash )
4817 jsonObjectFree( defaultselhash );
4822 // Build an ORDER BY clause, if there is one
4823 if( NULL == order_hash )
4824 ; // No ORDER BY? do nothing
4825 else if( JSON_ARRAY == order_hash->type ) {
4826 order_by_list = buildOrderByFromArray( ctx, order_hash );
4827 if( !order_by_list ) {
4829 buffer_free( group_buf );
4830 buffer_free( sql_buf );
4831 if( defaultselhash )
4832 jsonObjectFree( defaultselhash );
4835 } else if( JSON_HASH == order_hash->type ) {
4836 // This hash is keyed on class alias. Each class has either
4837 // an array of field names or a hash keyed on field name.
4838 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4839 jsonIterator* class_itr = jsonNewIterator( order_hash );
4840 while( (snode = jsonIteratorNext( class_itr )) ) {
4842 ClassInfo* order_class_info = search_alias( class_itr->key );
4843 if( ! order_class_info ) {
4844 osrfLogError( OSRF_LOG_MARK,
4845 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4846 modulename, class_itr->key );
4848 osrfAppSessionStatus(
4850 OSRF_STATUS_INTERNALSERVERERROR,
4851 "osrfMethodException",
4853 "Invalid class referenced in ORDER BY clause -- "
4854 "see error log for more details"
4856 jsonIteratorFree( class_itr );
4857 buffer_free( order_buf );
4859 buffer_free( group_buf );
4860 buffer_free( sql_buf );
4861 if( defaultselhash )
4862 jsonObjectFree( defaultselhash );
4866 osrfHash* field_list_def = order_class_info->fields;
4868 if( snode->type == JSON_HASH ) {
4870 // Hash is keyed on field names from the current class. For each field
4871 // there is another layer of hash to define the sorting details, if any,
4872 // or a string to indicate direction of sorting.
4873 jsonIterator* order_itr = jsonNewIterator( snode );
4874 while( (onode = jsonIteratorNext( order_itr )) ) {
4876 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4878 osrfLogError( OSRF_LOG_MARK,
4879 "%s: Invalid field \"%s\" in ORDER BY clause",
4880 modulename, order_itr->key );
4882 osrfAppSessionStatus(
4884 OSRF_STATUS_INTERNALSERVERERROR,
4885 "osrfMethodException",
4887 "Invalid field in ORDER BY clause -- "
4888 "see error log for more details"
4890 jsonIteratorFree( order_itr );
4891 jsonIteratorFree( class_itr );
4892 buffer_free( order_buf );
4894 buffer_free( group_buf );
4895 buffer_free( sql_buf );
4896 if( defaultselhash )
4897 jsonObjectFree( defaultselhash );
4899 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4900 osrfLogError( OSRF_LOG_MARK,
4901 "%s: Virtual field \"%s\" in ORDER BY clause",
4902 modulename, order_itr->key );
4904 osrfAppSessionStatus(
4906 OSRF_STATUS_INTERNALSERVERERROR,
4907 "osrfMethodException",
4909 "Virtual field in ORDER BY clause -- "
4910 "see error log for more details"
4912 jsonIteratorFree( order_itr );
4913 jsonIteratorFree( class_itr );
4914 buffer_free( order_buf );
4916 buffer_free( group_buf );
4917 buffer_free( sql_buf );
4918 if( defaultselhash )
4919 jsonObjectFree( defaultselhash );
4923 const char* direction = NULL;
4924 if( onode->type == JSON_HASH ) {
4925 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4926 string = searchFieldTransform(
4928 osrfHashGet( field_list_def, order_itr->key ),
4932 if( ctx ) osrfAppSessionStatus(
4934 OSRF_STATUS_INTERNALSERVERERROR,
4935 "osrfMethodException",
4937 "Severe query error in ORDER BY clause -- "
4938 "see error log for more details"
4940 jsonIteratorFree( order_itr );
4941 jsonIteratorFree( class_itr );
4943 buffer_free( group_buf );
4944 buffer_free( order_buf);
4945 buffer_free( sql_buf );
4946 if( defaultselhash )
4947 jsonObjectFree( defaultselhash );
4951 growing_buffer* field_buf = buffer_init( 16 );
4952 buffer_fadd( field_buf, "\"%s\".%s",
4953 class_itr->key, order_itr->key );
4954 string = buffer_release( field_buf );
4957 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4958 const char* dir = jsonObjectGetString( tmp_const );
4959 if(!strncasecmp( dir, "d", 1 )) {
4960 direction = " DESC";
4966 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4967 osrfLogError( OSRF_LOG_MARK,
4968 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4969 modulename, json_type( onode->type ) );
4971 osrfAppSessionStatus(
4973 OSRF_STATUS_INTERNALSERVERERROR,
4974 "osrfMethodException",
4976 "Malformed ORDER BY clause -- see error log for more details"
4978 jsonIteratorFree( order_itr );
4979 jsonIteratorFree( class_itr );
4981 buffer_free( group_buf );
4982 buffer_free( order_buf );
4983 buffer_free( sql_buf );
4984 if( defaultselhash )
4985 jsonObjectFree( defaultselhash );
4989 string = strdup( order_itr->key );
4990 const char* dir = jsonObjectGetString( onode );
4991 if( !strncasecmp( dir, "d", 1 )) {
4992 direction = " DESC";
4999 OSRF_BUFFER_ADD( order_buf, ", " );
5001 order_buf = buffer_init( 128 );
5003 OSRF_BUFFER_ADD( order_buf, string );
5007 OSRF_BUFFER_ADD( order_buf, direction );
5011 jsonIteratorFree( order_itr );
5013 } else if( snode->type == JSON_ARRAY ) {
5015 // Array is a list of fields from the current class
5016 unsigned long order_idx = 0;
5017 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
5019 const char* _f = jsonObjectGetString( onode );
5021 osrfHash* field_def = osrfHashGet( field_list_def, _f );
5023 osrfLogError( OSRF_LOG_MARK,
5024 "%s: Invalid field \"%s\" in ORDER BY clause",
5027 osrfAppSessionStatus(
5029 OSRF_STATUS_INTERNALSERVERERROR,
5030 "osrfMethodException",
5032 "Invalid field in ORDER BY clause -- "
5033 "see error log for more details"
5035 jsonIteratorFree( class_itr );
5036 buffer_free( order_buf );
5038 buffer_free( group_buf );
5039 buffer_free( sql_buf );
5040 if( defaultselhash )
5041 jsonObjectFree( defaultselhash );
5043 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5044 osrfLogError( OSRF_LOG_MARK,
5045 "%s: Virtual field \"%s\" in ORDER BY clause",
5048 osrfAppSessionStatus(
5050 OSRF_STATUS_INTERNALSERVERERROR,
5051 "osrfMethodException",
5053 "Virtual field in ORDER BY clause -- "
5054 "see error log for more details"
5056 jsonIteratorFree( class_itr );
5057 buffer_free( order_buf );
5059 buffer_free( group_buf );
5060 buffer_free( sql_buf );
5061 if( defaultselhash )
5062 jsonObjectFree( defaultselhash );
5067 OSRF_BUFFER_ADD( order_buf, ", " );
5069 order_buf = buffer_init( 128 );
5071 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
5075 // IT'S THE OOOOOOOOOOOLD STYLE!
5077 osrfLogError( OSRF_LOG_MARK,
5078 "%s: Possible SQL injection attempt; direct order by is not allowed",
5081 osrfAppSessionStatus(
5083 OSRF_STATUS_INTERNALSERVERERROR,
5084 "osrfMethodException",
5086 "Severe query error -- see error log for more details"
5091 buffer_free( group_buf );
5092 buffer_free( order_buf );
5093 buffer_free( sql_buf );
5094 if( defaultselhash )
5095 jsonObjectFree( defaultselhash );
5096 jsonIteratorFree( class_itr );
5100 jsonIteratorFree( class_itr );
5102 order_by_list = buffer_release( order_buf );
5104 osrfLogError( OSRF_LOG_MARK,
5105 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
5106 modulename, json_type( order_hash->type ) );
5108 osrfAppSessionStatus(
5110 OSRF_STATUS_INTERNALSERVERERROR,
5111 "osrfMethodException",
5113 "Malformed ORDER BY clause -- see error log for more details"
5116 buffer_free( group_buf );
5117 buffer_free( sql_buf );
5118 if( defaultselhash )
5119 jsonObjectFree( defaultselhash );
5124 string = buffer_release( group_buf );
5126 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
5127 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
5128 OSRF_BUFFER_ADD( sql_buf, string );
5133 if( having_buf && *having_buf ) {
5134 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
5135 OSRF_BUFFER_ADD( sql_buf, having_buf );
5139 if( order_by_list ) {
5141 if( *order_by_list ) {
5142 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5143 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5146 free( order_by_list );
5150 const char* str = jsonObjectGetString( limit );
5151 if (str) { // limit could be JSON_NULL, etc.
5152 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
5157 const char* str = jsonObjectGetString( offset );
5159 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
5163 if( !(flags & SUBSELECT) )
5164 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5166 if( defaultselhash )
5167 jsonObjectFree( defaultselhash );
5169 return buffer_release( sql_buf );
5171 } // end of SELECT()
5174 @brief Build a list of ORDER BY expressions.
5175 @param ctx Pointer to the method context.
5176 @param order_array Pointer to a JSON_ARRAY of field specifications.
5177 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
5178 Each expression may be either a column reference or a function call whose first parameter
5179 is a column reference.
5181 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
5182 It may optionally include entries for "direction" and/or "transform".
5184 The calling code is responsible for freeing the returned string.
5186 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
5187 if( ! order_array ) {
5188 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
5191 osrfAppSessionStatus(
5193 OSRF_STATUS_INTERNALSERVERERROR,
5194 "osrfMethodException",
5196 "Logic error: ORDER BY clause expected, not found; "
5197 "see error log for more details"
5200 } else if( order_array->type != JSON_ARRAY ) {
5201 osrfLogError( OSRF_LOG_MARK,
5202 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
5204 osrfAppSessionStatus(
5206 OSRF_STATUS_INTERNALSERVERERROR,
5207 "osrfMethodException",
5209 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
5213 growing_buffer* order_buf = buffer_init( 128 );
5214 int first = 1; // boolean
5216 jsonObject* order_spec;
5217 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
5219 if( JSON_HASH != order_spec->type ) {
5220 osrfLogError( OSRF_LOG_MARK,
5221 "%s: Malformed field specification in ORDER BY clause; "
5222 "expected JSON_HASH, found %s",
5223 modulename, json_type( order_spec->type ) );
5225 osrfAppSessionStatus(
5227 OSRF_STATUS_INTERNALSERVERERROR,
5228 "osrfMethodException",
5230 "Malformed ORDER BY clause -- see error log for more details"
5232 buffer_free( order_buf );
5236 const char* class_alias =
5237 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5239 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5241 jsonObject* compare_to = jsonObjectGetKey( order_spec, "compare" );
5243 if( !field || !class_alias ) {
5244 osrfLogError( OSRF_LOG_MARK,
5245 "%s: Missing class or field name in field specification of ORDER BY clause",
5248 osrfAppSessionStatus(
5250 OSRF_STATUS_INTERNALSERVERERROR,
5251 "osrfMethodException",
5253 "Malformed ORDER BY clause -- see error log for more details"
5255 buffer_free( order_buf );
5259 const ClassInfo* order_class_info = search_alias( class_alias );
5260 if( ! order_class_info ) {
5261 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5262 "not in FROM clause, skipping it", modulename, class_alias );
5266 // Add a separating comma, except at the beginning
5270 OSRF_BUFFER_ADD( order_buf, ", " );
5272 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5274 osrfLogError( OSRF_LOG_MARK,
5275 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5276 modulename, class_alias, field );
5278 osrfAppSessionStatus(
5280 OSRF_STATUS_INTERNALSERVERERROR,
5281 "osrfMethodException",
5283 "Invalid field referenced in ORDER BY clause -- "
5284 "see error log for more details"
5288 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5289 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5290 modulename, field );
5292 osrfAppSessionStatus(
5294 OSRF_STATUS_INTERNALSERVERERROR,
5295 "osrfMethodException",
5297 "Virtual field in ORDER BY clause -- see error log for more details"
5299 buffer_free( order_buf );
5303 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5304 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5305 if( ! transform_str ) {
5307 osrfAppSessionStatus(
5309 OSRF_STATUS_INTERNALSERVERERROR,
5310 "osrfMethodException",
5312 "Severe query error in ORDER BY clause -- "
5313 "see error log for more details"
5315 buffer_free( order_buf );
5319 OSRF_BUFFER_ADD( order_buf, transform_str );
5320 free( transform_str );
5321 } else if( compare_to ) {
5322 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5323 if( ! compare_str ) {
5325 osrfAppSessionStatus(
5327 OSRF_STATUS_INTERNALSERVERERROR,
5328 "osrfMethodException",
5330 "Severe query error in ORDER BY clause -- "
5331 "see error log for more details"
5333 buffer_free( order_buf );
5337 buffer_fadd( order_buf, "(%s)", compare_str );
5338 free( compare_str );
5341 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5343 const char* direction =
5344 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5346 if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5347 OSRF_BUFFER_ADD( order_buf, " DESC" );
5349 OSRF_BUFFER_ADD( order_buf, " ASC" );
5353 return buffer_release( order_buf );
5357 @brief Build a SELECT statement.
5358 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5359 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5360 @param meta Pointer to the class metadata for the core class.
5361 @param ctx Pointer to the method context.
5362 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5364 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5365 "order_by", "limit", and "offset".
5367 The SELECT statements built here are distinct from those built for the json_query method.
5369 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5370 osrfHash* meta, osrfMethodContext* ctx ) {
5372 const char* locale = osrf_message_get_last_locale();
5374 osrfHash* fields = osrfHashGet( meta, "fields" );
5375 const char* core_class = osrfHashGet( meta, "classname" );
5377 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5379 jsonObject* selhash = NULL;
5380 jsonObject* defaultselhash = NULL;
5382 growing_buffer* sql_buf = buffer_init( 128 );
5383 growing_buffer* select_buf = buffer_init( 128 );
5385 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5386 defaultselhash = jsonNewObjectType( JSON_HASH );
5387 selhash = defaultselhash;
5390 // If there's no SELECT list for the core class, build one
5391 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5392 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5394 // Add every non-virtual field to the field list
5395 osrfHash* field_def = NULL;
5396 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5397 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5398 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5399 const char* field = osrfHashIteratorKey( field_itr );
5400 jsonObjectPush( field_list, jsonNewObject( field ) );
5403 osrfHashIteratorFree( field_itr );
5404 jsonObjectSetKey( selhash, core_class, field_list );
5407 // Build a list of columns for the SELECT clause
5409 const jsonObject* snode = NULL;
5410 jsonIterator* class_itr = jsonNewIterator( selhash );
5411 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5413 // If the class isn't in the IDL, ignore it
5414 const char* cname = class_itr->key;
5415 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5419 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5420 if( strcmp( core_class, class_itr->key )) {
5424 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5425 if( !found->size ) {
5426 jsonObjectFree( found );
5430 jsonObjectFree( found );
5433 const jsonObject* node = NULL;
5434 jsonIterator* select_itr = jsonNewIterator( snode );
5435 while( (node = jsonIteratorNext( select_itr )) ) {
5436 const char* item_str = jsonObjectGetString( node );
5437 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5438 char* fname = osrfHashGet( field, "name" );
5443 if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5449 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5454 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5455 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5458 i18n = osrfHashGet( field, "i18n" );
5460 if( str_is_true( i18n ) ) {
5461 char* pkey = osrfHashGet( idlClass, "primarykey" );
5462 char* tname = osrfHashGet( idlClass, "tablename" );
5464 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5465 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5466 tname, cname, fname, pkey, cname, pkey, locale, fname );
5468 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5471 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5475 jsonIteratorFree( select_itr );
5478 jsonIteratorFree( class_itr );
5480 char* col_list = buffer_release( select_buf );
5481 char* table = oilsGetRelation( meta );
5483 table = strdup( "(null)" );
5485 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5489 // Clear the query stack (as a fail-safe precaution against possible
5490 // leftover garbage); then push the first query frame onto the stack.
5491 clear_query_stack();
5493 if( add_query_core( NULL, core_class ) ) {
5495 osrfAppSessionStatus(
5497 OSRF_STATUS_INTERNALSERVERERROR,
5498 "osrfMethodException",
5500 "Unable to build query frame for core class"
5502 buffer_free( sql_buf );
5503 if( defaultselhash )
5504 jsonObjectFree( defaultselhash );
5508 // Add the JOIN clauses, if any
5510 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5511 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5512 OSRF_BUFFER_ADD( sql_buf, join_clause );
5513 free( join_clause );
5516 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5517 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5519 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5521 // Add the conditions in the WHERE clause
5522 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5524 osrfAppSessionStatus(
5526 OSRF_STATUS_INTERNALSERVERERROR,
5527 "osrfMethodException",
5529 "Severe query error -- see error log for more details"
5531 buffer_free( sql_buf );
5532 if( defaultselhash )
5533 jsonObjectFree( defaultselhash );
5534 clear_query_stack();
5537 buffer_add( sql_buf, pred );
5541 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5542 if( rest_of_query ) {
5543 const jsonObject* order_by = NULL;
5544 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5546 char* order_by_list = NULL;
5548 if( JSON_ARRAY == order_by->type ) {
5549 order_by_list = buildOrderByFromArray( ctx, order_by );
5550 if( !order_by_list ) {
5551 buffer_free( sql_buf );
5552 if( defaultselhash )
5553 jsonObjectFree( defaultselhash );
5554 clear_query_stack();
5557 } else if( JSON_HASH == order_by->type ) {
5558 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5559 // and build a list of ORDER BY expressions.
5560 growing_buffer* order_buf = buffer_init( 128 );
5562 jsonIterator* class_itr = jsonNewIterator( order_by );
5563 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5565 ClassInfo* order_class_info = search_alias( class_itr->key );
5566 if( ! order_class_info )
5567 continue; // class not referenced by FROM clause? Ignore it.
5569 if( JSON_HASH == snode->type ) {
5571 // If the data for the current class is a JSON_HASH, then it is
5572 // keyed on field name.
5574 const jsonObject* onode = NULL;
5575 jsonIterator* order_itr = jsonNewIterator( snode );
5576 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5578 osrfHash* field_def = osrfHashGet(
5579 order_class_info->fields, order_itr->key );
5581 continue; // Field not defined in IDL? Ignore it.
5582 if( str_is_true( osrfHashGet( field_def, "virtual")))
5583 continue; // Field is virtual? Ignore it.
5585 char* field_str = NULL;
5586 char* direction = NULL;
5587 if( onode->type == JSON_HASH ) {
5588 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5589 field_str = searchFieldTransform(
5590 class_itr->key, field_def, onode );
5592 osrfAppSessionStatus(
5594 OSRF_STATUS_INTERNALSERVERERROR,
5595 "osrfMethodException",
5597 "Severe query error in ORDER BY clause -- "
5598 "see error log for more details"
5600 jsonIteratorFree( order_itr );
5601 jsonIteratorFree( class_itr );
5602 buffer_free( order_buf );
5603 buffer_free( sql_buf );
5604 if( defaultselhash )
5605 jsonObjectFree( defaultselhash );
5606 clear_query_stack();
5610 growing_buffer* field_buf = buffer_init( 16 );
5611 buffer_fadd( field_buf, "\"%s\".%s",
5612 class_itr->key, order_itr->key );
5613 field_str = buffer_release( field_buf );
5616 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5617 const char* dir = jsonObjectGetString( order_by );
5618 if(!strncasecmp( dir, "d", 1 )) {
5619 direction = " DESC";
5623 field_str = strdup( order_itr->key );
5624 const char* dir = jsonObjectGetString( onode );
5625 if( !strncasecmp( dir, "d", 1 )) {
5626 direction = " DESC";
5635 buffer_add( order_buf, ", " );
5638 buffer_add( order_buf, field_str );
5642 buffer_add( order_buf, direction );
5644 } // end while; looping over ORDER BY expressions
5646 jsonIteratorFree( order_itr );
5648 } else if( JSON_STRING == snode->type ) {
5649 // We expect a comma-separated list of sort fields.
5650 const char* str = jsonObjectGetString( snode );
5651 if( strchr( str, ';' )) {
5652 // No semicolons allowed. It is theoretically possible for a
5653 // legitimate semicolon to occur within quotes, but it's not likely
5654 // to occur in practice in the context of an ORDER BY list.
5655 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5656 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5658 osrfAppSessionStatus(
5660 OSRF_STATUS_INTERNALSERVERERROR,
5661 "osrfMethodException",
5663 "Possible attempt at SOL injection -- "
5664 "semicolon found in ORDER BY list"
5667 jsonIteratorFree( class_itr );
5668 buffer_free( order_buf );
5669 buffer_free( sql_buf );
5670 if( defaultselhash )
5671 jsonObjectFree( defaultselhash );
5672 clear_query_stack();
5675 buffer_add( order_buf, str );
5679 } // end while; looping over order_by classes
5681 jsonIteratorFree( class_itr );
5682 order_by_list = buffer_release( order_buf );
5685 osrfLogWarning( OSRF_LOG_MARK,
5686 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5687 "no ORDER BY generated" );
5690 if( order_by_list && *order_by_list ) {
5691 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5692 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5695 free( order_by_list );
5698 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5700 const char* str = jsonObjectGetString( limit );
5710 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5712 const char* str = jsonObjectGetString( offset );
5723 if( defaultselhash )
5724 jsonObjectFree( defaultselhash );
5725 clear_query_stack();
5727 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5728 return buffer_release( sql_buf );
5731 int doJSONSearch ( osrfMethodContext* ctx ) {
5732 if(osrfMethodVerifyContext( ctx )) {
5733 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5737 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5741 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5745 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5746 flags |= SELECT_DISTINCT;
5748 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5749 flags |= DISABLE_I18N;
5751 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5752 clear_query_stack(); // a possibly needless precaution
5753 char* sql = buildQuery( ctx, hash, flags );
5754 clear_query_stack();
5761 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5764 dbhandle = writehandle;
5766 dbi_result result = dbi_conn_query( dbhandle, sql );
5769 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5771 if( dbi_result_first_row( result )) {
5772 /* JSONify the result */
5773 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5776 jsonObject* return_val = oilsMakeJSONFromResult( result );
5777 osrfAppRespond( ctx, return_val );
5778 jsonObjectFree( return_val );
5779 } while( dbi_result_next_row( result ));
5782 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5785 osrfAppRespondComplete( ctx, NULL );
5787 /* clean up the query */
5788 dbi_result_free( result );
5793 int errnum = dbi_conn_error( dbhandle, &msg );
5794 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5795 modulename, sql, errnum, msg ? msg : "(No description available)" );
5796 osrfAppSessionStatus(
5798 OSRF_STATUS_INTERNALSERVERERROR,
5799 "osrfMethodException",
5801 "Severe query error -- see error log for more details"
5803 if( !oilsIsDBConnected( dbhandle ))
5804 osrfAppSessionPanic( ctx->session );
5811 // The last parameter, err, is used to report an error condition by updating an int owned by
5812 // the calling code.
5814 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5815 // It is the responsibility of the calling code to initialize *err before the
5816 // call, so that it will be able to make sense of the result.
5818 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5819 // redundant anyway.
5820 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5821 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5824 dbhandle = writehandle;
5826 char* core_class = osrfHashGet( class_meta, "classname" );
5827 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5829 char* pkey = osrfHashGet( class_meta, "primarykey" );
5831 if (!ctx->session->userData)
5832 (void) initSessionCache( ctx );
5834 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5835 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5836 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5838 int i_respond_directly = 0;
5839 int flesh_depth = 0;
5841 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5843 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5848 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5850 dbi_result result = dbi_conn_query( dbhandle, sql );
5851 if( NULL == result ) {
5853 int errnum = dbi_conn_error( dbhandle, &msg );
5854 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5855 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5856 msg ? msg : "(No description available)" );
5857 if( !oilsIsDBConnected( dbhandle ))
5858 osrfAppSessionPanic( ctx->session );
5859 osrfAppSessionStatus(
5861 OSRF_STATUS_INTERNALSERVERERROR,
5862 "osrfMethodException",
5864 "Severe query error -- see error log for more details"
5871 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5874 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5875 jsonObject* row_obj = NULL;
5877 // The following two steps are for verifyObjectPCRUD()'s benefit.
5878 // 1. get the flesh depth
5879 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5881 flesh_depth = (int) jsonObjectGetNumber( _tmp );
5882 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5883 flesh_depth = max_flesh_depth;
5886 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
5887 // over the whole life of this request. This means if we've already set
5888 // up a rs_size_req_%d, do nothing.
5889 // a. Incidentally, we can also use this opportunity to set i_respond_directly
5890 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
5891 if( !rs_size ) { // pointer null, so value not set in hash
5892 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
5893 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
5895 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
5896 unsigned long long result_count = dbi_result_get_numrows( result );
5897 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
5898 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
5901 if( dbi_result_first_row( result )) {
5903 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5904 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5905 // eliminate the duplicates.
5906 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5907 osrfHash* dedup = osrfNewHash();
5909 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5910 char* pkey_val = oilsFMGetString( row_obj, pkey );
5911 if( osrfHashGet( dedup, pkey_val ) ) {
5912 jsonObjectFree( row_obj );
5915 if( !enforce_pcrud || !need_to_verify ||
5916 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
5917 osrfHashSet( dedup, pkey_val, pkey_val );
5918 jsonObjectPush( res_list, row_obj );
5921 } while( dbi_result_next_row( result ));
5922 osrfHashFree( dedup );
5925 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5929 /* clean up the query */
5930 dbi_result_free( result );
5933 // If we're asked to flesh, and there's anything to flesh, then flesh it
5934 // (formerly we would skip fleshing if in pcrud mode, but now we support
5935 // fleshing even in PCRUD).
5936 if( res_list->size ) {
5937 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
5938 jsonObject* flesh_fields;
5939 jsonObject* flesh_blob = NULL;
5940 osrfStringArray* link_fields = NULL;
5941 osrfHash* links = NULL;
5945 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
5946 if( temp_blob && flesh_depth > 0 ) {
5948 flesh_blob = jsonObjectClone( temp_blob );
5949 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
5951 links = osrfHashGet( class_meta, "links" );
5953 // Make an osrfStringArray of the names of fields to be fleshed
5954 if( flesh_fields ) {
5955 if( flesh_fields->size == 1 ) {
5956 const char* _t = jsonObjectGetString(
5957 jsonObjectGetIndex( flesh_fields, 0 ) );
5958 if( !strcmp( _t, "*" ))
5959 link_fields = osrfHashKeys( links );
5962 if( !link_fields ) {
5964 link_fields = osrfNewStringArray( 1 );
5965 jsonIterator* _i = jsonNewIterator( flesh_fields );
5966 while ((_f = jsonIteratorNext( _i ))) {
5967 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5969 jsonIteratorFree( _i );
5972 want_flesh = link_fields ? 1 : 0;
5976 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5978 // Iterate over the JSON_ARRAY of rows
5980 unsigned long res_idx = 0;
5981 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5984 const char* link_field;
5986 // Iterate over the list of fleshable fields
5988 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5990 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5992 osrfHash* kid_link = osrfHashGet( links, link_field );
5994 continue; // Not a link field; skip it
5996 osrfHash* field = osrfHashGet( fields, link_field );
5998 continue; // Not a field at all; skip it (IDL is ill-formed)
6000 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
6001 osrfHashGet( kid_link, "class" ));
6003 continue; // The class it links to doesn't exist; skip it
6005 const char* reltype = osrfHashGet( kid_link, "reltype" );
6007 continue; // No reltype; skip it (IDL is ill-formed)
6009 osrfHash* value_field = field;
6011 if( !strcmp( reltype, "has_many" )
6012 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
6013 value_field = osrfHashGet(
6014 fields, osrfHashGet( class_meta, "primarykey" ) );
6017 int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
6018 // fleshing pcrud case: we require the controller in need_to_verify mode
6019 if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
6020 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
6024 (unsigned long) atoi( osrfHashGet(field, "array_position") ),
6026 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
6032 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
6034 if( link_map->size > 0 ) {
6035 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
6038 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
6043 osrfHashGet( kid_link, "class" ),
6050 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
6051 osrfHashGet( kid_link, "field" ),
6052 osrfHashGet( kid_link, "class" ),
6053 osrfHashGet( kid_link, "key" ),
6054 osrfHashGet( kid_link, "reltype" )
6057 const char* search_key = jsonObjectGetString(
6058 jsonObjectGetIndex( cur,
6059 atoi( osrfHashGet( value_field, "array_position" ) )
6064 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
6068 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
6070 // construct WHERE clause
6071 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
6074 osrfHashGet( kid_link, "key" ),
6075 jsonNewObject( search_key )
6078 // construct the rest of the query, mostly
6079 // by copying pieces of the previous level of query
6080 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
6081 jsonObjectSetKey( rest_of_query, "flesh",
6082 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
6086 jsonObjectSetKey( rest_of_query, "flesh_fields",
6087 jsonObjectClone( flesh_blob ));
6089 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
6090 jsonObjectSetKey( rest_of_query, "order_by",
6091 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
6095 if( jsonObjectGetKeyConst( query_hash, "select" )) {
6096 jsonObjectSetKey( rest_of_query, "select",
6097 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
6101 // do the query, recursively, to expand the fleshable field
6102 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
6103 where_clause, rest_of_query, err );
6105 jsonObjectFree( where_clause );
6106 jsonObjectFree( rest_of_query );
6109 osrfStringArrayFree( link_fields );
6110 jsonObjectFree( res_list );
6111 jsonObjectFree( flesh_blob );
6115 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
6116 osrfHashGet( kid_link, "class" ), kids->size );
6118 // Traverse the result set
6119 jsonObject* X = NULL;
6120 if( link_map->size > 0 && kids->size > 0 ) {
6122 kids = jsonNewObjectType( JSON_ARRAY );
6124 jsonObject* _k_node;
6125 unsigned long res_idx = 0;
6126 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
6132 (unsigned long) atoi(
6138 osrfHashGet( kid_link, "class" )
6142 osrfStringArrayGetString( link_map, 0 )
6150 } // end while loop traversing X
6153 if (kids->size > 0) {
6155 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
6156 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
6158 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
6159 osrfHashGet( kid_link, "field" ));
6162 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6163 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
6168 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
6170 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
6171 osrfHashGet( kid_link, "field" ) );
6174 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6175 jsonObjectClone( kids )
6180 jsonObjectFree( kids );
6184 jsonObjectFree( kids );
6186 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
6187 osrfHashGet( kid_link, "field" ) );
6188 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
6190 } // end while loop traversing list of fleshable fields
6193 if( i_respond_directly ) {
6194 if ( *methodtype == 'i' ) {
6195 osrfAppRespond( ctx,
6196 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
6198 osrfAppRespond( ctx, cur );
6201 } // end while loop traversing res_list
6202 jsonObjectFree( flesh_blob );
6203 osrfStringArrayFree( link_fields );
6206 if( i_respond_directly ) {
6207 jsonObjectFree( res_list );
6208 return jsonNewObjectType( JSON_ARRAY );
6215 int doUpdate( osrfMethodContext* ctx ) {
6216 if( osrfMethodVerifyContext( ctx )) {
6217 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6222 timeout_needs_resetting = 1;
6224 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6226 jsonObject* target = NULL;
6228 target = jsonObjectGetIndex( ctx->params, 1 );
6230 target = jsonObjectGetIndex( ctx->params, 0 );
6232 if(!verifyObjectClass( ctx, target )) {
6233 osrfAppRespondComplete( ctx, NULL );
6237 if( getXactId( ctx ) == NULL ) {
6238 osrfAppSessionStatus(
6240 OSRF_STATUS_BADREQUEST,
6241 "osrfMethodException",
6243 "No active transaction -- required for UPDATE"
6245 osrfAppRespondComplete( ctx, NULL );
6249 // The following test is harmless but redundant. If a class is
6250 // readonly, we don't register an update method for it.
6251 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6252 osrfAppSessionStatus(
6254 OSRF_STATUS_BADREQUEST,
6255 "osrfMethodException",
6257 "Cannot UPDATE readonly class"
6259 osrfAppRespondComplete( ctx, NULL );
6263 const char* trans_id = getXactId( ctx );
6265 // Set the last_xact_id
6266 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6268 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6269 trans_id, target->classname, index );
6270 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6273 char* pkey = osrfHashGet( meta, "primarykey" );
6274 osrfHash* fields = osrfHashGet( meta, "fields" );
6276 char* id = oilsFMGetString( target, pkey );
6280 "%s updating %s object with %s = %s",
6282 osrfHashGet( meta, "fieldmapper" ),
6287 dbhandle = writehandle;
6288 growing_buffer* sql = buffer_init( 128 );
6289 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6292 osrfHash* field_def = NULL;
6293 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6294 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6296 // Skip virtual fields, and the primary key
6297 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6300 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6304 const char* field_name = osrfHashIteratorKey( field_itr );
6305 if( ! strcmp( field_name, pkey ) )
6308 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6310 int value_is_numeric = 0; // boolean
6312 if( field_object && field_object->classname ) {
6313 value = oilsFMGetString(
6315 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6317 } else if( field_object && JSON_BOOL == field_object->type ) {
6318 if( jsonBoolIsTrue( field_object ) )
6319 value = strdup( "t" );
6321 value = strdup( "f" );
6323 value = jsonObjectToSimpleString( field_object );
6324 if( field_object && JSON_NUMBER == field_object->type )
6325 value_is_numeric = 1;
6328 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6329 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6331 if( !field_object || field_object->type == JSON_NULL ) {
6332 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6333 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6337 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6338 buffer_fadd( sql, " %s = NULL", field_name );
6341 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6345 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6347 const char* numtype = get_datatype( field_def );
6348 if( !strncmp( numtype, "INT", 3 ) ) {
6349 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6350 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6351 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6353 // Must really be intended as a string, so quote it
6354 if( dbi_conn_quote_string( dbhandle, &value )) {
6355 buffer_fadd( sql, " %s = %s", field_name, value );
6357 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6358 modulename, value );
6359 osrfAppSessionStatus(
6361 OSRF_STATUS_INTERNALSERVERERROR,
6362 "osrfMethodException",
6364 "Error quoting string -- please see the error log for more details"
6368 osrfHashIteratorFree( field_itr );
6370 osrfAppRespondComplete( ctx, NULL );
6375 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6378 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6382 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6383 buffer_fadd( sql, " %s = %s", field_name, value );
6385 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6386 osrfAppSessionStatus(
6388 OSRF_STATUS_INTERNALSERVERERROR,
6389 "osrfMethodException",
6391 "Error quoting string -- please see the error log for more details"
6395 osrfHashIteratorFree( field_itr );
6397 osrfAppRespondComplete( ctx, NULL );
6406 osrfHashIteratorFree( field_itr );
6408 jsonObject* obj = jsonNewObject( id );
6410 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6411 dbi_conn_quote_string( dbhandle, &id );
6413 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6415 char* query = buffer_release( sql );
6416 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6418 dbi_result result = dbi_conn_query( dbhandle, query );
6423 jsonObjectFree( obj );
6424 obj = jsonNewObject( NULL );
6426 int errnum = dbi_conn_error( dbhandle, &msg );
6429 "%s ERROR updating %s object with %s = %s: %d %s",
6431 osrfHashGet( meta, "fieldmapper" ),
6435 msg ? msg : "(No description available)"
6437 osrfAppSessionStatus(
6439 OSRF_STATUS_INTERNALSERVERERROR,
6440 "osrfMethodException",
6442 "Error in updating a row -- please see the error log for more details"
6444 if( !oilsIsDBConnected( dbhandle ))
6445 osrfAppSessionPanic( ctx->session );
6448 dbi_result_free( result );
6451 osrfAppRespondComplete( ctx, obj );
6452 jsonObjectFree( obj );
6456 int doDelete( osrfMethodContext* ctx ) {
6457 if( osrfMethodVerifyContext( ctx )) {
6458 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6463 timeout_needs_resetting = 1;
6465 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6467 if( getXactId( ctx ) == NULL ) {
6468 osrfAppSessionStatus(
6470 OSRF_STATUS_BADREQUEST,
6471 "osrfMethodException",
6473 "No active transaction -- required for DELETE"
6475 osrfAppRespondComplete( ctx, NULL );
6479 // The following test is harmless but redundant. If a class is
6480 // readonly, we don't register a delete method for it.
6481 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6482 osrfAppSessionStatus(
6484 OSRF_STATUS_BADREQUEST,
6485 "osrfMethodException",
6487 "Cannot DELETE readonly class"
6489 osrfAppRespondComplete( ctx, NULL );
6493 dbhandle = writehandle;
6495 char* pkey = osrfHashGet( meta, "primarykey" );
6502 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6503 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6504 osrfAppRespondComplete( ctx, NULL );
6508 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6510 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6511 osrfAppRespondComplete( ctx, NULL );
6514 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6519 "%s deleting %s object with %s = %s",
6521 osrfHashGet( meta, "fieldmapper" ),
6526 jsonObject* obj = jsonNewObject( id );
6528 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6529 dbi_conn_quote_string( writehandle, &id );
6531 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6532 osrfHashGet( meta, "tablename" ), pkey, id );
6537 jsonObjectFree( obj );
6538 obj = jsonNewObject( NULL );
6540 int errnum = dbi_conn_error( writehandle, &msg );
6543 "%s ERROR deleting %s object with %s = %s: %d %s",
6545 osrfHashGet( meta, "fieldmapper" ),
6549 msg ? msg : "(No description available)"
6551 osrfAppSessionStatus(
6553 OSRF_STATUS_INTERNALSERVERERROR,
6554 "osrfMethodException",
6556 "Error in deleting a row -- please see the error log for more details"
6558 if( !oilsIsDBConnected( writehandle ))
6559 osrfAppSessionPanic( ctx->session );
6561 dbi_result_free( result );
6565 osrfAppRespondComplete( ctx, obj );
6566 jsonObjectFree( obj );
6571 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6572 @param result An iterator for a result set; we only look at the current row.
6573 @param @meta Pointer to the class metadata for the core class.
6574 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6576 If a column is not defined in the IDL, or if it has no array_position defined for it in
6577 the IDL, or if it is defined as virtual, ignore it.
6579 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6580 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6581 array_position in the IDL.
6583 A field defined in the IDL but not represented in the returned row will leave a hole
6584 in the JSON_ARRAY. In effect it will be treated as a null value.
6586 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6587 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6588 classname corresponding to the @a meta argument.
6590 The calling code is responsible for freeing the the resulting jsonObject by calling
6593 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6594 if( !( result && meta )) return NULL;
6596 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6597 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6598 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6600 osrfHash* fields = osrfHashGet( meta, "fields" );
6602 int columnIndex = 1;
6603 const char* columnName;
6605 /* cycle through the columns in the row returned from the database */
6606 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6608 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6610 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6612 /* determine the field type and storage attributes */
6613 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6614 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6616 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6617 // or if it has no sequence number there, or if it's virtual, skip it.
6618 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6621 if( str_is_true( osrfHashGet( _f, "virtual" )))
6622 continue; // skip this column: IDL says it's virtual
6624 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6625 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6626 continue; // since we assign sequence numbers dynamically as we load the IDL.
6628 fmIndex = atoi( pos );
6629 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6631 continue; // This field is not defined in the IDL
6634 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6635 // sequence number from the IDL (which is likely to be different from the sequence
6636 // of columns in the SELECT clause).
6637 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6638 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6643 case DBI_TYPE_INTEGER :
6645 if( attr & DBI_INTEGER_SIZE8 )
6646 jsonObjectSetIndex( object, fmIndex,
6647 jsonNewNumberObject(
6648 dbi_result_get_longlong_idx( result, columnIndex )));
6650 jsonObjectSetIndex( object, fmIndex,
6651 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6655 case DBI_TYPE_DECIMAL :
6656 jsonObjectSetIndex( object, fmIndex,
6657 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6660 case DBI_TYPE_STRING :
6665 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6670 case DBI_TYPE_DATETIME : {
6672 char dt_string[ 256 ] = "";
6675 // Fetch the date column as a time_t
6676 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6678 // Translate the time_t to a human-readable string
6679 if( !( attr & DBI_DATETIME_DATE )) {
6680 gmtime_r( &_tmp_dt, &gmdt );
6681 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6682 } else if( !( attr & DBI_DATETIME_TIME )) {
6683 localtime_r( &_tmp_dt, &gmdt );
6684 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6686 localtime_r( &_tmp_dt, &gmdt );
6687 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6690 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6694 case DBI_TYPE_BINARY :
6695 osrfLogError( OSRF_LOG_MARK,
6696 "Can't do binary at column %s : index %d", columnName, columnIndex );
6705 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6706 if( !result ) return NULL;
6708 jsonObject* object = jsonNewObject( NULL );
6711 char dt_string[ 256 ];
6715 int columnIndex = 1;
6717 unsigned short type;
6718 const char* columnName;
6720 /* cycle through the column list */
6721 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6723 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6725 fmIndex = -1; // reset the position
6727 /* determine the field type and storage attributes */
6728 type = dbi_result_get_field_type_idx( result, columnIndex );
6729 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6731 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6732 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6737 case DBI_TYPE_INTEGER :
6739 if( attr & DBI_INTEGER_SIZE8 )
6740 jsonObjectSetKey( object, columnName,
6741 jsonNewNumberObject( dbi_result_get_longlong_idx(
6742 result, columnIndex )) );
6744 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6745 dbi_result_get_int_idx( result, columnIndex )) );
6748 case DBI_TYPE_DECIMAL :
6749 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6750 dbi_result_get_double_idx( result, columnIndex )) );
6753 case DBI_TYPE_STRING :
6754 jsonObjectSetKey( object, columnName,
6755 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6758 case DBI_TYPE_DATETIME :
6760 memset( dt_string, '\0', sizeof( dt_string ));
6761 memset( &gmdt, '\0', sizeof( gmdt ));
6763 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6765 if( !( attr & DBI_DATETIME_DATE )) {
6766 gmtime_r( &_tmp_dt, &gmdt );
6767 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6768 } else if( !( attr & DBI_DATETIME_TIME )) {
6769 localtime_r( &_tmp_dt, &gmdt );
6770 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6772 localtime_r( &_tmp_dt, &gmdt );
6773 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6776 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6779 case DBI_TYPE_BINARY :
6780 osrfLogError( OSRF_LOG_MARK,
6781 "Can't do binary at column %s : index %d", columnName, columnIndex );
6785 } // end while loop traversing result
6790 // Interpret a string as true or false
6791 int str_is_true( const char* str ) {
6792 if( NULL == str || strcasecmp( str, "true" ) )
6798 // Interpret a jsonObject as true or false
6799 static int obj_is_true( const jsonObject* obj ) {
6802 else switch( obj->type )
6810 if( strcasecmp( obj->value.s, "true" ) )
6814 case JSON_NUMBER : // Support 1/0 for perl's sake
6815 if( jsonObjectGetNumber( obj ) == 1.0 )
6824 // Translate a numeric code into a text string identifying a type of
6825 // jsonObject. To be used for building error messages.
6826 static const char* json_type( int code ) {
6832 return "JSON_ARRAY";
6834 return "JSON_STRING";
6836 return "JSON_NUMBER";
6842 return "(unrecognized)";
6846 // Extract the "primitive" attribute from an IDL field definition.
6847 // If we haven't initialized the app, then we must be running in
6848 // some kind of testbed. In that case, default to "string".
6849 static const char* get_primitive( osrfHash* field ) {
6850 const char* s = osrfHashGet( field, "primitive" );
6852 if( child_initialized )
6855 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6857 osrfHashGet( field, "name" )
6865 // Extract the "datatype" attribute from an IDL field definition.
6866 // If we haven't initialized the app, then we must be running in
6867 // some kind of testbed. In that case, default to to NUMERIC,
6868 // since we look at the datatype only for numbers.
6869 static const char* get_datatype( osrfHash* field ) {
6870 const char* s = osrfHashGet( field, "datatype" );
6872 if( child_initialized )
6875 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6877 osrfHashGet( field, "name" )
6886 @brief Determine whether a string is potentially a valid SQL identifier.
6887 @param s The identifier to be tested.
6888 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6890 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6891 need to follow all the rules exactly, such as requiring that the first character not
6894 We allow leading and trailing white space. In between, we do not allow punctuation
6895 (except for underscores and dollar signs), control characters, or embedded white space.
6897 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6898 for the foreseeable future such quoted identifiers are not likely to be an issue.
6900 int is_identifier( const char* s) {
6904 // Skip leading white space
6905 while( isspace( (unsigned char) *s ) )
6909 return 0; // Nothing but white space? Not okay.
6911 // Check each character until we reach white space or
6912 // end-of-string. Letters, digits, underscores, and
6913 // dollar signs are okay. With the exception of periods
6914 // (as in schema.identifier), control characters and other
6915 // punctuation characters are not okay. Anything else
6916 // is okay -- it could for example be part of a multibyte
6917 // UTF8 character such as a letter with diacritical marks,
6918 // and those are allowed.
6920 if( isalnum( (unsigned char) *s )
6924 ; // Fine; keep going
6925 else if( ispunct( (unsigned char) *s )
6926 || iscntrl( (unsigned char) *s ) )
6929 } while( *s && ! isspace( (unsigned char) *s ) );
6931 // If we found any white space in the above loop,
6932 // the rest had better be all white space.
6934 while( isspace( (unsigned char) *s ) )
6938 return 0; // White space was embedded within non-white space
6944 @brief Determine whether to accept a character string as a comparison operator.
6945 @param op The candidate comparison operator.
6946 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6948 We don't validate the operator for real. We just make sure that it doesn't contain
6949 any semicolons or white space (with special exceptions for a few specific operators).
6950 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6951 space but it's still not a valid operator, then the database will complain.
6953 Another approach would be to compare the string against a short list of approved operators.
6954 We don't do that because we want to allow custom operators like ">100*", which at this
6955 writing would be difficult or impossible to express otherwise in a JSON query.
6957 int is_good_operator( const char* op ) {
6958 if( !op ) return 0; // Sanity check
6962 if( isspace( (unsigned char) *s ) ) {
6963 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6964 // and IS NOT DISTINCT FROM.
6965 if( !strcasecmp( op, "similar to" ) )
6967 else if( !strcasecmp( op, "is distinct from" ) )
6969 else if( !strcasecmp( op, "is not distinct from" ) )
6974 else if( ';' == *s )
6982 @name Query Frame Management
6984 The following machinery supports a stack of query frames for use by SELECT().
6986 A query frame caches information about one level of a SELECT query. When we enter
6987 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6989 The query frame stores information about the core class, and about any joined classes
6992 The main purpose is to map table aliases to classes and tables, so that a query can
6993 join to the same table more than once. A secondary goal is to reduce the number of
6994 lookups in the IDL by caching the results.
6998 #define STATIC_CLASS_INFO_COUNT 3
7000 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
7003 @brief Allocate a ClassInfo as raw memory.
7004 @return Pointer to the newly allocated ClassInfo.
7006 Except for the in_use flag, which is used only by the allocation and deallocation
7007 logic, we don't initialize the ClassInfo here.
7009 static ClassInfo* allocate_class_info( void ) {
7010 // In order to reduce the number of mallocs and frees, we return a static
7011 // instance of ClassInfo, if we can find one that we're not already using.
7012 // We rely on the fact that the compiler will implicitly initialize the
7013 // static instances so that in_use == 0.
7016 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7017 if( ! static_class_info[ i ].in_use ) {
7018 static_class_info[ i ].in_use = 1;
7019 return static_class_info + i;
7023 // The static ones are all in use. Malloc one.
7025 return safe_malloc( sizeof( ClassInfo ) );
7029 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
7030 @param info Pointer to the ClassInfo to be cleared.
7032 static void clear_class_info( ClassInfo* info ) {
7037 // Free any malloc'd strings
7039 if( info->alias != info->alias_store )
7040 free( info->alias );
7042 if( info->class_name != info->class_name_store )
7043 free( info->class_name );
7045 free( info->source_def );
7047 info->alias = info->class_name = info->source_def = NULL;
7052 @brief Free a ClassInfo and everything it owns.
7053 @param info Pointer to the ClassInfo to be freed.
7055 static void free_class_info( ClassInfo* info ) {
7060 clear_class_info( info );
7062 // If it's one of the static instances, just mark it as not in use
7065 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7066 if( info == static_class_info + i ) {
7067 static_class_info[ i ].in_use = 0;
7072 // Otherwise it must have been malloc'd, so free it
7078 @brief Populate an already-allocated ClassInfo.
7079 @param info Pointer to the ClassInfo to be populated.
7080 @param alias Alias for the class. If it is NULL, or an empty string, use the class
7082 @param class Name of the class.
7083 @return Zero if successful, or 1 if not.
7085 Populate the ClassInfo with copies of the alias and class name, and with pointers to
7086 the relevant portions of the IDL for the specified class.
7088 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
7091 osrfLogError( OSRF_LOG_MARK,
7092 "%s ERROR: No ClassInfo available to populate", modulename );
7093 info->alias = info->class_name = info->source_def = NULL;
7094 info->class_def = info->fields = info->links = NULL;
7099 osrfLogError( OSRF_LOG_MARK,
7100 "%s ERROR: No class name provided for lookup", modulename );
7101 info->alias = info->class_name = info->source_def = NULL;
7102 info->class_def = info->fields = info->links = NULL;
7106 // Alias defaults to class name if not supplied
7107 if( ! alias || ! alias[ 0 ] )
7110 // Look up class info in the IDL
7111 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
7113 osrfLogError( OSRF_LOG_MARK,
7114 "%s ERROR: Class %s not defined in IDL", modulename, class );
7115 info->alias = info->class_name = info->source_def = NULL;
7116 info->class_def = info->fields = info->links = NULL;
7118 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
7119 osrfLogError( OSRF_LOG_MARK,
7120 "%s ERROR: Class %s is defined as virtual", modulename, class );
7121 info->alias = info->class_name = info->source_def = NULL;
7122 info->class_def = info->fields = info->links = NULL;
7126 osrfHash* links = osrfHashGet( class_def, "links" );
7128 osrfLogError( OSRF_LOG_MARK,
7129 "%s ERROR: No links defined in IDL for class %s", modulename, class );
7130 info->alias = info->class_name = info->source_def = NULL;
7131 info->class_def = info->fields = info->links = NULL;
7135 osrfHash* fields = osrfHashGet( class_def, "fields" );
7137 osrfLogError( OSRF_LOG_MARK,
7138 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
7139 info->alias = info->class_name = info->source_def = NULL;
7140 info->class_def = info->fields = info->links = NULL;
7144 char* source_def = oilsGetRelation( class_def );
7148 // We got everything we need, so populate the ClassInfo
7149 if( strlen( alias ) > ALIAS_STORE_SIZE )
7150 info->alias = strdup( alias );
7152 strcpy( info->alias_store, alias );
7153 info->alias = info->alias_store;
7156 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
7157 info->class_name = strdup( class );
7159 strcpy( info->class_name_store, class );
7160 info->class_name = info->class_name_store;
7163 info->source_def = source_def;
7165 info->class_def = class_def;
7166 info->links = links;
7167 info->fields = fields;
7172 #define STATIC_FRAME_COUNT 3
7174 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
7177 @brief Allocate a QueryFrame as raw memory.
7178 @return Pointer to the newly allocated QueryFrame.
7180 Except for the in_use flag, which is used only by the allocation and deallocation
7181 logic, we don't initialize the QueryFrame here.
7183 static QueryFrame* allocate_frame( void ) {
7184 // In order to reduce the number of mallocs and frees, we return a static
7185 // instance of QueryFrame, if we can find one that we're not already using.
7186 // We rely on the fact that the compiler will implicitly initialize the
7187 // static instances so that in_use == 0.
7190 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7191 if( ! static_frame[ i ].in_use ) {
7192 static_frame[ i ].in_use = 1;
7193 return static_frame + i;
7197 // The static ones are all in use. Malloc one.
7199 return safe_malloc( sizeof( QueryFrame ) );
7203 @brief Free a QueryFrame, and all the memory it owns.
7204 @param frame Pointer to the QueryFrame to be freed.
7206 static void free_query_frame( QueryFrame* frame ) {
7211 clear_class_info( &frame->core );
7213 // Free the join list
7215 ClassInfo* info = frame->join_list;
7218 free_class_info( info );
7222 frame->join_list = NULL;
7225 // If the frame is a static instance, just mark it as unused
7227 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7228 if( frame == static_frame + i ) {
7229 static_frame[ i ].in_use = 0;
7234 // Otherwise it must have been malloc'd, so free it
7240 @brief Search a given QueryFrame for a specified alias.
7241 @param frame Pointer to the QueryFrame to be searched.
7242 @param target The alias for which to search.
7243 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7245 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7246 if( ! frame || ! target ) {
7250 ClassInfo* found_class = NULL;
7252 if( !strcmp( target, frame->core.alias ) )
7253 return &(frame->core);
7255 ClassInfo* curr_class = frame->join_list;
7256 while( curr_class ) {
7257 if( strcmp( target, curr_class->alias ) )
7258 curr_class = curr_class->next;
7260 found_class = curr_class;
7270 @brief Push a new (blank) QueryFrame onto the stack.
7272 static void push_query_frame( void ) {
7273 QueryFrame* frame = allocate_frame();
7274 frame->join_list = NULL;
7275 frame->next = curr_query;
7277 // Initialize the ClassInfo for the core class
7278 ClassInfo* core = &frame->core;
7279 core->alias = core->class_name = core->source_def = NULL;
7280 core->class_def = core->fields = core->links = NULL;
7286 @brief Pop a QueryFrame off the stack and destroy it.
7288 static void pop_query_frame( void ) {
7293 QueryFrame* popped = curr_query;
7294 curr_query = popped->next;
7296 free_query_frame( popped );
7300 @brief Populate the ClassInfo for the core class.
7301 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7302 class name as an alias.
7303 @param class_name Name of the core class.
7304 @return Zero if successful, or 1 if not.
7306 Populate the ClassInfo of the core class with copies of the alias and class name, and
7307 with pointers to the relevant portions of the IDL for the core class.
7309 static int add_query_core( const char* alias, const char* class_name ) {
7312 if( ! curr_query ) {
7313 osrfLogError( OSRF_LOG_MARK,
7314 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7316 } else if( curr_query->core.alias ) {
7317 osrfLogError( OSRF_LOG_MARK,
7318 "%s ERROR: Core class %s already populated as %s",
7319 modulename, curr_query->core.class_name, curr_query->core.alias );
7323 build_class_info( &curr_query->core, alias, class_name );
7324 if( curr_query->core.alias )
7327 osrfLogError( OSRF_LOG_MARK,
7328 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7334 @brief Search the current QueryFrame for a specified alias.
7335 @param target The alias for which to search.
7336 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7338 static inline ClassInfo* search_alias( const char* target ) {
7339 return search_alias_in_frame( curr_query, target );
7343 @brief Search all levels of query for a specified alias, starting with the current query.
7344 @param target The alias for which to search.
7345 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7347 static ClassInfo* search_all_alias( const char* target ) {
7348 ClassInfo* found_class = NULL;
7349 QueryFrame* curr_frame = curr_query;
7351 while( curr_frame ) {
7352 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7355 curr_frame = curr_frame->next;
7362 @brief Add a class to the list of classes joined to the current query.
7363 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7364 the class name as an alias.
7365 @param classname The name of the class to be added.
7366 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7368 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7370 if( ! classname || ! *classname ) { // sanity check
7371 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7378 const ClassInfo* conflict = search_alias( alias );
7380 osrfLogError( OSRF_LOG_MARK,
7381 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7382 modulename, alias, conflict->class_name );
7386 ClassInfo* info = allocate_class_info();
7388 if( build_class_info( info, alias, classname ) ) {
7389 free_class_info( info );
7393 // Add the new ClassInfo to the join list of the current QueryFrame
7394 info->next = curr_query->join_list;
7395 curr_query->join_list = info;
7401 @brief Destroy all nodes on the query stack.
7403 static void clear_query_stack( void ) {
7409 @brief Implement the set_audit_info method.
7410 @param ctx Pointer to the method context.
7411 @return Zero if successful, or -1 if not.
7413 Issue a SAVEPOINT to the database server.
7418 - workstation id (int)
7420 If user id is not provided the authkey will be used.
7421 For PCRUD the authkey is always used, even if a user is provided.
7423 int setAuditInfo( osrfMethodContext* ctx ) {
7424 if(osrfMethodVerifyContext( ctx )) {
7425 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
7429 // Get the user id from the parameters
7430 const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7432 if( enforce_pcrud || !user_id ) {
7433 timeout_needs_resetting = 1;
7434 const jsonObject* user = verifyUserPCRUD( ctx );
7437 osrfAppRespondComplete( ctx, NULL );
7441 // Not PCRUD and have a user_id?
7442 int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7443 osrfAppRespondComplete( ctx, NULL );
7448 @brief Save a audit info
7449 @param ctx Pointer to the method context.
7450 @param user_id User ID to write as a string
7451 @param ws_id Workstation ID to write as a string
7453 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7454 if( ctx && ctx->session ) {
7455 osrfAppSession* session = ctx->session;
7457 osrfHash* cache = session->userData;
7459 // If the session doesn't already have a hash, create one. Make sure
7460 // that the application session frees the hash when it terminates.
7461 if( NULL == cache ) {
7462 session->userData = cache = osrfNewHash();
7463 osrfHashSetCallback( cache, &sessionDataFree );
7464 ctx->session->userDataFree = &userDataFree;
7467 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7469 osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7471 int errnum = dbi_conn_error( writehandle, &msg );
7474 "%s: Error setting auditor information: %d %s",
7477 msg ? msg : "(No description available)"
7479 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7480 "osrfMethodException", ctx->request, "Error setting auditor info" );
7481 if( !oilsIsDBConnected( writehandle ))
7482 osrfAppSessionPanic( ctx->session );
7485 dbi_result_free( result );
7492 @brief Remove all but safe character from savepoint name
7493 @param sp User-supplied savepoint name
7494 @return sanitized savepoint name, or NULL
7496 The caller is expected to free the returned string. Note that
7497 this function exists only because we can't use PQescapeLiteral
7498 without either forking libdbi or abandoning it.
7500 static char* _sanitize_savepoint_name( const char* sp ) {
7502 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_";
7504 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7505 // and the default value of NAMEDATALEN is 64; that should be long enough
7506 // for our purposes, and it's unlikely that anyone is going to recompile
7507 // PostgreSQL to have a smaller value, so cap the identifier name
7508 // accordingly to avoid the remote chance that someone manages to pass in a
7509 // 12GB savepoint name
7510 const int MAX_LITERAL_NAMELEN = 63;
7513 if (len > MAX_LITERAL_NAMELEN) {
7514 len = MAX_LITERAL_NAMELEN;
7517 char* safeSpName = safe_malloc( len + 1 );
7521 for (j = 0; j < len; j++) {
7522 found = strchr(safe_chars, sp[j]);
7524 safeSpName[ i++ ] = found[0];
7527 safeSpName[ i ] = '\0';