3 @brief Utility routines for translating JSON into SQL.
11 #include "opensrf/utils.h"
12 #include "opensrf/log.h"
13 #include "opensrf/osrf_application.h"
14 #include "openils/oils_utils.h"
15 #include "openils/oils_sql.h"
17 // The next four macros are OR'd together as needed to form a set
18 // of bitflags. SUBCOMBO enables an extra pair of parentheses when
19 // nesting one UNION, INTERSECT or EXCEPT inside another.
20 // SUBSELECT tells us we're in a subquery, so don't add the
21 // terminal semicolon yet.
24 #define DISABLE_I18N 2
25 #define SELECT_DISTINCT 1
30 struct ClassInfoStruct;
31 typedef struct ClassInfoStruct ClassInfo;
33 #define ALIAS_STORE_SIZE 16
34 #define CLASS_NAME_STORE_SIZE 16
36 struct ClassInfoStruct {
40 osrfHash* class_def; // Points into IDL
41 osrfHash* fields; // Points into IDL
42 osrfHash* links; // Points into IDL
44 // The remaining members are private and internal. Client code should not
45 // access them directly.
47 ClassInfo* next; // Supports linked list of joined classes
48 int in_use; // boolean
50 // We usually store the alias and class name in the following arrays, and
51 // point the corresponding pointers at them. When the string is too big
52 // for the array (which will probably never happen in practice), we strdup it.
54 char alias_store[ ALIAS_STORE_SIZE + 1 ];
55 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
58 struct QueryFrameStruct;
59 typedef struct QueryFrameStruct QueryFrame;
61 struct QueryFrameStruct {
63 ClassInfo* join_list; // linked list of classes joined to the core class
64 QueryFrame* next; // implements stack as linked list
65 int in_use; // boolean
68 static int timeout_needs_resetting;
69 static time_t time_next_reset;
71 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
73 static void setXactId( osrfMethodContext* ctx );
74 static inline const char* getXactId( osrfMethodContext* ctx );
75 static inline void clearXactId( osrfMethodContext* ctx );
77 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
78 jsonObject* where_hash, jsonObject* query_hash, int* err );
79 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
80 static jsonObject* oilsMakeJSONFromResult( dbi_result );
82 static char* searchSimplePredicate ( const char* op, const char* class_alias,
83 osrfHash* field, const jsonObject* node );
84 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
85 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject* );
86 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*,
88 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
89 static char* searchINPredicate ( const char*, osrfHash*,
90 jsonObject*, const char*, osrfMethodContext* );
91 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
92 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
93 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo*, int, osrfMethodContext* );
94 static char* buildSELECT( const jsonObject*, jsonObject* rest_of_query,
95 osrfHash* meta, osrfMethodContext* ctx );
96 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array );
98 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
100 char* SELECT ( osrfMethodContext*, jsonObject*, const jsonObject*, const jsonObject*,
101 const jsonObject*, const jsonObject*, const jsonObject*, const jsonObject*, int );
103 static osrfStringArray* getPermLocationCache( osrfMethodContext*, const char* );
104 static void setPermLocationCache( osrfMethodContext*, const char*, osrfStringArray* );
106 void userDataFree( void* );
107 static void sessionDataFree( char*, void* );
108 static void pcacheFree( char*, void* );
109 static int obj_is_true( const jsonObject* obj );
110 static const char* json_type( int code );
111 static const char* get_primitive( osrfHash* field );
112 static const char* get_datatype( osrfHash* field );
113 static void pop_query_frame( void );
114 static void push_query_frame( void );
115 static int add_query_core( const char* alias, const char* class_name );
116 static inline ClassInfo* search_alias( const char* target );
117 static ClassInfo* search_all_alias( const char* target );
118 static ClassInfo* add_joined_class( const char* alias, const char* classname );
119 static void clear_query_stack( void );
121 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
122 static int verifyObjectPCRUD( osrfMethodContext*, osrfHash*, const jsonObject*, int );
123 static const char* org_tree_root( osrfMethodContext* ctx );
124 static jsonObject* single_hash( const char* key, const char* value );
126 static int child_initialized = 0; /* boolean */
128 static dbi_conn writehandle; /* our MASTER db connection */
129 static dbi_conn dbhandle; /* our CURRENT db connection */
130 //static osrfHash * readHandles;
132 // The following points to the top of a stack of QueryFrames. It's a little
133 // confusing because the top level of the query is at the bottom of the stack.
134 static QueryFrame* curr_query = NULL;
136 static dbi_conn writehandle; /* our MASTER db connection */
137 static dbi_conn dbhandle; /* our CURRENT db connection */
138 //static osrfHash * readHandles;
140 static int max_flesh_depth = 100;
142 static int perm_at_threshold = 5;
143 static int enforce_pcrud = 0; // Boolean
144 static char* modulename = NULL;
146 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id);
148 static char* _sanitize_savepoint_name( const char* sp );
151 @brief Connect to the database.
152 @return A database connection if successful, or NULL if not.
154 dbi_conn oilsConnectDB( const char* mod_name ) {
156 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
157 if( dbi_initialize( NULL ) == -1 ) {
158 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
161 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
163 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
164 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
165 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
166 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
167 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
168 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
170 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
171 dbi_conn handle = dbi_conn_new( driver );
174 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
177 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
179 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
180 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
182 if( host ) dbi_conn_set_option( handle, "host", host );
183 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
184 if( user ) dbi_conn_set_option( handle, "username", user );
185 if( pw ) dbi_conn_set_option( handle, "password", pw );
186 if( db ) dbi_conn_set_option( handle, "dbname", db );
194 if( dbi_conn_connect( handle ) < 0 ) {
196 if( dbi_conn_connect( handle ) < 0 ) {
198 dbi_conn_error( handle, &msg );
199 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
200 msg ? msg : "(No description available)" );
205 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
211 @brief Select some options.
212 @param module_name: Name of the server.
213 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
215 This source file is used (at this writing) to implement three different servers:
216 - open-ils.reporter-store
220 These servers behave mostly the same, but they implement different combinations of
221 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
223 Here we use the server name in messages to identify which kind of server issued them.
224 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
226 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
228 module_name = "open-ils.cstore"; // bulletproofing with a default
233 modulename = strdup( module_name );
234 enforce_pcrud = do_pcrud;
235 max_flesh_depth = flesh_depth;
239 @brief Install a database connection.
240 @param conn Pointer to a database connection.
242 In some contexts, @a conn may merely provide a driver so that we can process strings
243 properly, without providing an open database connection.
245 void oilsSetDBConnection( dbi_conn conn ) {
246 dbhandle = writehandle = conn;
250 @brief Determine whether a database connection is alive.
251 @param handle Handle for a database connection.
252 @return 1 if the connection is alive, or zero if it isn't.
254 int oilsIsDBConnected( dbi_conn handle ) {
255 // Do an innocuous SELECT. If it succeeds, the database connection is still good.
256 dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
258 dbi_result_free( result );
261 // This is a terrible, horrible, no good, very bad kludge.
262 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
263 // but because (due to a previous error) the database is ignoring all commands,
264 // even innocuous SELECTs, until the current transaction is rolled back. The only
265 // known way to detect this condition via the dbi library is by looking at the error
266 // message. This approach will break if the language or wording of the message ever
268 // Note: the dbi_conn_ping function purports to determine whether the database
269 // connection is live, but at this writing this function is unreliable and useless.
270 static const char* ok_msg = "ERROR: current transaction is aborted, commands "
271 "ignored until end of transaction block\n";
273 dbi_conn_error( handle, &msg );
274 if( strcmp( msg, ok_msg )) {
275 osrfLogError( OSRF_LOG_MARK, "Database connection isn't working" );
278 return 1; // ignoring SELECT due to previous error; that's okay
283 @brief Get a table name, view name, or subquery for use in a FROM clause.
284 @param class Pointer to the IDL class entry.
285 @return A table name, a view name, or a subquery in parentheses.
287 In some cases the IDL defines a class, not with a table name or a view name, but with
288 a SELECT statement, which may be used as a subquery.
290 char* oilsGetRelation( osrfHash* classdef ) {
292 char* source_def = NULL;
293 const char* tabledef = osrfHashGet( classdef, "tablename" );
296 source_def = strdup( tabledef ); // Return the name of a table or view
298 tabledef = osrfHashGet( classdef, "source_definition" );
300 // Return a subquery, enclosed in parentheses
301 source_def = safe_malloc( strlen( tabledef ) + 3 );
302 source_def[ 0 ] = '(';
303 strcpy( source_def + 1, tabledef );
304 strcat( source_def, ")" );
306 // Not found: return an error
307 const char* classname = osrfHashGet( classdef, "classname" );
312 "%s ERROR No tablename or source_definition for class \"%s\"",
323 @brief Add datatypes from the database to the fields in the IDL.
324 @param handle Handle for a database connection
325 @return Zero if successful, or 1 upon error.
327 For each relevant class in the IDL: ask the database for the datatype of every field.
328 In particular, determine which fields are text fields and which fields are numeric
329 fields, so that we know whether to enclose their values in quotes.
331 int oilsExtendIDL( dbi_conn handle ) {
332 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
333 osrfHash* class = NULL;
334 growing_buffer* query_buf = buffer_init( 64 );
335 int results_found = 0; // boolean
337 // For each class in the IDL...
338 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
339 const char* classname = osrfHashIteratorKey( class_itr );
340 osrfHash* fields = osrfHashGet( class, "fields" );
342 // If the class is virtual, ignore it
343 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
344 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
348 char* tabledef = oilsGetRelation( class );
350 continue; // No such relation -- a query of it would be doomed to failure
352 buffer_reset( query_buf );
353 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
357 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
358 modulename, OSRF_BUFFER_C_STR( query_buf ) );
360 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
365 const char* columnName;
366 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
368 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
371 /* fetch the fieldmapper index */
372 osrfHash* _f = osrfHashGet(fields, columnName);
375 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
377 /* determine the field type and storage attributes */
379 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
381 case DBI_TYPE_INTEGER : {
383 if( !osrfHashGet(_f, "primitive") )
384 osrfHashSet(_f, "number", "primitive");
386 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
387 if( attr & DBI_INTEGER_SIZE8 )
388 osrfHashSet( _f, "INT8", "datatype" );
390 osrfHashSet( _f, "INT", "datatype" );
393 case DBI_TYPE_DECIMAL :
394 if( !osrfHashGet( _f, "primitive" ))
395 osrfHashSet( _f, "number", "primitive" );
397 osrfHashSet( _f, "NUMERIC", "datatype" );
400 case DBI_TYPE_STRING :
401 if( !osrfHashGet( _f, "primitive" ))
402 osrfHashSet( _f, "string", "primitive" );
404 osrfHashSet( _f,"TEXT", "datatype" );
407 case DBI_TYPE_DATETIME :
408 if( !osrfHashGet( _f, "primitive" ))
409 osrfHashSet( _f, "string", "primitive" );
411 osrfHashSet( _f, "TIMESTAMP", "datatype" );
414 case DBI_TYPE_BINARY :
415 if( !osrfHashGet( _f, "primitive" ))
416 osrfHashSet( _f, "string", "primitive" );
418 osrfHashSet( _f, "BYTEA", "datatype" );
423 "Setting [%s] to primitive [%s] and datatype [%s]...",
425 osrfHashGet( _f, "primitive" ),
426 osrfHashGet( _f, "datatype" )
430 } // end while loop for traversing columns of result
431 dbi_result_free( result );
434 int errnum = dbi_conn_error( handle, &msg );
435 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
436 errnum, msg ? msg : "(No description available)" );
437 // We don't check the database connection here. It's routine to get failures at
438 // this point; we routinely try to query tables that don't exist, because they
439 // are defined in the IDL but not in the database.
441 } // end for each class in IDL
443 buffer_free( query_buf );
444 osrfHashIteratorFree( class_itr );
445 child_initialized = 1;
447 if( !results_found ) {
448 osrfLogError( OSRF_LOG_MARK,
449 "No results found for any class -- bad database connection?" );
451 } else if( ! oilsIsDBConnected( handle )) {
452 osrfLogError( OSRF_LOG_MARK,
453 "Unable to extend IDL: database connection isn't working" );
461 @brief Free an osrfHash that stores a transaction ID.
462 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
464 This function is a callback, to be called by the application session when it ends.
465 The application session stores the osrfHash via an opaque pointer.
467 If the osrfHash contains an entry for the key "xact_id", it means that an
468 uncommitted transaction is pending. Roll it back.
470 void userDataFree( void* blob ) {
471 osrfHash* hash = (osrfHash*) blob;
472 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
473 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
475 int errnum = dbi_conn_error( writehandle, &msg );
476 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
477 errnum, msg ? msg : "(No description available)" );
481 if( !dbi_conn_query( writehandle, "SELECT auditor.clear_audit_info();" ) ) {
483 int errnum = dbi_conn_error( writehandle, &msg );
484 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform audit info clearing: %d %s",
485 errnum, msg ? msg : "(No description available)" );
489 osrfHashFree( hash );
493 @name Managing session data
494 @brief Maintain data stored via the userData pointer of the application session.
496 Currently, session-level data is stored in an osrfHash. Other arrangements are
497 possible, and some would be more efficient. The application session calls a
498 callback function to free userData before terminating.
500 Currently, the only data we store at the session level is the transaction id. By this
501 means we can ensure that any pending transactions are rolled back before the application
507 @brief Free an item in the application session's userData.
508 @param key The name of a key for an osrfHash.
509 @param item An opaque pointer to the item associated with the key.
511 We store an osrfHash as userData with the application session, and arrange (by
512 installing userDataFree() as a different callback) for the session to free that
513 osrfHash before terminating.
515 This function is a callback for freeing items in the osrfHash. Currently we store
517 - Transaction id of a pending transaction; a character string. Key: "xact_id".
518 - Authkey; a character string. Key: "authkey".
519 - User object from the authentication server; a jsonObject. Key: "user_login".
521 If we ever store anything else in userData, we will need to revisit this function so
522 that it will free whatever else needs freeing.
524 static void sessionDataFree( char* key, void* item ) {
525 if( !strcmp( key, "xact_id" ) || !strcmp( key, "authkey" ) || !strncmp( key, "rs_size_", 8) )
527 else if( !strcmp( key, "user_login" ) )
528 jsonObjectFree( (jsonObject*) item );
529 else if( !strcmp( key, "pcache" ) )
530 osrfHashFree( (osrfHash*) item );
533 static void pcacheFree( char* key, void* item ) {
534 osrfStringArrayFree( (osrfStringArray*) item );
538 @brief Initialize session cache.
539 @param ctx Pointer to the method context.
541 Create a cache for the session by making the session's userData member point
542 to an osrfHash instance.
544 static osrfHash* initSessionCache( osrfMethodContext* ctx ) {
545 ctx->session->userData = osrfNewHash();
546 osrfHashSetCallback( (osrfHash*) ctx->session->userData, &sessionDataFree );
547 ctx->session->userDataFree = &userDataFree;
548 return ctx->session->userData;
552 @brief Save a transaction id.
553 @param ctx Pointer to the method context.
555 Save the session_id of the current application session as a transaction id.
557 static void setXactId( osrfMethodContext* ctx ) {
558 if( ctx && ctx->session ) {
559 osrfAppSession* session = ctx->session;
561 osrfHash* cache = session->userData;
563 // If the session doesn't already have a hash, create one. Make sure
564 // that the application session frees the hash when it terminates.
566 cache = initSessionCache( ctx );
568 // Save the transaction id in the hash, with the key "xact_id"
569 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
574 @brief Get the transaction ID for the current transaction, if any.
575 @param ctx Pointer to the method context.
576 @return Pointer to the transaction ID.
578 The return value points to an internal buffer, and will become invalid upon issuing
579 a commit or rollback.
581 static inline const char* getXactId( osrfMethodContext* ctx ) {
582 if( ctx && ctx->session && ctx->session->userData )
583 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
589 @brief Clear the current transaction id.
590 @param ctx Pointer to the method context.
592 static inline void clearXactId( osrfMethodContext* ctx ) {
593 if( ctx && ctx->session && ctx->session->userData )
594 osrfHashRemove( ctx->session->userData, "xact_id" );
599 @brief Stash the location for a particular perm in the sessionData cache
600 @param ctx Pointer to the method context.
601 @param perm Name of the permission we're looking at
602 @param array StringArray of perm location ids
604 static void setPermLocationCache( osrfMethodContext* ctx, const char* perm, osrfStringArray* locations ) {
605 if( ctx && ctx->session ) {
606 osrfAppSession* session = ctx->session;
608 osrfHash* cache = session->userData;
610 // If the session doesn't already have a hash, create one. Make sure
611 // that the application session frees the hash when it terminates.
613 cache = initSessionCache( ctx );
615 osrfHash* pcache = osrfHashGet(cache, "pcache");
617 if( NULL == pcache ) {
618 pcache = osrfNewHash();
619 osrfHashSetCallback( pcache, &pcacheFree );
620 osrfHashSet( cache, pcache, "pcache" );
623 if( perm && locations )
624 osrfHashSet( pcache, locations, strdup(perm) );
629 @brief Grab stashed location for a particular perm in the sessionData cache
630 @param ctx Pointer to the method context.
631 @param perm Name of the permission we're looking at
633 static osrfStringArray* getPermLocationCache( osrfMethodContext* ctx, const char* perm ) {
634 if( ctx && ctx->session ) {
635 osrfAppSession* session = ctx->session;
636 osrfHash* cache = session->userData;
638 osrfHash* pcache = osrfHashGet(cache, "pcache");
640 return osrfHashGet( pcache, perm );
649 @brief Save the user's login in the userData for the current application session.
650 @param ctx Pointer to the method context.
651 @param user_login Pointer to the user login object to be cached (we cache the original,
654 If @a user_login is NULL, remove the user login if one is already cached.
656 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
657 if( ctx && ctx->session ) {
658 osrfAppSession* session = ctx->session;
660 osrfHash* cache = session->userData;
662 // If the session doesn't already have a hash, create one. Make sure
663 // that the application session frees the hash when it terminates.
665 cache = initSessionCache( ctx );
668 osrfHashSet( cache, user_login, "user_login" );
670 osrfHashRemove( cache, "user_login" );
675 @brief Get the user login object for the current application session, if any.
676 @param ctx Pointer to the method context.
677 @return Pointer to the user login object if found; otherwise NULL.
679 The user login object was returned from the authentication server, and then cached so
680 we don't have to call the authentication server again for the same user.
682 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
683 if( ctx && ctx->session && ctx->session->userData )
684 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
690 @brief Save a copy of an authkey in the userData of the current application session.
691 @param ctx Pointer to the method context.
692 @param authkey The authkey to be saved.
694 If @a authkey is NULL, remove the authkey if one is already cached.
696 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
697 if( ctx && ctx->session && authkey ) {
698 osrfAppSession* session = ctx->session;
699 osrfHash* cache = session->userData;
701 // If the session doesn't already have a hash, create one. Make sure
702 // that the application session frees the hash when it terminates.
704 cache = initSessionCache( ctx );
706 // Save the transaction id in the hash, with the key "xact_id"
707 if( authkey && *authkey )
708 osrfHashSet( cache, strdup( authkey ), "authkey" );
710 osrfHashRemove( cache, "authkey" );
715 @brief Reset the login timeout.
716 @param authkey The authentication key for the current login session.
717 @param now The current time.
718 @return Zero if successful, or 1 if not.
720 Tell the authentication server to reset the timeout so that the login session won't
721 expire for a while longer.
723 We could dispense with the @a now parameter by calling time(). But we just called
724 time() in order to decide whether to reset the timeout, so we might as well reuse
725 the result instead of calling time() again.
727 static int reset_timeout( const char* authkey, time_t now ) {
728 jsonObject* auth_object = jsonNewObject( authkey );
730 // Ask the authentication server to reset the timeout. It returns an event
731 // indicating success or failure.
732 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
733 "open-ils.auth.session.reset_timeout", auth_object );
734 jsonObjectFree( auth_object );
736 if( !result || result->type != JSON_HASH ) {
737 osrfLogError( OSRF_LOG_MARK,
738 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
739 jsonObjectFree( result );
740 return 1; // Not the right sort of object returned
743 const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
744 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
745 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
746 jsonObjectFree( result );
747 return 1; // Return code from method not available
750 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
751 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
753 desc = "(No reason available)"; // failsafe; shouldn't happen
754 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
755 jsonObjectFree( result );
759 // Revise our local proxy for the timeout deadline
760 // by a smallish fraction of the timeout interval
761 const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
763 timeout = "1"; // failsafe; shouldn't happen
764 time_next_reset = now + atoi( timeout ) / 15;
766 jsonObjectFree( result );
767 return 0; // Successfully reset timeout
771 @brief Get the authkey string for the current application session, if any.
772 @param ctx Pointer to the method context.
773 @return Pointer to the cached authkey if found; otherwise NULL.
775 If present, the authkey string was cached from a previous method call.
777 static const char* getAuthkey( osrfMethodContext* ctx ) {
778 if( ctx && ctx->session && ctx->session->userData ) {
779 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
780 // LFW recent changes mean the userData hash gets set up earlier, but
781 // doesn't necessarily have an authkey yet
785 // Possibly reset the authentication timeout to keep the login alive. We do so
786 // no more than once per method call, and not at all if it has been only a short
787 // time since the last reset.
789 // Here we reset explicitly, if at all. We also implicitly reset the timeout
790 // whenever we call the "open-ils.auth.session.retrieve" method.
791 if( timeout_needs_resetting ) {
792 time_t now = time( NULL );
793 if( now >= time_next_reset && reset_timeout( authkey, now ) )
794 authkey = NULL; // timeout has apparently expired already
797 timeout_needs_resetting = 0;
805 @brief Implement the transaction.begin method.
806 @param ctx Pointer to the method context.
807 @return Zero if successful, or -1 upon error.
809 Start a transaction. Save a transaction ID for future reference.
812 - authkey (PCRUD only)
814 Return to client: Transaction ID
816 int beginTransaction( osrfMethodContext* ctx ) {
817 if(osrfMethodVerifyContext( ctx )) {
818 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
822 if( enforce_pcrud ) {
823 timeout_needs_resetting = 1;
824 const jsonObject* user = verifyUserPCRUD( ctx );
829 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
832 int errnum = dbi_conn_error( writehandle, &msg );
833 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
834 modulename, errnum, msg ? msg : "(No description available)" );
835 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
836 "osrfMethodException", ctx->request, "Error starting transaction" );
837 if( !oilsIsDBConnected( writehandle ))
838 osrfAppSessionPanic( ctx->session );
841 dbi_result_free( result );
843 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
844 osrfAppRespondComplete( ctx, ret );
845 jsonObjectFree( ret );
851 @brief Implement the savepoint.set method.
852 @param ctx Pointer to the method context.
853 @return Zero if successful, or -1 if not.
855 Issue a SAVEPOINT to the database server.
858 - authkey (PCRUD only)
861 Return to client: Savepoint name
863 int setSavepoint( osrfMethodContext* ctx ) {
864 if(osrfMethodVerifyContext( ctx )) {
865 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
870 if( enforce_pcrud ) {
872 timeout_needs_resetting = 1;
873 const jsonObject* user = verifyUserPCRUD( ctx );
878 // Verify that a transaction is pending
879 const char* trans_id = getXactId( ctx );
880 if( NULL == trans_id ) {
881 osrfAppSessionStatus(
883 OSRF_STATUS_INTERNALSERVERERROR,
884 "osrfMethodException",
886 "No active transaction -- required for savepoints"
891 // Get the savepoint name from the method params
892 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
895 osrfLogWarning(OSRF_LOG_MARK, "savepoint.set called with no name");
899 char *safeSpName = _sanitize_savepoint_name( spName );
901 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", safeSpName );
905 int errnum = dbi_conn_error( writehandle, &msg );
908 "%s: Error creating savepoint %s in transaction %s: %d %s",
913 msg ? msg : "(No description available)"
915 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
916 "osrfMethodException", ctx->request, "Error creating savepoint" );
917 if( !oilsIsDBConnected( writehandle ))
918 osrfAppSessionPanic( ctx->session );
921 dbi_result_free( result );
922 jsonObject* ret = jsonNewObject( spName );
923 osrfAppRespondComplete( ctx, ret );
924 jsonObjectFree( ret );
930 @brief Implement the savepoint.release method.
931 @param ctx Pointer to the method context.
932 @return Zero if successful, or -1 if not.
934 Issue a RELEASE SAVEPOINT to the database server.
937 - authkey (PCRUD only)
940 Return to client: Savepoint name
942 int releaseSavepoint( osrfMethodContext* ctx ) {
943 if(osrfMethodVerifyContext( ctx )) {
944 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
949 if( enforce_pcrud ) {
951 timeout_needs_resetting = 1;
952 const jsonObject* user = verifyUserPCRUD( ctx );
957 // Verify that a transaction is pending
958 const char* trans_id = getXactId( ctx );
959 if( NULL == trans_id ) {
960 osrfAppSessionStatus(
962 OSRF_STATUS_INTERNALSERVERERROR,
963 "osrfMethodException",
965 "No active transaction -- required for savepoints"
970 // Get the savepoint name from the method params
971 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
974 osrfLogWarning(OSRF_LOG_MARK, "savepoint.release called with no name");
978 char *safeSpName = _sanitize_savepoint_name( spName );
980 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", safeSpName );
984 int errnum = dbi_conn_error( writehandle, &msg );
987 "%s: Error releasing savepoint %s in transaction %s: %d %s",
992 msg ? msg : "(No description available)"
994 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
995 "osrfMethodException", ctx->request, "Error releasing savepoint" );
996 if( !oilsIsDBConnected( writehandle ))
997 osrfAppSessionPanic( ctx->session );
1000 dbi_result_free( result );
1001 jsonObject* ret = jsonNewObject( spName );
1002 osrfAppRespondComplete( ctx, ret );
1003 jsonObjectFree( ret );
1009 @brief Implement the savepoint.rollback method.
1010 @param ctx Pointer to the method context.
1011 @return Zero if successful, or -1 if not.
1013 Issue a ROLLBACK TO SAVEPOINT to the database server.
1016 - authkey (PCRUD only)
1019 Return to client: Savepoint name
1021 int rollbackSavepoint( osrfMethodContext* ctx ) {
1022 if(osrfMethodVerifyContext( ctx )) {
1023 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1028 if( enforce_pcrud ) {
1030 timeout_needs_resetting = 1;
1031 const jsonObject* user = verifyUserPCRUD( ctx );
1036 // Verify that a transaction is pending
1037 const char* trans_id = getXactId( ctx );
1038 if( NULL == trans_id ) {
1039 osrfAppSessionStatus(
1041 OSRF_STATUS_INTERNALSERVERERROR,
1042 "osrfMethodException",
1044 "No active transaction -- required for savepoints"
1049 // Get the savepoint name from the method params
1050 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1053 osrfLogWarning(OSRF_LOG_MARK, "savepoint.rollback called with no name");
1057 char *safeSpName = _sanitize_savepoint_name( spName );
1059 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", safeSpName );
1063 int errnum = dbi_conn_error( writehandle, &msg );
1066 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
1071 msg ? msg : "(No description available)"
1073 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1074 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1075 if( !oilsIsDBConnected( writehandle ))
1076 osrfAppSessionPanic( ctx->session );
1079 dbi_result_free( result );
1080 jsonObject* ret = jsonNewObject( spName );
1081 osrfAppRespondComplete( ctx, ret );
1082 jsonObjectFree( ret );
1088 @brief Implement the transaction.commit method.
1089 @param ctx Pointer to the method context.
1090 @return Zero if successful, or -1 if not.
1092 Issue a COMMIT to the database server.
1095 - authkey (PCRUD only)
1097 Return to client: Transaction ID.
1099 int commitTransaction( osrfMethodContext* ctx ) {
1100 if(osrfMethodVerifyContext( ctx )) {
1101 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1105 if( enforce_pcrud ) {
1106 timeout_needs_resetting = 1;
1107 const jsonObject* user = verifyUserPCRUD( ctx );
1112 // Verify that a transaction is pending
1113 const char* trans_id = getXactId( ctx );
1114 if( NULL == trans_id ) {
1115 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1116 "osrfMethodException", ctx->request, "No active transaction to commit" );
1120 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1123 int errnum = dbi_conn_error( writehandle, &msg );
1124 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1125 modulename, errnum, msg ? msg : "(No description available)" );
1126 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1127 "osrfMethodException", ctx->request, "Error committing transaction" );
1128 if( !oilsIsDBConnected( writehandle ))
1129 osrfAppSessionPanic( ctx->session );
1132 dbi_result_free( result );
1133 jsonObject* ret = jsonNewObject( trans_id );
1134 osrfAppRespondComplete( ctx, ret );
1135 jsonObjectFree( ret );
1142 @brief Implement the transaction.rollback method.
1143 @param ctx Pointer to the method context.
1144 @return Zero if successful, or -1 if not.
1146 Issue a ROLLBACK to the database server.
1149 - authkey (PCRUD only)
1151 Return to client: Transaction ID
1153 int rollbackTransaction( osrfMethodContext* ctx ) {
1154 if( osrfMethodVerifyContext( ctx )) {
1155 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1159 if( enforce_pcrud ) {
1160 timeout_needs_resetting = 1;
1161 const jsonObject* user = verifyUserPCRUD( ctx );
1166 // Verify that a transaction is pending
1167 const char* trans_id = getXactId( ctx );
1168 if( NULL == trans_id ) {
1169 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1170 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1174 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1177 int errnum = dbi_conn_error( writehandle, &msg );
1178 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1179 modulename, errnum, msg ? msg : "(No description available)" );
1180 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1181 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1182 if( !oilsIsDBConnected( writehandle ))
1183 osrfAppSessionPanic( ctx->session );
1186 dbi_result_free( result );
1187 jsonObject* ret = jsonNewObject( trans_id );
1188 osrfAppRespondComplete( ctx, ret );
1189 jsonObjectFree( ret );
1196 @brief Implement the "search" method.
1197 @param ctx Pointer to the method context.
1198 @return Zero if successful, or -1 if not.
1201 - authkey (PCRUD only)
1202 - WHERE clause, as jsonObject
1203 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1205 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1206 Optionally flesh linked fields.
1208 int doSearch( osrfMethodContext* ctx ) {
1209 if( osrfMethodVerifyContext( ctx )) {
1210 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1215 timeout_needs_resetting = 1;
1217 jsonObject* where_clause;
1218 jsonObject* rest_of_query;
1220 if( enforce_pcrud ) {
1221 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1222 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1224 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1225 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1228 if( !where_clause ) {
1229 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1233 // Get the class metadata
1234 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1235 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1239 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1241 osrfAppRespondComplete( ctx, NULL );
1245 // doFieldmapperSearch() now takes care of our responding for us
1246 // // Return each row to the client
1247 // jsonObject* cur = 0;
1248 // unsigned long res_idx = 0;
1250 // while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1251 // // We used to discard based on perms here, but now that's
1252 // // inside doFieldmapperSearch()
1253 // osrfAppRespond( ctx, cur );
1256 jsonObjectFree( obj );
1258 osrfAppRespondComplete( ctx, NULL );
1263 @brief Implement the "id_list" method.
1264 @param ctx Pointer to the method context.
1265 @param err Pointer through which to return an error code.
1266 @return Zero if successful, or -1 if not.
1269 - authkey (PCRUD only)
1270 - WHERE clause, as jsonObject
1271 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1273 Return to client: The primary key values for all rows of the relevant class that
1274 satisfy a specified WHERE clause.
1276 This method relies on the assumption that every class has a primary key consisting of
1279 int doIdList( osrfMethodContext* ctx ) {
1280 if( osrfMethodVerifyContext( ctx )) {
1281 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1286 timeout_needs_resetting = 1;
1288 jsonObject* where_clause;
1289 jsonObject* rest_of_query;
1291 // We use the where clause without change. But we need to massage the rest of the
1292 // query, so we work with a copy of it instead of modifying the original.
1294 if( enforce_pcrud ) {
1295 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1296 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1298 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1299 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1302 if( !where_clause ) {
1303 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1307 // Eliminate certain SQL clauses, if present.
1308 if( rest_of_query ) {
1309 jsonObjectRemoveKey( rest_of_query, "select" );
1310 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1311 jsonObjectRemoveKey( rest_of_query, "flesh" );
1312 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1314 rest_of_query = jsonNewObjectType( JSON_HASH );
1317 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1319 // Get the class metadata
1320 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1321 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1323 // Build a SELECT list containing just the primary key,
1324 // i.e. like { "classname":["keyname"] }
1325 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1327 // Load array with name of primary key
1328 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1329 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1330 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1332 jsonObjectSetKey( rest_of_query, "select", select_clause );
1337 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1339 jsonObjectFree( rest_of_query );
1341 osrfAppRespondComplete( ctx, NULL );
1345 // Return each primary key value to the client
1347 unsigned long res_idx = 0;
1348 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1349 // We used to discard based on perms here, but now that's
1350 // inside doFieldmapperSearch()
1351 osrfAppRespond( ctx,
1352 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1355 jsonObjectFree( obj );
1356 osrfAppRespondComplete( ctx, NULL );
1361 @brief Verify that we have a valid class reference.
1362 @param ctx Pointer to the method context.
1363 @param param Pointer to the method parameters.
1364 @return 1 if the class reference is valid, or zero if it isn't.
1366 The class of the method params must match the class to which the method id devoted.
1367 For PCRUD there are additional restrictions.
1369 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1371 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1372 osrfHash* class = osrfHashGet( method_meta, "class" );
1374 // Compare the method's class to the parameters' class
1375 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1377 // Oops -- they don't match. Complain.
1378 growing_buffer* msg = buffer_init( 128 );
1381 "%s: %s method for type %s was passed a %s",
1383 osrfHashGet( method_meta, "methodtype" ),
1384 osrfHashGet( class, "classname" ),
1385 param->classname ? param->classname : "(null)"
1388 char* m = buffer_release( msg );
1389 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1397 return verifyObjectPCRUD( ctx, class, param, 1 );
1403 @brief (PCRUD only) Verify that the user is properly logged in.
1404 @param ctx Pointer to the method context.
1405 @return If the user is logged in, a pointer to the user object from the authentication
1406 server; otherwise NULL.
1408 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1410 // Get the authkey (the first method parameter)
1411 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1413 // See if we have the same authkey, and a user object,
1414 // locally cached from a previous call
1415 const char* cached_authkey = getAuthkey( ctx );
1416 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1417 const jsonObject* cached_user = getUserLogin( ctx );
1422 // We have no matching authentication data in the cache. Authenticate from scratch.
1423 jsonObject* auth_object = jsonNewObject( auth );
1425 // Fetch the user object from the authentication server
1426 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1428 jsonObjectFree( auth_object );
1430 if( !user->classname || strcmp(user->classname, "au" )) {
1432 growing_buffer* msg = buffer_init( 128 );
1435 "%s: permacrud received a bad auth token: %s",
1440 char* m = buffer_release( msg );
1441 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1445 jsonObjectFree( user );
1447 } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
1448 // Failed to set audit information - But note that write_audit_info already set error information.
1449 jsonObjectFree( user );
1453 setUserLogin( ctx, user );
1454 setAuthkey( ctx, auth );
1456 // Allow ourselves up to a second before we have to reset the login timeout.
1457 // It would be nice to use some fraction of the timeout interval enforced by the
1458 // authentication server, but that value is not readily available at this point.
1459 // Instead, we use a conservative default interval.
1460 time_next_reset = time( NULL ) + 1;
1466 @brief For PCRUD: Determine whether the current user may access the current row.
1467 @param ctx Pointer to the method context.
1468 @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1469 @param obj Pointer to the row being potentially accessed.
1470 @return 1 if access is permitted, or 0 if it isn't.
1472 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1474 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1476 dbhandle = writehandle;
1478 // Figure out what class and method are involved
1479 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1480 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1483 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1484 if (rs_size_from_hash) {
1485 rs_size = *rs_size_from_hash;
1486 osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1490 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1491 // contexts we will do another lookup of the current row, even if we already have a
1492 // previously fetched row image, because the row image in hand may not include the
1493 // foreign key(s) that we need.
1495 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1496 // but they aren't implemented yet.
1499 if( *method_type == 's' || *method_type == 'i' ) {
1500 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1502 } else if( *method_type == 'u' || *method_type == 'd' ) {
1503 fetch = 1; // MUST go to the db for the object for update and delete
1506 // Get the appropriate permacrud entry from the IDL, depending on method type
1507 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1509 // No permacrud for this method type on this class
1511 growing_buffer* msg = buffer_init( 128 );
1514 "%s: %s on class %s has no permacrud IDL entry",
1516 osrfHashGet( method_metadata, "methodtype" ),
1517 osrfHashGet( class, "classname" )
1520 char* m = buffer_release( msg );
1521 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1522 "osrfMethodException", ctx->request, m );
1529 // Get the user id, and make sure the user is logged in
1530 const jsonObject* user = verifyUserPCRUD( ctx );
1532 return 0; // Not logged in? No access.
1534 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1536 // Get a list of permissions from the permacrud entry.
1537 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1538 if( permission->size == 0 ) {
1541 "No permissions required for this action (class %s), passing through",
1542 osrfHashGet(class, "classname")
1547 // Build a list of org units that own the row. This is fairly convoluted because there
1548 // are several different ways that an org unit may own the row, as defined by the
1551 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1552 // identifying an owning org_unit..
1553 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1555 // Foreign context adds a layer of indirection. The row points to some other row that
1556 // an org unit may own. The "jump" attribute, if present, adds another layer of
1558 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1560 // The following string array stores the list of org units. (We don't have a thingie
1561 // for storing lists of integers, so we fake it with a list of strings.)
1562 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1564 const char* context_org = NULL;
1565 const char* pkey = NULL;
1566 jsonObject *param = NULL;
1567 const char* perm = NULL;
1571 const char* pkey_value = NULL;
1572 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1573 // If the global_required attribute is present and true, then the only owning
1574 // org unit is the root org unit, i.e. the one with no parent.
1575 osrfLogDebug( OSRF_LOG_MARK,
1576 "global-level permissions required, fetching top of the org tree" );
1578 // no need to check perms for org tree root retrieval
1579 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1580 // check for perm at top of org tree
1581 const char* org_tree_root_id = org_tree_root( ctx );
1582 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1584 if( org_tree_root_id ) {
1585 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1586 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1588 osrfStringArrayFree( context_org_array );
1593 // If the global_required attribute is absent or false, then we look for
1594 // local and/or foreign context. In order to find the relevant foreign
1595 // keys, we must either read the relevant row from the database, or look at
1596 // the image of the row that we already have in memory.
1598 // Even if we have an image of the row in memory, that image may not include the
1599 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1600 // of the row to make sure that we have what we need.
1602 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1603 "fetching context org ids" );
1605 pkey = osrfHashGet( class, "primarykey" );
1608 // There is no primary key, so we can't do a fresh lookup. Use the row
1609 // image that we already have. If it doesn't have everything we need, too bad.
1611 param = jsonObjectClone( obj );
1612 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1613 } else if( obj->classname ) {
1614 pkey_value = oilsFMGetStringConst( obj, pkey );
1616 param = jsonObjectClone( obj );
1617 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1620 pkey_value = jsonObjectGetString( obj );
1622 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1623 "of %s and retrieving from the database", pkey_value );
1627 // Fetch the row so that we can look at the foreign key(s)
1628 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1629 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1630 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1631 jsonObjectFree( _tmp_params );
1632 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1634 param = jsonObjectExtractIndex( _list, 0 );
1635 jsonObjectFree( _list );
1641 // The row doesn't exist. Complain, and deny access.
1642 osrfLogDebug( OSRF_LOG_MARK,
1643 "Object not found in the database with primary key %s of %s",
1646 growing_buffer* msg = buffer_init( 128 );
1649 "%s: no object found with primary key %s of %s",
1655 char* m = buffer_release( msg );
1656 osrfAppSessionStatus(
1658 OSRF_STATUS_INTERNALSERVERERROR,
1659 "osrfMethodException",
1668 if( local_context && local_context->size > 0 ) {
1669 // The IDL provides a list of column names for the foreign keys denoting
1670 // local context, i.e. columns identifying owing org units directly. Look up
1671 // the value of each one, and if it isn't null, add it to the list of org units.
1672 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1673 local_context->size );
1675 const char* lcontext = NULL;
1676 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1677 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1678 if( fkey_value ) { // if not null
1679 osrfStringArrayAdd( context_org_array, fkey_value );
1682 "adding class-local field %s (value: %s) to the context org list",
1684 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1690 if( foreign_context ) {
1691 unsigned long class_count = osrfHashGetCount( foreign_context );
1692 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1694 if( class_count > 0 ) {
1696 // The IDL provides a list of foreign key columns pointing to rows that
1697 // an org unit may own. Follow each link, identify the owning org unit,
1698 // and add it to the list.
1699 osrfHash* fcontext = NULL;
1700 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1701 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1702 // For each class to which a foreign key points:
1703 const char* class_name = osrfHashIteratorKey( class_itr );
1704 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1708 "%d foreign context fields(s) specified for class %s",
1709 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1713 // Get the name of the key field in the foreign table
1714 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1716 // Get the value of the foreign key pointing to the foreign table
1717 char* foreign_pkey_value =
1718 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1719 if( !foreign_pkey_value )
1720 continue; // Foreign key value is null; skip it
1722 // Look up the row to which the foreign key points
1723 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1725 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1726 jsonObject* _list = doFieldmapperSearch(
1727 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1728 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1730 jsonObject* _fparam = NULL;
1731 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1732 _fparam = jsonObjectExtractIndex( _list, 0 );
1734 jsonObjectFree( _tmp_params );
1735 jsonObjectFree( _list );
1737 // At this point _fparam either points to the row identified by the
1738 // foreign key, or it's NULL (no such row found).
1740 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1742 const char* bad_class = NULL; // For noting failed lookups
1744 bad_class = class_name; // Referenced row not found
1745 else if( jump_list ) {
1746 // Follow a chain of rows, linked by foreign keys, to find an owner
1747 const char* flink = NULL;
1749 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1750 // For each entry in the jump list. Each entry (i.e. flink) is
1751 // the name of a foreign key column in the current row.
1753 // From the IDL, get the linkage information for the next jump
1754 osrfHash* foreign_link_hash =
1755 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1757 // Get the class metadata for the class
1758 // to which the foreign key points
1759 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1760 osrfHashGet( foreign_link_hash, "class" ));
1762 // Get the name of the referenced key of that class
1763 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1765 // Get the value of the foreign key pointing to that class
1766 free( foreign_pkey_value );
1767 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1768 if( !foreign_pkey_value )
1769 break; // Foreign key is null; quit looking
1771 // Build a WHERE clause for the lookup
1772 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1775 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1776 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1777 _tmp_params, NULL, &err );
1778 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1780 // Get the resulting row
1781 jsonObjectFree( _fparam );
1782 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1783 _fparam = jsonObjectExtractIndex( _list, 0 );
1785 // Referenced row not found
1787 bad_class = osrfHashGet( foreign_link_hash, "class" );
1790 jsonObjectFree( _tmp_params );
1791 jsonObjectFree( _list );
1797 // We had a foreign key pointing to such-and-such a row, but then
1798 // we couldn't fetch that row. The data in the database are in an
1799 // inconsistent state; the database itself may even be corrupted.
1800 growing_buffer* msg = buffer_init( 128 );
1803 "%s: no object of class %s found with primary key %s of %s",
1807 foreign_pkey_value ? foreign_pkey_value : "(null)"
1810 char* m = buffer_release( msg );
1811 osrfAppSessionStatus(
1813 OSRF_STATUS_INTERNALSERVERERROR,
1814 "osrfMethodException",
1820 osrfHashIteratorFree( class_itr );
1821 free( foreign_pkey_value );
1822 jsonObjectFree( param );
1827 free( foreign_pkey_value );
1830 // Examine each context column of the foreign row,
1831 // and add its value to the list of org units.
1833 const char* foreign_field = NULL;
1834 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1835 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1836 osrfStringArrayAdd( context_org_array,
1837 oilsFMGetStringConst( _fparam, foreign_field ));
1838 osrfLogDebug( OSRF_LOG_MARK,
1839 "adding foreign class %s field %s (value: %s) "
1840 "to the context org list",
1843 osrfStringArrayGetString(
1844 context_org_array, context_org_array->size - 1 )
1848 jsonObjectFree( _fparam );
1852 osrfHashIteratorFree( class_itr );
1857 // If there is an owning_user attached to the action, we allow that user and users with
1858 // object perms on the object. CREATE can't use this. We only do this when there is no
1859 // context org for this action, and when we're not ignoring object perms.
1861 *method_type != 'c' &&
1862 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
1863 context_org_array->size == 0
1865 char* owning_user_field = osrfHashGet( pcrud, "owning_user" );
1866 if (owning_user_field) {
1868 if (!param) { // We didn't get it during the context lookup
1869 pkey = osrfHashGet( class, "primarykey" );
1872 // There is no primary key, so we can't do a fresh lookup. Use the row
1873 // image that we already have. If it doesn't have everything we need, too bad.
1875 param = jsonObjectClone( obj );
1876 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1877 } else if( obj->classname ) {
1878 pkey_value = oilsFMGetStringConst( obj, pkey );
1880 param = jsonObjectClone( obj );
1881 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1884 pkey_value = jsonObjectGetString( obj );
1886 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1887 "of %s and retrieving from the database", pkey_value );
1891 // Fetch the row so that we can look at the foreign key(s)
1892 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1893 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1894 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1895 jsonObjectFree( _tmp_params );
1896 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1898 param = jsonObjectExtractIndex( _list, 0 );
1899 jsonObjectFree( _list );
1904 // The row doesn't exist. Complain, and deny access.
1905 osrfLogDebug( OSRF_LOG_MARK,
1906 "Object not found in the database with primary key %s of %s",
1909 growing_buffer* msg = buffer_init( 128 );
1912 "%s: no object found with primary key %s of %s",
1918 char* m = buffer_release( msg );
1919 osrfAppSessionStatus(
1921 OSRF_STATUS_INTERNALSERVERERROR,
1922 "osrfMethodException",
1931 int ownerid = atoi( oilsFMGetStringConst( param, owning_user_field ) );
1933 // Allow the owner to do whatever
1934 if (ownerid == userid)
1938 while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
1943 "Checking object permission [%s] for user %d "
1944 "on object %s (class %s)",
1948 osrfHashGet( class, "classname" )
1951 result = dbi_conn_queryf(
1953 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s') AS has_perm;",
1956 osrfHashGet( class, "classname" ),
1963 "Received a result for object permission [%s] "
1964 "for user %d on object %s (class %s)",
1968 osrfHashGet( class, "classname" )
1971 if( dbi_result_first_row( result )) {
1972 jsonObject* return_val = oilsMakeJSONFromResult( result );
1973 const char* has_perm = jsonObjectGetString(
1974 jsonObjectGetKeyConst( return_val, "has_perm" ));
1978 "Status of object permission [%s] for user %d "
1979 "on object %s (class %s) is %s",
1983 osrfHashGet(class, "classname"),
1987 if( *has_perm == 't' )
1989 jsonObjectFree( return_val );
1992 dbi_result_free( result );
1997 int errnum = dbi_conn_error( writehandle, &msg );
1998 osrfLogWarning( OSRF_LOG_MARK,
1999 "Unable to call check object permissions: %d, %s",
2000 errnum, msg ? msg : "(No description available)" );
2001 if( !oilsIsDBConnected( writehandle ))
2002 osrfAppSessionPanic( ctx->session );
2009 // For every combination of permission and context org unit: call a stored procedure
2010 // to determine if the user has this permission in the context of this org unit.
2011 // If the answer is yes at any point, then we're done, and the user has permission.
2012 // In other words permissions are additive.
2014 while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
2017 osrfStringArray* pcache = NULL;
2018 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
2019 pcache = getPermLocationCache(ctx, perm);
2022 pcache = osrfNewStringArray(0);
2024 result = dbi_conn_queryf(
2026 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
2034 "Received a result for permission [%s] for user %d",
2039 if( dbi_result_first_row( result )) {
2041 jsonObject* return_val = oilsMakeJSONFromResult( result );
2042 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
2043 jsonObjectFree( return_val );
2044 } while( dbi_result_next_row( result ));
2046 setPermLocationCache(ctx, perm, pcache);
2049 dbi_result_free( result );
2055 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
2057 if (rs_size > perm_at_threshold) {
2058 if (osrfStringArrayContains( pcache, context_org )) {
2066 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
2068 !str_is_true( osrfHashGet(pcrud, "global_required") ) ||
2069 osrfHashGet(pcrud, "owning_user")
2074 "Checking object permission [%s] for user %d "
2075 "on object %s (class %s) at org %d",
2079 osrfHashGet( class, "classname" ),
2083 result = dbi_conn_queryf(
2085 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
2088 osrfHashGet( class, "classname" ),
2096 "Received a result for object permission [%s] "
2097 "for user %d on object %s (class %s) at org %d",
2101 osrfHashGet( class, "classname" ),
2105 if( dbi_result_first_row( result )) {
2106 jsonObject* return_val = oilsMakeJSONFromResult( result );
2107 const char* has_perm = jsonObjectGetString(
2108 jsonObjectGetKeyConst( return_val, "has_perm" ));
2112 "Status of object permission [%s] for user %d "
2113 "on object %s (class %s) at org %d is %s",
2117 osrfHashGet(class, "classname"),
2122 if( *has_perm == 't' )
2124 jsonObjectFree( return_val );
2127 dbi_result_free( result );
2132 int errnum = dbi_conn_error( writehandle, &msg );
2133 osrfLogWarning( OSRF_LOG_MARK,
2134 "Unable to call check object permissions: %d, %s",
2135 errnum, msg ? msg : "(No description available)" );
2136 if( !oilsIsDBConnected( writehandle ))
2137 osrfAppSessionPanic( ctx->session );
2141 if (rs_size > perm_at_threshold) break;
2143 osrfLogDebug( OSRF_LOG_MARK,
2144 "Checking non-object permission [%s] for user %d at org %d",
2145 perm, userid, atoi(context_org) );
2146 result = dbi_conn_queryf(
2148 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
2155 osrfLogDebug( OSRF_LOG_MARK,
2156 "Received a result for permission [%s] for user %d at org %d",
2157 perm, userid, atoi( context_org ));
2158 if( dbi_result_first_row( result )) {
2159 jsonObject* return_val = oilsMakeJSONFromResult( result );
2160 const char* has_perm = jsonObjectGetString(
2161 jsonObjectGetKeyConst( return_val, "has_perm" ));
2162 osrfLogDebug( OSRF_LOG_MARK,
2163 "Status of permission [%s] for user %d at org %d is [%s]",
2164 perm, userid, atoi( context_org ), has_perm );
2165 if( *has_perm == 't' )
2167 jsonObjectFree( return_val );
2170 dbi_result_free( result );
2175 int errnum = dbi_conn_error( writehandle, &msg );
2176 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
2177 errnum, msg ? msg : "(No description available)" );
2178 if( !oilsIsDBConnected( writehandle ))
2179 osrfAppSessionPanic( ctx->session );
2188 osrfStringArrayFree( context_org_array );
2194 @brief Look up the root of the org_unit tree.
2195 @param ctx Pointer to the method context.
2196 @return The id of the root org unit, as a character string.
2198 Query actor.org_unit where parent_ou is null, and return the id as a string.
2200 This function assumes that there is only one root org unit, i.e. that we
2201 have a single tree, not a forest.
2203 The calling code is responsible for freeing the returned string.
2205 static const char* org_tree_root( osrfMethodContext* ctx ) {
2207 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
2208 static time_t last_lookup_time = 0;
2209 time_t current_time = time( NULL );
2211 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2212 // We successfully looked this up less than an hour ago.
2213 // It's not likely to have changed since then.
2214 return strdup( cached_root_id );
2216 last_lookup_time = current_time;
2219 jsonObject* where_clause = single_hash( "parent_ou", NULL );
2220 jsonObject* result = doFieldmapperSearch(
2221 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2222 jsonObjectFree( where_clause );
2224 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2227 jsonObjectFree( result );
2229 growing_buffer* msg = buffer_init( 128 );
2230 OSRF_BUFFER_ADD( msg, modulename );
2231 OSRF_BUFFER_ADD( msg,
2232 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2234 char* m = buffer_release( msg );
2235 osrfAppSessionStatus( ctx->session,
2236 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2239 cached_root_id[ 0 ] = '\0';
2243 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2244 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2246 strcpy( cached_root_id, root_org_unit_id );
2247 jsonObjectFree( result );
2248 return cached_root_id;
2252 @brief Create a JSON_HASH with a single key/value pair.
2253 @param key The key of the key/value pair.
2254 @param value the value of the key/value pair.
2255 @return Pointer to a newly created jsonObject of type JSON_HASH.
2257 The value of the key/value is either a string or (if @a value is NULL) a null.
2259 static jsonObject* single_hash( const char* key, const char* value ) {
2261 if( ! key ) key = "";
2263 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2264 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2269 int doCreate( osrfMethodContext* ctx ) {
2270 if(osrfMethodVerifyContext( ctx )) {
2271 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2276 timeout_needs_resetting = 1;
2278 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2279 jsonObject* target = NULL;
2280 jsonObject* options = NULL;
2282 if( enforce_pcrud ) {
2283 target = jsonObjectGetIndex( ctx->params, 1 );
2284 options = jsonObjectGetIndex( ctx->params, 2 );
2286 target = jsonObjectGetIndex( ctx->params, 0 );
2287 options = jsonObjectGetIndex( ctx->params, 1 );
2290 if( !verifyObjectClass( ctx, target )) {
2291 osrfAppRespondComplete( ctx, NULL );
2295 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2297 const char* trans_id = getXactId( ctx );
2299 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2301 osrfAppSessionStatus(
2303 OSRF_STATUS_BADREQUEST,
2304 "osrfMethodException",
2306 "No active transaction -- required for CREATE"
2308 osrfAppRespondComplete( ctx, NULL );
2312 // The following test is harmless but redundant. If a class is
2313 // readonly, we don't register a create method for it.
2314 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2315 osrfAppSessionStatus(
2317 OSRF_STATUS_BADREQUEST,
2318 "osrfMethodException",
2320 "Cannot INSERT readonly class"
2322 osrfAppRespondComplete( ctx, NULL );
2326 // Set the last_xact_id
2327 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2329 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2330 trans_id, target->classname, index);
2331 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2334 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2336 dbhandle = writehandle;
2338 osrfHash* fields = osrfHashGet( meta, "fields" );
2339 char* pkey = osrfHashGet( meta, "primarykey" );
2340 char* seq = osrfHashGet( meta, "sequence" );
2342 growing_buffer* table_buf = buffer_init( 128 );
2343 growing_buffer* col_buf = buffer_init( 128 );
2344 growing_buffer* val_buf = buffer_init( 128 );
2346 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2347 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2348 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2349 buffer_add( val_buf,"VALUES (" );
2353 osrfHash* field = NULL;
2354 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2355 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2357 const char* field_name = osrfHashIteratorKey( field_itr );
2359 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2362 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2365 if( field_object && field_object->classname ) {
2366 value = oilsFMGetString(
2368 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2370 } else if( field_object && JSON_BOOL == field_object->type ) {
2371 if( jsonBoolIsTrue( field_object ) )
2372 value = strdup( "t" );
2374 value = strdup( "f" );
2376 value = jsonObjectToSimpleString( field_object );
2382 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2383 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2386 buffer_add( col_buf, field_name );
2388 if( !field_object || field_object->type == JSON_NULL ) {
2389 buffer_add( val_buf, "DEFAULT" );
2391 } else if( !strcmp( get_primitive( field ), "number" )) {
2392 const char* numtype = get_datatype( field );
2393 if( !strcmp( numtype, "INT8" )) {
2394 buffer_fadd( val_buf, "%lld", atoll( value ));
2396 } else if( !strcmp( numtype, "INT" )) {
2397 buffer_fadd( val_buf, "%d", atoi( value ));
2399 } else if( !strcmp( numtype, "NUMERIC" )) {
2400 buffer_fadd( val_buf, "%f", atof( value ));
2403 if( dbi_conn_quote_string( writehandle, &value )) {
2404 OSRF_BUFFER_ADD( val_buf, value );
2407 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2408 osrfAppSessionStatus(
2410 OSRF_STATUS_INTERNALSERVERERROR,
2411 "osrfMethodException",
2413 "Error quoting string -- please see the error log for more details"
2416 buffer_free( table_buf );
2417 buffer_free( col_buf );
2418 buffer_free( val_buf );
2419 osrfAppRespondComplete( ctx, NULL );
2427 osrfHashIteratorFree( field_itr );
2429 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2430 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2432 char* table_str = buffer_release( table_buf );
2433 char* col_str = buffer_release( col_buf );
2434 char* val_str = buffer_release( val_buf );
2435 growing_buffer* sql = buffer_init( 128 );
2436 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2441 char* query = buffer_release( sql );
2443 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2445 jsonObject* obj = NULL;
2448 dbi_result result = dbi_conn_query( writehandle, query );
2450 obj = jsonNewObject( NULL );
2452 int errnum = dbi_conn_error( writehandle, &msg );
2455 "%s ERROR inserting %s object using query [%s]: %d %s",
2457 osrfHashGet(meta, "fieldmapper"),
2460 msg ? msg : "(No description available)"
2462 osrfAppSessionStatus(
2464 OSRF_STATUS_INTERNALSERVERERROR,
2465 "osrfMethodException",
2467 "INSERT error -- please see the error log for more details"
2469 if( !oilsIsDBConnected( writehandle ))
2470 osrfAppSessionPanic( ctx->session );
2473 dbi_result_free( result );
2475 char* id = oilsFMGetString( target, pkey );
2477 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2478 growing_buffer* _id = buffer_init( 10 );
2479 buffer_fadd( _id, "%lld", new_id );
2480 id = buffer_release( _id );
2483 // Find quietness specification, if present
2484 const char* quiet_str = NULL;
2486 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2488 quiet_str = jsonObjectGetString( quiet_obj );
2491 if( str_is_true( quiet_str )) { // if quietness is specified
2492 obj = jsonNewObject( id );
2496 // Fetch the row that we just inserted, so that we can return it to the client
2497 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2498 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2501 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2505 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2507 jsonObjectFree( list );
2508 jsonObjectFree( where_clause );
2515 osrfAppRespondComplete( ctx, obj );
2516 jsonObjectFree( obj );
2521 @brief Implement the retrieve method.
2522 @param ctx Pointer to the method context.
2523 @param err Pointer through which to return an error code.
2524 @return If successful, a pointer to the result to be returned to the client;
2527 From the method's class, fetch a row with a specified value in the primary key. This
2528 method relies on the database design convention that a primary key consists of a single
2532 - authkey (PCRUD only)
2533 - value of the primary key for the desired row, for building the WHERE clause
2534 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2536 Return to client: One row from the query.
2538 int doRetrieve( osrfMethodContext* ctx ) {
2539 if(osrfMethodVerifyContext( ctx )) {
2540 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2545 timeout_needs_resetting = 1;
2550 if( enforce_pcrud ) {
2555 // Get the class metadata
2556 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2558 // Get the value of the primary key, from a method parameter
2559 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2563 "%s retrieving %s object with primary key value of %s",
2565 osrfHashGet( class_def, "fieldmapper" ),
2566 jsonObjectGetString( id_obj )
2569 // Build a WHERE clause based on the key value
2570 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2573 osrfHashGet( class_def, "primarykey" ), // name of key column
2574 jsonObjectClone( id_obj ) // value of key column
2577 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2581 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2583 jsonObjectFree( where_clause );
2585 osrfAppRespondComplete( ctx, NULL );
2589 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2590 jsonObjectFree( list );
2592 if( enforce_pcrud ) {
2593 // no result, skip this entirely
2594 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2595 jsonObjectFree( obj );
2597 growing_buffer* msg = buffer_init( 128 );
2598 OSRF_BUFFER_ADD( msg, modulename );
2599 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2601 char* m = buffer_release( msg );
2602 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2606 osrfAppRespondComplete( ctx, NULL );
2611 // doFieldmapperSearch() now does the responding for us
2612 //osrfAppRespondComplete( ctx, obj );
2613 osrfAppRespondComplete( ctx, NULL );
2615 jsonObjectFree( obj );
2620 @brief Translate a numeric value to a string representation for the database.
2621 @param field Pointer to the IDL field definition.
2622 @param value Pointer to a jsonObject holding the value of a field.
2623 @return Pointer to a newly allocated string.
2625 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2626 its contents are numeric. A non-numeric string is likely to result in invalid SQL.
2628 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2630 The calling code is responsible for freeing the resulting string by calling free().
2632 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2633 growing_buffer* val_buf = buffer_init( 32 );
2635 // If the value is a number and the DB field is numeric, no quotes needed
2636 if( value->type == JSON_NUMBER && !strcmp( get_primitive( field ), "number") ) {
2637 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2639 // Presumably this was really intended to be a string, so quote it
2640 char* str = jsonObjectToSimpleString( value );
2641 if( dbi_conn_quote_string( dbhandle, &str )) {
2642 OSRF_BUFFER_ADD( val_buf, str );
2645 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2647 buffer_free( val_buf );
2652 return buffer_release( val_buf );
2655 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2656 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2657 growing_buffer* sql_buf = buffer_init( 32 );
2663 osrfHashGet( field, "name" )
2667 buffer_add( sql_buf, "IN (" );
2668 } else if( !strcasecmp( op,"not in" )) {
2669 buffer_add( sql_buf, "NOT IN (" );
2671 buffer_add( sql_buf, "IN (" );
2674 if( node->type == JSON_HASH ) {
2675 // subquery predicate
2676 char* subpred = buildQuery( ctx, node, SUBSELECT );
2678 buffer_free( sql_buf );
2682 buffer_add( sql_buf, subpred );
2685 } else if( node->type == JSON_ARRAY ) {
2686 // literal value list
2687 int in_item_index = 0;
2688 int in_item_first = 1;
2689 const jsonObject* in_item;
2690 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2695 buffer_add( sql_buf, ", " );
2698 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2699 osrfLogError( OSRF_LOG_MARK,
2700 "%s: Expected string or number within IN list; found %s",
2701 modulename, json_type( in_item->type ) );
2702 buffer_free( sql_buf );
2706 // Append the literal value -- quoted if not a number
2707 if( JSON_NUMBER == in_item->type ) {
2708 char* val = jsonNumberToDBString( field, in_item );
2709 OSRF_BUFFER_ADD( sql_buf, val );
2712 } else if( !strcmp( get_primitive( field ), "number" )) {
2713 char* val = jsonNumberToDBString( field, in_item );
2714 OSRF_BUFFER_ADD( sql_buf, val );
2718 char* key_string = jsonObjectToSimpleString( in_item );
2719 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2720 OSRF_BUFFER_ADD( sql_buf, key_string );
2723 osrfLogError( OSRF_LOG_MARK,
2724 "%s: Error quoting key string [%s]", modulename, key_string );
2726 buffer_free( sql_buf );
2732 if( in_item_first ) {
2733 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2734 buffer_free( sql_buf );
2738 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2739 modulename, json_type( node->type ));
2740 buffer_free( sql_buf );
2744 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2746 return buffer_release( sql_buf );
2749 // Receive a JSON_ARRAY representing a function call. The first
2750 // entry in the array is the function name. The rest are parameters.
2751 static char* searchValueTransform( const jsonObject* array ) {
2753 if( array->size < 1 ) {
2754 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2758 // Get the function name
2759 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2760 if( func_item->type != JSON_STRING ) {
2761 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2762 modulename, json_type( func_item->type ));
2766 growing_buffer* sql_buf = buffer_init( 32 );
2768 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2769 OSRF_BUFFER_ADD( sql_buf, "( " );
2771 // Get the parameters
2772 int func_item_index = 1; // We already grabbed the zeroth entry
2773 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2775 // Add a separator comma, if we need one
2776 if( func_item_index > 2 )
2777 buffer_add( sql_buf, ", " );
2779 // Add the current parameter
2780 if( func_item->type == JSON_NULL ) {
2781 buffer_add( sql_buf, "NULL" );
2783 if( func_item->type == JSON_BOOL ) {
2784 if( jsonBoolIsTrue(func_item) ) {
2785 buffer_add( sql_buf, "TRUE" );
2787 buffer_add( sql_buf, "FALSE" );
2790 char* val = jsonObjectToSimpleString( func_item );
2791 if( dbi_conn_quote_string( dbhandle, &val )) {
2792 OSRF_BUFFER_ADD( sql_buf, val );
2795 osrfLogError( OSRF_LOG_MARK,
2796 "%s: Error quoting key string [%s]", modulename, val );
2797 buffer_free( sql_buf );
2805 buffer_add( sql_buf, " )" );
2807 return buffer_release( sql_buf );
2810 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2811 const jsonObject* node, const char* op ) {
2813 if( ! is_good_operator( op ) ) {
2814 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2818 char* val = searchValueTransform( node );
2822 const char* right_percent = "";
2823 const char* real_op = op;
2825 if( !strcasecmp( op, "startwith") ) {
2827 right_percent = "|| '%'";
2830 growing_buffer* sql_buf = buffer_init( 32 );
2833 "\"%s\".%s %s %s%s",
2835 osrfHashGet( field, "name" ),
2843 return buffer_release( sql_buf );
2846 // class_alias is a class name or other table alias
2847 // field is a field definition as stored in the IDL
2848 // node comes from the method parameter, and may represent an entry in the SELECT list
2849 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2850 const jsonObject* node ) {
2851 growing_buffer* sql_buf = buffer_init( 32 );
2853 const char* field_transform = jsonObjectGetString(
2854 jsonObjectGetKeyConst( node, "transform" ) );
2855 const char* transform_subcolumn = jsonObjectGetString(
2856 jsonObjectGetKeyConst( node, "result_field" ) );
2858 if( transform_subcolumn ) {
2859 if( ! is_identifier( transform_subcolumn ) ) {
2860 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2861 modulename, transform_subcolumn );
2862 buffer_free( sql_buf );
2865 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2868 if( field_transform ) {
2870 if( ! is_identifier( field_transform ) ) {
2871 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2872 modulename, field_transform );
2873 buffer_free( sql_buf );
2877 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2878 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2879 field_transform, class_alias, osrfHashGet( field, "name" ));
2881 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2882 field_transform, class_alias, osrfHashGet( field, "name" ));
2885 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2888 if( array->type != JSON_ARRAY ) {
2889 osrfLogError( OSRF_LOG_MARK,
2890 "%s: Expected JSON_ARRAY for function params; found %s",
2891 modulename, json_type( array->type ) );
2892 buffer_free( sql_buf );
2895 int func_item_index = 0;
2896 jsonObject* func_item;
2897 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2899 char* val = jsonObjectToSimpleString( func_item );
2902 buffer_add( sql_buf, ",NULL" );
2903 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2904 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2905 OSRF_BUFFER_ADD( sql_buf, val );
2907 osrfLogError( OSRF_LOG_MARK,
2908 "%s: Error quoting key string [%s]", modulename, val );
2910 buffer_free( sql_buf );
2917 buffer_add( sql_buf, " )" );
2920 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2923 if( transform_subcolumn )
2924 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2926 return buffer_release( sql_buf );
2929 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2930 const jsonObject* node, const char* op ) {
2932 if( ! is_good_operator( op ) ) {
2933 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2937 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2938 if( ! field_transform )
2941 int extra_parens = 0; // boolean
2943 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2945 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2947 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2949 free( field_transform );
2953 } else if( value_obj->type == JSON_ARRAY ) {
2954 value = searchValueTransform( value_obj );
2956 osrfLogError( OSRF_LOG_MARK,
2957 "%s: Error building value transform for field transform", modulename );
2958 free( field_transform );
2961 } else if( value_obj->type == JSON_HASH ) {
2962 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2964 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2966 free( field_transform );
2970 } else if( value_obj->type == JSON_NUMBER ) {
2971 value = jsonNumberToDBString( field, value_obj );
2972 } else if( value_obj->type == JSON_NULL ) {
2973 osrfLogError( OSRF_LOG_MARK,
2974 "%s: Error building predicate for field transform: null value", modulename );
2975 free( field_transform );
2977 } else if( value_obj->type == JSON_BOOL ) {
2978 osrfLogError( OSRF_LOG_MARK,
2979 "%s: Error building predicate for field transform: boolean value", modulename );
2980 free( field_transform );
2983 if( !strcmp( get_primitive( field ), "number") ) {
2984 value = jsonNumberToDBString( field, value_obj );
2986 value = jsonObjectToSimpleString( value_obj );
2987 if( !dbi_conn_quote_string( dbhandle, &value )) {
2988 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2989 modulename, value );
2991 free( field_transform );
2997 const char* left_parens = "";
2998 const char* right_parens = "";
3000 if( extra_parens ) {
3005 const char* right_percent = "";
3006 const char* real_op = op;
3008 if( !strcasecmp( op, "startwith") ) {
3010 right_percent = "|| '%'";
3013 growing_buffer* sql_buf = buffer_init( 32 );
3017 "%s%s %s %s %s%s %s%s",
3029 free( field_transform );
3031 return buffer_release( sql_buf );
3034 static char* searchSimplePredicate( const char* op, const char* class_alias,
3035 osrfHash* field, const jsonObject* node ) {
3037 if( ! is_good_operator( op ) ) {
3038 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
3044 // Get the value to which we are comparing the specified column
3045 if( node->type != JSON_NULL ) {
3046 if( node->type == JSON_NUMBER ) {
3047 val = jsonNumberToDBString( field, node );
3048 } else if( !strcmp( get_primitive( field ), "number" ) ) {
3049 val = jsonNumberToDBString( field, node );
3051 val = jsonObjectToSimpleString( node );
3056 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
3057 // Value is not numeric; enclose it in quotes
3058 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
3059 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3066 // Compare to a null value
3067 val = strdup( "NULL" );
3068 if( strcmp( op, "=" ))
3074 const char* right_percent = "";
3075 const char* real_op = op;
3077 if( !strcasecmp( op, "startwith") ) {
3079 right_percent = "|| '%'";
3082 growing_buffer* sql_buf = buffer_init( 32 );
3083 buffer_fadd( sql_buf, "\"%s\".%s %s %s%s", class_alias, osrfHashGet(field, "name"), real_op, val, right_percent );
3084 char* pred = buffer_release( sql_buf );
3091 static char* searchBETWEENPredicate( const char* class_alias,
3092 osrfHash* field, const jsonObject* node ) {
3094 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
3095 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
3097 if( NULL == y_node ) {
3098 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
3101 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
3102 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
3109 if( !strcmp( get_primitive( field ), "number") ) {
3110 x_string = jsonNumberToDBString( field, x_node );
3111 y_string = jsonNumberToDBString( field, y_node );
3114 x_string = jsonObjectToSimpleString( x_node );
3115 y_string = jsonObjectToSimpleString( y_node );
3116 if( !(dbi_conn_quote_string( dbhandle, &x_string )
3117 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
3118 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
3119 modulename, x_string, y_string );
3126 growing_buffer* sql_buf = buffer_init( 32 );
3127 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
3128 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
3132 return buffer_release( sql_buf );
3135 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
3136 jsonObject* node, osrfMethodContext* ctx ) {
3139 if( node->type == JSON_ARRAY ) { // equality IN search
3140 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
3141 } else if( node->type == JSON_HASH ) { // other search
3142 jsonIterator* pred_itr = jsonNewIterator( node );
3143 if( !jsonIteratorHasNext( pred_itr ) ) {
3144 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
3145 modulename, osrfHashGet(field, "name" ));
3147 jsonObject* pred_node = jsonIteratorNext( pred_itr );
3149 // Verify that there are no additional predicates
3150 if( jsonIteratorHasNext( pred_itr ) ) {
3151 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
3152 modulename, osrfHashGet(field, "name" ));
3153 } else if( !(strcasecmp( pred_itr->key,"between" )) )
3154 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
3155 else if( !(strcasecmp( pred_itr->key,"in" ))
3156 || !(strcasecmp( pred_itr->key,"not in" )) )
3157 pred = searchINPredicate(
3158 class_info->alias, field, pred_node, pred_itr->key, ctx );
3159 else if( pred_node->type == JSON_ARRAY )
3160 pred = searchFunctionPredicate(
3161 class_info->alias, field, pred_node, pred_itr->key );
3162 else if( pred_node->type == JSON_HASH )
3163 pred = searchFieldTransformPredicate(
3164 class_info, field, pred_node, pred_itr->key );
3166 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
3168 jsonIteratorFree( pred_itr );
3170 } else if( node->type == JSON_NULL ) { // IS NULL search
3171 growing_buffer* _p = buffer_init( 64 );
3174 "\"%s\".%s IS NULL",
3176 osrfHashGet( field, "name" )
3178 pred = buffer_release( _p );
3179 } else { // equality search
3180 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3199 field : call_number,
3215 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3217 const jsonObject* working_hash;
3218 jsonObject* freeable_hash = NULL;
3220 if( join_hash->type == JSON_HASH ) {
3221 working_hash = join_hash;
3222 } else if( join_hash->type == JSON_STRING ) {
3223 // turn it into a JSON_HASH by creating a wrapper
3224 // around a copy of the original
3225 const char* _tmp = jsonObjectGetString( join_hash );
3226 freeable_hash = jsonNewObjectType( JSON_HASH );
3227 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3228 working_hash = freeable_hash;
3232 "%s: JOIN failed; expected JSON object type not found",
3238 growing_buffer* join_buf = buffer_init( 128 );
3239 const char* leftclass = left_info->class_name;
3241 jsonObject* snode = NULL;
3242 jsonIterator* search_itr = jsonNewIterator( working_hash );
3244 while ( (snode = jsonIteratorNext( search_itr )) ) {
3245 const char* right_alias = search_itr->key;
3247 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3249 class = right_alias;
3251 const ClassInfo* right_info = add_joined_class( right_alias, class );
3255 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3259 jsonIteratorFree( search_itr );
3260 buffer_free( join_buf );
3262 jsonObjectFree( freeable_hash );
3265 osrfHash* links = right_info->links;
3266 const char* table = right_info->source_def;
3268 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3269 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3271 if( field && !fkey ) {
3272 // Look up the corresponding join column in the IDL.
3273 // The link must be defined in the child table,
3274 // and point to the right parent table.
3275 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3276 const char* reltype = NULL;
3277 const char* other_class = NULL;
3278 reltype = osrfHashGet( idl_link, "reltype" );
3279 if( reltype && strcmp( reltype, "has_many" ) )
3280 other_class = osrfHashGet( idl_link, "class" );
3281 if( other_class && !strcmp( other_class, leftclass ) )
3282 fkey = osrfHashGet( idl_link, "key" );
3286 "%s: JOIN failed. No link defined from %s.%s to %s",
3292 buffer_free( join_buf );
3294 jsonObjectFree( freeable_hash );
3295 jsonIteratorFree( search_itr );
3299 } else if( !field && fkey ) {
3300 // Look up the corresponding join column in the IDL.
3301 // The link must be defined in the child table,
3302 // and point to the right parent table.
3303 osrfHash* left_links = left_info->links;
3304 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
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, class ) )
3311 field = 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 osrfHash* left_links = left_info->links;
3331 // For each link defined for the left class:
3332 // see if the link references the joined class
3333 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3334 osrfHash* curr_link = NULL;
3335 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3336 const char* other_class = osrfHashGet( curr_link, "class" );
3337 if( other_class && !strcmp( other_class, class ) ) {
3339 // In the IDL, the parent class doesn't always know then names of the child
3340 // columns that are pointing to it, so don't use that end of the link
3341 const char* reltype = osrfHashGet( curr_link, "reltype" );
3342 if( reltype && strcmp( reltype, "has_many" ) ) {
3343 // Found a link between the classes
3344 fkey = osrfHashIteratorKey( itr );
3345 field = osrfHashGet( curr_link, "key" );
3350 osrfHashIteratorFree( itr );
3352 if( !field || !fkey ) {
3353 // Do another such search, with the classes reversed
3355 // For each link defined for the joined class:
3356 // see if the link references the left class
3357 osrfHashIterator* itr = osrfNewHashIterator( links );
3358 osrfHash* curr_link = NULL;
3359 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3360 const char* other_class = osrfHashGet( curr_link, "class" );
3361 if( other_class && !strcmp( other_class, leftclass ) ) {
3363 // In the IDL, the parent class doesn't know then names of the child
3364 // columns that are pointing to it, so don't use that end of the link
3365 const char* reltype = osrfHashGet( curr_link, "reltype" );
3366 if( reltype && strcmp( reltype, "has_many" ) ) {
3367 // Found a link between the classes
3368 field = osrfHashIteratorKey( itr );
3369 fkey = osrfHashGet( curr_link, "key" );
3374 osrfHashIteratorFree( itr );
3377 if( !field || !fkey ) {
3380 "%s: JOIN failed. No link defined between %s and %s",
3385 buffer_free( join_buf );
3387 jsonObjectFree( freeable_hash );
3388 jsonIteratorFree( search_itr );
3393 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3395 if( !strcasecmp( type,"left" )) {
3396 buffer_add( join_buf, " LEFT JOIN" );
3397 } else if( !strcasecmp( type,"right" )) {
3398 buffer_add( join_buf, " RIGHT JOIN" );
3399 } else if( !strcasecmp( type,"full" )) {
3400 buffer_add( join_buf, " FULL JOIN" );
3402 buffer_add( join_buf, " INNER JOIN" );
3405 buffer_add( join_buf, " INNER JOIN" );
3408 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3409 table, right_alias, right_alias, field, left_info->alias, fkey );
3411 // Add any other join conditions as specified by "filter"
3412 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3414 const char* filter_op = jsonObjectGetString(
3415 jsonObjectGetKeyConst( snode, "filter_op" ) );
3416 if( filter_op && !strcasecmp( "or",filter_op )) {
3417 buffer_add( join_buf, " OR " );
3419 buffer_add( join_buf, " AND " );
3422 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3424 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3425 OSRF_BUFFER_ADD( join_buf, jpred );
3430 "%s: JOIN failed. Invalid conditional expression.",
3433 jsonIteratorFree( search_itr );
3434 buffer_free( join_buf );
3436 jsonObjectFree( freeable_hash );
3441 buffer_add( join_buf, " ) " );
3443 // Recursively add a nested join, if one is present
3444 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3446 char* jpred = searchJOIN( join_filter, right_info );
3448 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3449 OSRF_BUFFER_ADD( join_buf, jpred );
3452 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3453 jsonIteratorFree( search_itr );
3454 buffer_free( join_buf );
3456 jsonObjectFree( freeable_hash );
3463 jsonObjectFree( freeable_hash );
3464 jsonIteratorFree( search_itr );
3466 return buffer_release( join_buf );
3471 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3472 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3473 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3475 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3477 search_hash is the JSON expression of the conditions.
3478 meta is the class definition from the IDL, for the relevant table.
3479 opjoin_type indicates whether multiple conditions, if present, should be
3480 connected by AND or OR.
3481 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3482 to pass it to other functions -- and all they do with it is to use the session
3483 and request members to send error messages back to the client.
3487 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3488 int opjoin_type, osrfMethodContext* ctx ) {
3492 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3493 "opjoin_type = %d, ctx addr = %p",
3496 class_info->class_def,
3501 growing_buffer* sql_buf = buffer_init( 128 );
3503 jsonObject* node = NULL;
3506 if( search_hash->type == JSON_ARRAY ) {
3507 if( 0 == search_hash->size ) {
3510 "%s: Invalid predicate structure: empty JSON array",
3513 buffer_free( sql_buf );
3517 unsigned long i = 0;
3518 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3522 if( opjoin_type == OR_OP_JOIN )
3523 buffer_add( sql_buf, " OR " );
3525 buffer_add( sql_buf, " AND " );
3528 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3530 buffer_free( sql_buf );
3534 buffer_fadd( sql_buf, "( %s )", subpred );
3538 } else if( search_hash->type == JSON_HASH ) {
3539 osrfLogDebug( OSRF_LOG_MARK,
3540 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3541 jsonIterator* search_itr = jsonNewIterator( search_hash );
3542 if( !jsonIteratorHasNext( search_itr ) ) {
3545 "%s: Invalid predicate structure: empty JSON object",
3548 jsonIteratorFree( search_itr );
3549 buffer_free( sql_buf );
3553 while( (node = jsonIteratorNext( search_itr )) ) {
3558 if( opjoin_type == OR_OP_JOIN )
3559 buffer_add( sql_buf, " OR " );
3561 buffer_add( sql_buf, " AND " );
3564 if( '+' == search_itr->key[ 0 ] ) {
3566 // This plus sign prefixes a class name or other table alias;
3567 // make sure the table alias is in scope
3568 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3569 if( ! alias_info ) {
3572 "%s: Invalid table alias \"%s\" in WHERE clause",
3576 jsonIteratorFree( search_itr );
3577 buffer_free( sql_buf );
3581 if( node->type == JSON_STRING ) {
3582 // It's the name of a column; make sure it belongs to the class
3583 const char* fieldname = jsonObjectGetString( node );
3584 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3587 "%s: Invalid column name \"%s\" in WHERE clause "
3588 "for table alias \"%s\"",
3593 jsonIteratorFree( search_itr );
3594 buffer_free( sql_buf );
3598 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3600 // It's something more complicated
3601 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3603 jsonIteratorFree( search_itr );
3604 buffer_free( sql_buf );
3608 buffer_fadd( sql_buf, "( %s )", subpred );
3611 } else if( '-' == search_itr->key[ 0 ] ) {
3612 if( !strcasecmp( "-or", search_itr->key )) {
3613 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3615 jsonIteratorFree( search_itr );
3616 buffer_free( sql_buf );
3620 buffer_fadd( sql_buf, "( %s )", subpred );
3622 } else if( !strcasecmp( "-and", search_itr->key )) {
3623 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3625 jsonIteratorFree( search_itr );
3626 buffer_free( sql_buf );
3630 buffer_fadd( sql_buf, "( %s )", subpred );
3632 } else if( !strcasecmp("-not",search_itr->key) ) {
3633 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3635 jsonIteratorFree( search_itr );
3636 buffer_free( sql_buf );
3640 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3642 } else if( !strcasecmp( "-exists", search_itr->key )) {
3643 char* subpred = buildQuery( ctx, node, SUBSELECT );
3645 jsonIteratorFree( search_itr );
3646 buffer_free( sql_buf );
3650 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3652 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3653 char* subpred = buildQuery( ctx, node, SUBSELECT );
3655 jsonIteratorFree( search_itr );
3656 buffer_free( sql_buf );
3660 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3662 } else { // Invalid "minus" operator
3665 "%s: Invalid operator \"%s\" in WHERE clause",
3669 jsonIteratorFree( search_itr );
3670 buffer_free( sql_buf );
3676 const char* class = class_info->class_name;
3677 osrfHash* fields = class_info->fields;
3678 osrfHash* field = osrfHashGet( fields, search_itr->key );
3681 const char* table = class_info->source_def;
3684 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3687 table ? table : "?",
3690 jsonIteratorFree( search_itr );
3691 buffer_free( sql_buf );
3695 char* subpred = searchPredicate( class_info, field, node, ctx );
3697 buffer_free( sql_buf );
3698 jsonIteratorFree( search_itr );
3702 buffer_add( sql_buf, subpred );
3706 jsonIteratorFree( search_itr );
3709 // ERROR ... only hash and array allowed at this level
3710 char* predicate_string = jsonObjectToJSON( search_hash );
3713 "%s: Invalid predicate structure: %s",
3717 buffer_free( sql_buf );
3718 free( predicate_string );
3722 return buffer_release( sql_buf );
3725 /* Build a JSON_ARRAY of field names for a given table alias
3727 static jsonObject* defaultSelectList( const char* table_alias ) {
3732 ClassInfo* class_info = search_all_alias( table_alias );
3733 if( ! class_info ) {
3736 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3743 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3744 osrfHash* field_def = NULL;
3745 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3746 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3747 const char* field_name = osrfHashIteratorKey( field_itr );
3748 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3749 jsonObjectPush( array, jsonNewObject( field_name ) );
3752 osrfHashIteratorFree( field_itr );
3757 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3758 // The jsonObject must be a JSON_HASH with an single entry for "union",
3759 // "intersect", or "except". The data associated with this key must be an
3760 // array of hashes, each hash being a query.
3761 // Also allowed but currently ignored: entries for "order_by" and "alias".
3762 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3764 if( ! combo || combo->type != JSON_HASH )
3765 return NULL; // should be impossible; validated by caller
3767 const jsonObject* query_array = NULL; // array of subordinate queries
3768 const char* op = NULL; // name of operator, e.g. UNION
3769 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3770 int op_count = 0; // for detecting conflicting operators
3771 int excepting = 0; // boolean
3772 int all = 0; // boolean
3773 jsonObject* order_obj = NULL;
3775 // Identify the elements in the hash
3776 jsonIterator* query_itr = jsonNewIterator( combo );
3777 jsonObject* curr_obj = NULL;
3778 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3779 if( ! strcmp( "union", query_itr->key ) ) {
3782 query_array = curr_obj;
3783 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3786 query_array = curr_obj;
3787 } else if( ! strcmp( "except", query_itr->key ) ) {
3791 query_array = curr_obj;
3792 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3795 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3798 order_obj = curr_obj;
3799 } else if( ! strcmp( "alias", query_itr->key ) ) {
3800 if( curr_obj->type != JSON_STRING ) {
3801 jsonIteratorFree( query_itr );
3804 alias = jsonObjectGetString( curr_obj );
3805 } else if( ! strcmp( "all", query_itr->key ) ) {
3806 if( obj_is_true( curr_obj ) )
3810 osrfAppSessionStatus(
3812 OSRF_STATUS_INTERNALSERVERERROR,
3813 "osrfMethodException",
3815 "Malformed query; unexpected entry in query object"
3819 "%s: Unexpected entry for \"%s\" in%squery",
3824 jsonIteratorFree( query_itr );
3828 jsonIteratorFree( query_itr );
3830 // More sanity checks
3831 if( ! query_array ) {
3833 osrfAppSessionStatus(
3835 OSRF_STATUS_INTERNALSERVERERROR,
3836 "osrfMethodException",
3838 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3842 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3845 return NULL; // should be impossible...
3846 } else if( op_count > 1 ) {
3848 osrfAppSessionStatus(
3850 OSRF_STATUS_INTERNALSERVERERROR,
3851 "osrfMethodException",
3853 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3857 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3861 } if( query_array->type != JSON_ARRAY ) {
3863 osrfAppSessionStatus(
3865 OSRF_STATUS_INTERNALSERVERERROR,
3866 "osrfMethodException",
3868 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3872 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3875 json_type( query_array->type )
3878 } if( query_array->size < 2 ) {
3880 osrfAppSessionStatus(
3882 OSRF_STATUS_INTERNALSERVERERROR,
3883 "osrfMethodException",
3885 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3889 "%s:%srequires multiple queries as operands",
3894 } else if( excepting && query_array->size > 2 ) {
3896 osrfAppSessionStatus(
3898 OSRF_STATUS_INTERNALSERVERERROR,
3899 "osrfMethodException",
3901 "EXCEPT operator has too many queries as operands"
3905 "%s:EXCEPT operator has too many queries as operands",
3909 } else if( order_obj && ! alias ) {
3911 osrfAppSessionStatus(
3913 OSRF_STATUS_INTERNALSERVERERROR,
3914 "osrfMethodException",
3916 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3920 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3926 // So far so good. Now build the SQL.
3927 growing_buffer* sql = buffer_init( 256 );
3929 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3930 // Add a layer of parentheses
3931 if( flags & SUBCOMBO )
3932 OSRF_BUFFER_ADD( sql, "( " );
3934 // Traverse the query array. Each entry should be a hash.
3935 int first = 1; // boolean
3937 jsonObject* query = NULL;
3938 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3939 if( query->type != JSON_HASH ) {
3941 osrfAppSessionStatus(
3943 OSRF_STATUS_INTERNALSERVERERROR,
3944 "osrfMethodException",
3946 "Malformed query under UNION, INTERSECT or EXCEPT"
3950 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3953 json_type( query->type )
3962 OSRF_BUFFER_ADD( sql, op );
3964 OSRF_BUFFER_ADD( sql, "ALL " );
3967 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3971 "%s: Error building query under%s",
3979 OSRF_BUFFER_ADD( sql, query_str );
3982 if( flags & SUBCOMBO )
3983 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3985 if( !(flags & SUBSELECT) )
3986 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3988 return buffer_release( sql );
3991 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3992 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3993 // or "except" to indicate the type of query.
3994 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3998 osrfAppSessionStatus(
4000 OSRF_STATUS_INTERNALSERVERERROR,
4001 "osrfMethodException",
4003 "Malformed query; no query object"
4005 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
4007 } else if( query->type != JSON_HASH ) {
4009 osrfAppSessionStatus(
4011 OSRF_STATUS_INTERNALSERVERERROR,
4012 "osrfMethodException",
4014 "Malformed query object"
4018 "%s: Query object is %s instead of JSON_HASH",
4020 json_type( query->type )
4025 // Determine what kind of query it purports to be, and dispatch accordingly.
4026 if( jsonObjectGetKeyConst( query, "union" ) ||
4027 jsonObjectGetKeyConst( query, "intersect" ) ||
4028 jsonObjectGetKeyConst( query, "except" )) {
4029 return doCombo( ctx, query, flags );
4031 // It is presumably a SELECT query
4033 // Push a node onto the stack for the current query. Every level of
4034 // subquery gets its own QueryFrame on the Stack.
4037 // Build an SQL SELECT statement
4040 jsonObjectGetKey( query, "select" ),
4041 jsonObjectGetKeyConst( query, "from" ),
4042 jsonObjectGetKeyConst( query, "where" ),
4043 jsonObjectGetKeyConst( query, "having" ),
4044 jsonObjectGetKeyConst( query, "order_by" ),
4045 jsonObjectGetKeyConst( query, "limit" ),
4046 jsonObjectGetKeyConst( query, "offset" ),
4055 /* method context */ osrfMethodContext* ctx,
4057 /* SELECT */ jsonObject* selhash,
4058 /* FROM */ const jsonObject* join_hash,
4059 /* WHERE */ const jsonObject* search_hash,
4060 /* HAVING */ const jsonObject* having_hash,
4061 /* ORDER BY */ const jsonObject* order_hash,
4062 /* LIMIT */ const jsonObject* limit,
4063 /* OFFSET */ const jsonObject* offset,
4064 /* flags */ int flags
4066 const char* locale = osrf_message_get_last_locale();
4068 // general tmp objects
4069 const jsonObject* tmp_const;
4070 jsonObject* selclass = NULL;
4071 jsonObject* snode = NULL;
4072 jsonObject* onode = NULL;
4074 char* string = NULL;
4075 int from_function = 0;
4080 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
4082 // punt if there's no FROM clause
4083 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
4086 "%s: FROM clause is missing or empty",
4090 osrfAppSessionStatus(
4092 OSRF_STATUS_INTERNALSERVERERROR,
4093 "osrfMethodException",
4095 "FROM clause is missing or empty in JSON query"
4100 // the core search class
4101 const char* core_class = NULL;
4103 // get the core class -- the only key of the top level FROM clause, or a string
4104 if( join_hash->type == JSON_HASH ) {
4105 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
4106 snode = jsonIteratorNext( tmp_itr );
4108 // Populate the current QueryFrame with information
4109 // about the core class
4110 if( add_query_core( NULL, tmp_itr->key ) ) {
4112 osrfAppSessionStatus(
4114 OSRF_STATUS_INTERNALSERVERERROR,
4115 "osrfMethodException",
4117 "Unable to look up core class"
4121 core_class = curr_query->core.class_name;
4124 jsonObject* extra = jsonIteratorNext( tmp_itr );
4126 jsonIteratorFree( tmp_itr );
4129 // There shouldn't be more than one entry in join_hash
4133 "%s: Malformed FROM clause: extra entry in JSON_HASH",
4137 osrfAppSessionStatus(
4139 OSRF_STATUS_INTERNALSERVERERROR,
4140 "osrfMethodException",
4142 "Malformed FROM clause in JSON query"
4144 return NULL; // Malformed join_hash; extra entry
4146 } else if( join_hash->type == JSON_ARRAY ) {
4147 // We're selecting from a function, not from a table
4149 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
4152 } else if( join_hash->type == JSON_STRING ) {
4153 // Populate the current QueryFrame with information
4154 // about the core class
4155 core_class = jsonObjectGetString( join_hash );
4157 if( add_query_core( NULL, core_class ) ) {
4159 osrfAppSessionStatus(
4161 OSRF_STATUS_INTERNALSERVERERROR,
4162 "osrfMethodException",
4164 "Unable to look up core class"
4172 "%s: FROM clause is unexpected JSON type: %s",
4174 json_type( join_hash->type )
4177 osrfAppSessionStatus(
4179 OSRF_STATUS_INTERNALSERVERERROR,
4180 "osrfMethodException",
4182 "Ill-formed FROM clause in JSON query"
4187 // Build the join clause, if any, while filling out the list
4188 // of joined classes in the current QueryFrame.
4189 char* join_clause = NULL;
4190 if( join_hash && ! from_function ) {
4192 join_clause = searchJOIN( join_hash, &curr_query->core );
4193 if( ! join_clause ) {
4195 osrfAppSessionStatus(
4197 OSRF_STATUS_INTERNALSERVERERROR,
4198 "osrfMethodException",
4200 "Unable to construct JOIN clause(s)"
4206 // For in case we don't get a select list
4207 jsonObject* defaultselhash = NULL;
4209 // if there is no select list, build a default select list ...
4210 if( !selhash && !from_function ) {
4211 jsonObject* default_list = defaultSelectList( core_class );
4212 if( ! default_list ) {
4214 osrfAppSessionStatus(
4216 OSRF_STATUS_INTERNALSERVERERROR,
4217 "osrfMethodException",
4219 "Unable to build default SELECT clause in JSON query"
4221 free( join_clause );
4226 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4227 jsonObjectSetKey( selhash, core_class, default_list );
4230 // The SELECT clause can be encoded only by a hash
4231 if( !from_function && selhash->type != JSON_HASH ) {
4234 "%s: Expected JSON_HASH for SELECT clause; found %s",
4236 json_type( selhash->type )
4240 osrfAppSessionStatus(
4242 OSRF_STATUS_INTERNALSERVERERROR,
4243 "osrfMethodException",
4245 "Malformed SELECT clause in JSON query"
4247 free( join_clause );
4251 // If you see a null or wild card specifier for the core class, or an
4252 // empty array, replace it with a default SELECT list
4253 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4255 int default_needed = 0; // boolean
4256 if( JSON_STRING == tmp_const->type
4257 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4259 else if( JSON_NULL == tmp_const->type )
4262 if( default_needed ) {
4263 // Build a default SELECT list
4264 jsonObject* default_list = defaultSelectList( core_class );
4265 if( ! default_list ) {
4267 osrfAppSessionStatus(
4269 OSRF_STATUS_INTERNALSERVERERROR,
4270 "osrfMethodException",
4272 "Can't build default SELECT clause in JSON query"
4274 free( join_clause );
4279 jsonObjectSetKey( selhash, core_class, default_list );
4283 // temp buffers for the SELECT list and GROUP BY clause
4284 growing_buffer* select_buf = buffer_init( 128 );
4285 growing_buffer* group_buf = buffer_init( 128 );
4287 int aggregate_found = 0; // boolean
4289 // Build a select list
4290 if( from_function ) // From a function we select everything
4291 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4294 // Build the SELECT list as SQL
4298 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4299 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4301 const char* cname = selclass_itr->key;
4303 // Make sure the target relation is in the FROM clause.
4305 // At this point join_hash is a step down from the join_hash we
4306 // received as a parameter. If the original was a JSON_STRING,
4307 // then json_hash is now NULL. If the original was a JSON_HASH,
4308 // then json_hash is now the first (and only) entry in it,
4309 // denoting the core class. We've already excluded the
4310 // possibility that the original was a JSON_ARRAY, because in
4311 // that case from_function would be non-NULL, and we wouldn't
4314 // If the current table alias isn't in scope, bail out
4315 ClassInfo* class_info = search_alias( cname );
4316 if( ! class_info ) {
4319 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4324 osrfAppSessionStatus(
4326 OSRF_STATUS_INTERNALSERVERERROR,
4327 "osrfMethodException",
4329 "Selected class not in FROM clause in JSON query"
4331 jsonIteratorFree( selclass_itr );
4332 buffer_free( select_buf );
4333 buffer_free( group_buf );
4334 if( defaultselhash )
4335 jsonObjectFree( defaultselhash );
4336 free( join_clause );
4340 if( selclass->type != JSON_ARRAY ) {
4343 "%s: Malformed SELECT list for class \"%s\"; not an array",
4348 osrfAppSessionStatus(
4350 OSRF_STATUS_INTERNALSERVERERROR,
4351 "osrfMethodException",
4353 "Selected class not in FROM clause in JSON query"
4356 jsonIteratorFree( selclass_itr );
4357 buffer_free( select_buf );
4358 buffer_free( group_buf );
4359 if( defaultselhash )
4360 jsonObjectFree( defaultselhash );
4361 free( join_clause );
4365 // Look up some attributes of the current class
4366 osrfHash* idlClass = class_info->class_def;
4367 osrfHash* class_field_set = class_info->fields;
4368 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4369 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4371 if( 0 == selclass->size ) {
4374 "%s: No columns selected from \"%s\"",
4380 // stitch together the column list for the current table alias...
4381 unsigned long field_idx = 0;
4382 jsonObject* selfield = NULL;
4383 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4385 // If we need a separator comma, add one
4389 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4392 // if the field specification is a string, add it to the list
4393 if( selfield->type == JSON_STRING ) {
4395 // Look up the field in the IDL
4396 const char* col_name = jsonObjectGetString( selfield );
4397 osrfHash* field_def = NULL;
4399 if (!osrfStringArrayContains(
4401 osrfHashGet( class_field_set, col_name ),
4402 "suppress_controller"),
4405 field_def = osrfHashGet( class_field_set, col_name );
4408 // No such field in current class
4411 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4417 osrfAppSessionStatus(
4419 OSRF_STATUS_INTERNALSERVERERROR,
4420 "osrfMethodException",
4422 "Selected column not defined in JSON query"
4424 jsonIteratorFree( selclass_itr );
4425 buffer_free( select_buf );
4426 buffer_free( group_buf );
4427 if( defaultselhash )
4428 jsonObjectFree( defaultselhash );
4429 free( join_clause );
4431 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4432 // Virtual field not allowed
4435 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4441 osrfAppSessionStatus(
4443 OSRF_STATUS_INTERNALSERVERERROR,
4444 "osrfMethodException",
4446 "Selected column may not be virtual in JSON query"
4448 jsonIteratorFree( selclass_itr );
4449 buffer_free( select_buf );
4450 buffer_free( group_buf );
4451 if( defaultselhash )
4452 jsonObjectFree( defaultselhash );
4453 free( join_clause );
4459 if( flags & DISABLE_I18N )
4462 i18n = osrfHashGet( field_def, "i18n" );
4464 if( str_is_true( i18n ) ) {
4465 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4466 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4467 class_tname, cname, col_name, class_pkey,
4468 cname, class_pkey, locale, col_name );
4470 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4471 cname, col_name, col_name );
4474 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4475 cname, col_name, col_name );
4478 // ... but it could be an object, in which case we check for a Field Transform
4479 } else if( selfield->type == JSON_HASH ) {
4481 const char* col_name = jsonObjectGetString(
4482 jsonObjectGetKeyConst( selfield, "column" ) );
4484 // Get the field definition from the IDL
4485 osrfHash* field_def = NULL;
4486 if (!osrfStringArrayContains(
4488 osrfHashGet( class_field_set, col_name ),
4489 "suppress_controller"),
4492 field_def = osrfHashGet( class_field_set, col_name );
4496 // No such field in current class
4499 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4505 osrfAppSessionStatus(
4507 OSRF_STATUS_INTERNALSERVERERROR,
4508 "osrfMethodException",
4510 "Selected column is not defined in JSON query"
4512 jsonIteratorFree( selclass_itr );
4513 buffer_free( select_buf );
4514 buffer_free( group_buf );
4515 if( defaultselhash )
4516 jsonObjectFree( defaultselhash );
4517 free( join_clause );
4519 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4520 // No such field in current class
4523 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4529 osrfAppSessionStatus(
4531 OSRF_STATUS_INTERNALSERVERERROR,
4532 "osrfMethodException",
4534 "Selected column is virtual in JSON query"
4536 jsonIteratorFree( selclass_itr );
4537 buffer_free( select_buf );
4538 buffer_free( group_buf );
4539 if( defaultselhash )
4540 jsonObjectFree( defaultselhash );
4541 free( join_clause );
4545 // Decide what to use as a column alias
4547 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4548 _alias = jsonObjectGetString( tmp_const );
4549 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4550 _alias = jsonObjectGetString( tmp_const );
4551 } else { // Use field name as the alias
4555 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4556 char* transform_str = searchFieldTransform(
4557 class_info->alias, field_def, selfield );
4558 if( transform_str ) {
4559 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4560 free( transform_str );
4563 osrfAppSessionStatus(
4565 OSRF_STATUS_INTERNALSERVERERROR,
4566 "osrfMethodException",
4568 "Unable to generate transform function in JSON query"
4570 jsonIteratorFree( selclass_itr );
4571 buffer_free( select_buf );
4572 buffer_free( group_buf );
4573 if( defaultselhash )
4574 jsonObjectFree( defaultselhash );
4575 free( join_clause );
4582 if( flags & DISABLE_I18N )
4585 i18n = osrfHashGet( field_def, "i18n" );
4587 if( str_is_true( i18n ) ) {
4588 buffer_fadd( select_buf,
4589 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4590 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4591 class_tname, cname, col_name, class_pkey, cname,
4592 class_pkey, locale, _alias );
4594 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4595 cname, col_name, _alias );
4598 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4599 cname, col_name, _alias );
4606 "%s: Selected item is unexpected JSON type: %s",
4608 json_type( selfield->type )
4611 osrfAppSessionStatus(
4613 OSRF_STATUS_INTERNALSERVERERROR,
4614 "osrfMethodException",
4616 "Ill-formed SELECT item in JSON query"
4618 jsonIteratorFree( selclass_itr );
4619 buffer_free( select_buf );
4620 buffer_free( group_buf );
4621 if( defaultselhash )
4622 jsonObjectFree( defaultselhash );
4623 free( join_clause );
4627 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4628 if( obj_is_true( agg_obj ) )
4629 aggregate_found = 1;
4631 // Append a comma (except for the first one)
4632 // and add the column to a GROUP BY clause
4636 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4638 buffer_fadd( group_buf, " %d", sel_pos );
4642 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4644 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( selfield, "aggregate");
4645 if ( ! obj_is_true( aggregate_obj ) ) {
4649 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4652 buffer_fadd(group_buf, " %d", sel_pos);
4655 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4659 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4662 _column = searchFieldTransform(class_info->alias, field, selfield);
4663 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4664 OSRF_BUFFER_ADD(group_buf, _column);
4665 _column = searchFieldTransform(class_info->alias, field, selfield);
4672 } // end while -- iterating across SELECT columns
4674 } // end while -- iterating across classes
4676 jsonIteratorFree( selclass_itr );
4679 char* col_list = buffer_release( select_buf );
4681 // Make sure the SELECT list isn't empty. This can happen, for example,
4682 // if we try to build a default SELECT clause from a non-core table.
4685 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4687 osrfAppSessionStatus(
4689 OSRF_STATUS_INTERNALSERVERERROR,
4690 "osrfMethodException",
4692 "SELECT list is empty"
4695 buffer_free( group_buf );
4696 if( defaultselhash )
4697 jsonObjectFree( defaultselhash );
4698 free( join_clause );
4704 table = searchValueTransform( join_hash );
4706 table = strdup( curr_query->core.source_def );
4710 osrfAppSessionStatus(
4712 OSRF_STATUS_INTERNALSERVERERROR,
4713 "osrfMethodException",
4715 "Unable to identify table for core class"
4718 buffer_free( group_buf );
4719 if( defaultselhash )
4720 jsonObjectFree( defaultselhash );
4721 free( join_clause );
4725 // Put it all together
4726 growing_buffer* sql_buf = buffer_init( 128 );
4727 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4731 // Append the join clause, if any
4733 buffer_add(sql_buf, join_clause );
4734 free( join_clause );
4737 char* order_by_list = NULL;
4738 char* having_buf = NULL;
4740 if( !from_function ) {
4742 // Build a WHERE clause, if there is one
4744 buffer_add( sql_buf, " WHERE " );
4746 // and it's on the WHERE clause
4747 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4750 osrfAppSessionStatus(
4752 OSRF_STATUS_INTERNALSERVERERROR,
4753 "osrfMethodException",
4755 "Severe query error in WHERE predicate -- see error log for more details"
4758 buffer_free( group_buf );
4759 buffer_free( sql_buf );
4760 if( defaultselhash )
4761 jsonObjectFree( defaultselhash );
4765 buffer_add( sql_buf, pred );
4769 // Build a HAVING clause, if there is one
4772 // and it's on the the WHERE clause
4773 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4775 if( ! having_buf ) {
4777 osrfAppSessionStatus(
4779 OSRF_STATUS_INTERNALSERVERERROR,
4780 "osrfMethodException",
4782 "Severe query error in HAVING predicate -- see error log for more details"
4785 buffer_free( group_buf );
4786 buffer_free( sql_buf );
4787 if( defaultselhash )
4788 jsonObjectFree( defaultselhash );
4793 // Build an ORDER BY clause, if there is one
4794 if( NULL == order_hash )
4795 ; // No ORDER BY? do nothing
4796 else if( JSON_ARRAY == order_hash->type ) {
4797 order_by_list = buildOrderByFromArray( ctx, order_hash );
4798 if( !order_by_list ) {
4800 buffer_free( group_buf );
4801 buffer_free( sql_buf );
4802 if( defaultselhash )
4803 jsonObjectFree( defaultselhash );
4806 } else if( JSON_HASH == order_hash->type ) {
4807 // This hash is keyed on class alias. Each class has either
4808 // an array of field names or a hash keyed on field name.
4809 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4810 jsonIterator* class_itr = jsonNewIterator( order_hash );
4811 while( (snode = jsonIteratorNext( class_itr )) ) {
4813 ClassInfo* order_class_info = search_alias( class_itr->key );
4814 if( ! order_class_info ) {
4815 osrfLogError( OSRF_LOG_MARK,
4816 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4817 modulename, class_itr->key );
4819 osrfAppSessionStatus(
4821 OSRF_STATUS_INTERNALSERVERERROR,
4822 "osrfMethodException",
4824 "Invalid class referenced in ORDER BY clause -- "
4825 "see error log for more details"
4827 jsonIteratorFree( class_itr );
4828 buffer_free( order_buf );
4830 buffer_free( group_buf );
4831 buffer_free( sql_buf );
4832 if( defaultselhash )
4833 jsonObjectFree( defaultselhash );
4837 osrfHash* field_list_def = order_class_info->fields;
4839 if( snode->type == JSON_HASH ) {
4841 // Hash is keyed on field names from the current class. For each field
4842 // there is another layer of hash to define the sorting details, if any,
4843 // or a string to indicate direction of sorting.
4844 jsonIterator* order_itr = jsonNewIterator( snode );
4845 while( (onode = jsonIteratorNext( order_itr )) ) {
4847 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4849 osrfLogError( OSRF_LOG_MARK,
4850 "%s: Invalid field \"%s\" in ORDER BY clause",
4851 modulename, order_itr->key );
4853 osrfAppSessionStatus(
4855 OSRF_STATUS_INTERNALSERVERERROR,
4856 "osrfMethodException",
4858 "Invalid field in ORDER BY clause -- "
4859 "see error log for more details"
4861 jsonIteratorFree( order_itr );
4862 jsonIteratorFree( class_itr );
4863 buffer_free( order_buf );
4865 buffer_free( group_buf );
4866 buffer_free( sql_buf );
4867 if( defaultselhash )
4868 jsonObjectFree( defaultselhash );
4870 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4871 osrfLogError( OSRF_LOG_MARK,
4872 "%s: Virtual field \"%s\" in ORDER BY clause",
4873 modulename, order_itr->key );
4875 osrfAppSessionStatus(
4877 OSRF_STATUS_INTERNALSERVERERROR,
4878 "osrfMethodException",
4880 "Virtual field in ORDER BY clause -- "
4881 "see error log for more details"
4883 jsonIteratorFree( order_itr );
4884 jsonIteratorFree( class_itr );
4885 buffer_free( order_buf );
4887 buffer_free( group_buf );
4888 buffer_free( sql_buf );
4889 if( defaultselhash )
4890 jsonObjectFree( defaultselhash );
4894 const char* direction = NULL;
4895 if( onode->type == JSON_HASH ) {
4896 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4897 string = searchFieldTransform(
4899 osrfHashGet( field_list_def, order_itr->key ),
4903 if( ctx ) osrfAppSessionStatus(
4905 OSRF_STATUS_INTERNALSERVERERROR,
4906 "osrfMethodException",
4908 "Severe query error in ORDER BY clause -- "
4909 "see error log for more details"
4911 jsonIteratorFree( order_itr );
4912 jsonIteratorFree( class_itr );
4914 buffer_free( group_buf );
4915 buffer_free( order_buf);
4916 buffer_free( sql_buf );
4917 if( defaultselhash )
4918 jsonObjectFree( defaultselhash );
4922 growing_buffer* field_buf = buffer_init( 16 );
4923 buffer_fadd( field_buf, "\"%s\".%s",
4924 class_itr->key, order_itr->key );
4925 string = buffer_release( field_buf );
4928 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4929 const char* dir = jsonObjectGetString( tmp_const );
4930 if(!strncasecmp( dir, "d", 1 )) {
4931 direction = " DESC";
4937 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4938 osrfLogError( OSRF_LOG_MARK,
4939 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4940 modulename, json_type( onode->type ) );
4942 osrfAppSessionStatus(
4944 OSRF_STATUS_INTERNALSERVERERROR,
4945 "osrfMethodException",
4947 "Malformed ORDER BY clause -- see error log for more details"
4949 jsonIteratorFree( order_itr );
4950 jsonIteratorFree( class_itr );
4952 buffer_free( group_buf );
4953 buffer_free( order_buf );
4954 buffer_free( sql_buf );
4955 if( defaultselhash )
4956 jsonObjectFree( defaultselhash );
4960 string = strdup( order_itr->key );
4961 const char* dir = jsonObjectGetString( onode );
4962 if( !strncasecmp( dir, "d", 1 )) {
4963 direction = " DESC";
4970 OSRF_BUFFER_ADD( order_buf, ", " );
4972 order_buf = buffer_init( 128 );
4974 OSRF_BUFFER_ADD( order_buf, string );
4978 OSRF_BUFFER_ADD( order_buf, direction );
4982 jsonIteratorFree( order_itr );
4984 } else if( snode->type == JSON_ARRAY ) {
4986 // Array is a list of fields from the current class
4987 unsigned long order_idx = 0;
4988 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4990 const char* _f = jsonObjectGetString( onode );
4992 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4994 osrfLogError( OSRF_LOG_MARK,
4995 "%s: Invalid field \"%s\" in ORDER BY clause",
4998 osrfAppSessionStatus(
5000 OSRF_STATUS_INTERNALSERVERERROR,
5001 "osrfMethodException",
5003 "Invalid field in ORDER BY clause -- "
5004 "see error log for more details"
5006 jsonIteratorFree( class_itr );
5007 buffer_free( order_buf );
5009 buffer_free( group_buf );
5010 buffer_free( sql_buf );
5011 if( defaultselhash )
5012 jsonObjectFree( defaultselhash );
5014 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5015 osrfLogError( OSRF_LOG_MARK,
5016 "%s: Virtual field \"%s\" in ORDER BY clause",
5019 osrfAppSessionStatus(
5021 OSRF_STATUS_INTERNALSERVERERROR,
5022 "osrfMethodException",
5024 "Virtual field in ORDER BY clause -- "
5025 "see error log for more details"
5027 jsonIteratorFree( class_itr );
5028 buffer_free( order_buf );
5030 buffer_free( group_buf );
5031 buffer_free( sql_buf );
5032 if( defaultselhash )
5033 jsonObjectFree( defaultselhash );
5038 OSRF_BUFFER_ADD( order_buf, ", " );
5040 order_buf = buffer_init( 128 );
5042 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
5046 // IT'S THE OOOOOOOOOOOLD STYLE!
5048 osrfLogError( OSRF_LOG_MARK,
5049 "%s: Possible SQL injection attempt; direct order by is not allowed",
5052 osrfAppSessionStatus(
5054 OSRF_STATUS_INTERNALSERVERERROR,
5055 "osrfMethodException",
5057 "Severe query error -- see error log for more details"
5062 buffer_free( group_buf );
5063 buffer_free( order_buf );
5064 buffer_free( sql_buf );
5065 if( defaultselhash )
5066 jsonObjectFree( defaultselhash );
5067 jsonIteratorFree( class_itr );
5071 jsonIteratorFree( class_itr );
5073 order_by_list = buffer_release( order_buf );
5075 osrfLogError( OSRF_LOG_MARK,
5076 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
5077 modulename, json_type( order_hash->type ) );
5079 osrfAppSessionStatus(
5081 OSRF_STATUS_INTERNALSERVERERROR,
5082 "osrfMethodException",
5084 "Malformed ORDER BY clause -- see error log for more details"
5087 buffer_free( group_buf );
5088 buffer_free( sql_buf );
5089 if( defaultselhash )
5090 jsonObjectFree( defaultselhash );
5095 string = buffer_release( group_buf );
5097 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
5098 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
5099 OSRF_BUFFER_ADD( sql_buf, string );
5104 if( having_buf && *having_buf ) {
5105 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
5106 OSRF_BUFFER_ADD( sql_buf, having_buf );
5110 if( order_by_list ) {
5112 if( *order_by_list ) {
5113 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5114 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5117 free( order_by_list );
5121 const char* str = jsonObjectGetString( limit );
5122 if (str) { // limit could be JSON_NULL, etc.
5123 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
5128 const char* str = jsonObjectGetString( offset );
5130 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
5134 if( !(flags & SUBSELECT) )
5135 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5137 if( defaultselhash )
5138 jsonObjectFree( defaultselhash );
5140 return buffer_release( sql_buf );
5142 } // end of SELECT()
5145 @brief Build a list of ORDER BY expressions.
5146 @param ctx Pointer to the method context.
5147 @param order_array Pointer to a JSON_ARRAY of field specifications.
5148 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
5149 Each expression may be either a column reference or a function call whose first parameter
5150 is a column reference.
5152 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
5153 It may optionally include entries for "direction" and/or "transform".
5155 The calling code is responsible for freeing the returned string.
5157 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
5158 if( ! order_array ) {
5159 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
5162 osrfAppSessionStatus(
5164 OSRF_STATUS_INTERNALSERVERERROR,
5165 "osrfMethodException",
5167 "Logic error: ORDER BY clause expected, not found; "
5168 "see error log for more details"
5171 } else if( order_array->type != JSON_ARRAY ) {
5172 osrfLogError( OSRF_LOG_MARK,
5173 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
5175 osrfAppSessionStatus(
5177 OSRF_STATUS_INTERNALSERVERERROR,
5178 "osrfMethodException",
5180 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
5184 growing_buffer* order_buf = buffer_init( 128 );
5185 int first = 1; // boolean
5187 jsonObject* order_spec;
5188 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
5190 if( JSON_HASH != order_spec->type ) {
5191 osrfLogError( OSRF_LOG_MARK,
5192 "%s: Malformed field specification in ORDER BY clause; "
5193 "expected JSON_HASH, found %s",
5194 modulename, json_type( order_spec->type ) );
5196 osrfAppSessionStatus(
5198 OSRF_STATUS_INTERNALSERVERERROR,
5199 "osrfMethodException",
5201 "Malformed ORDER BY clause -- see error log for more details"
5203 buffer_free( order_buf );
5207 const char* class_alias =
5208 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5210 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5212 jsonObject* compare_to = jsonObjectGetKey( order_spec, "compare" );
5214 if( !field || !class_alias ) {
5215 osrfLogError( OSRF_LOG_MARK,
5216 "%s: Missing class or field name in field specification of ORDER BY clause",
5219 osrfAppSessionStatus(
5221 OSRF_STATUS_INTERNALSERVERERROR,
5222 "osrfMethodException",
5224 "Malformed ORDER BY clause -- see error log for more details"
5226 buffer_free( order_buf );
5230 const ClassInfo* order_class_info = search_alias( class_alias );
5231 if( ! order_class_info ) {
5232 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5233 "not in FROM clause, skipping it", modulename, class_alias );
5237 // Add a separating comma, except at the beginning
5241 OSRF_BUFFER_ADD( order_buf, ", " );
5243 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5245 osrfLogError( OSRF_LOG_MARK,
5246 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5247 modulename, class_alias, field );
5249 osrfAppSessionStatus(
5251 OSRF_STATUS_INTERNALSERVERERROR,
5252 "osrfMethodException",
5254 "Invalid field referenced in ORDER BY clause -- "
5255 "see error log for more details"
5259 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5260 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5261 modulename, field );
5263 osrfAppSessionStatus(
5265 OSRF_STATUS_INTERNALSERVERERROR,
5266 "osrfMethodException",
5268 "Virtual field in ORDER BY clause -- see error log for more details"
5270 buffer_free( order_buf );
5274 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5275 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5276 if( ! transform_str ) {
5278 osrfAppSessionStatus(
5280 OSRF_STATUS_INTERNALSERVERERROR,
5281 "osrfMethodException",
5283 "Severe query error in ORDER BY clause -- "
5284 "see error log for more details"
5286 buffer_free( order_buf );
5290 OSRF_BUFFER_ADD( order_buf, transform_str );
5291 free( transform_str );
5292 } else if( compare_to ) {
5293 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5294 if( ! compare_str ) {
5296 osrfAppSessionStatus(
5298 OSRF_STATUS_INTERNALSERVERERROR,
5299 "osrfMethodException",
5301 "Severe query error in ORDER BY clause -- "
5302 "see error log for more details"
5304 buffer_free( order_buf );
5308 buffer_fadd( order_buf, "(%s)", compare_str );
5309 free( compare_str );
5312 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5314 const char* direction =
5315 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5317 if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5318 OSRF_BUFFER_ADD( order_buf, " DESC" );
5320 OSRF_BUFFER_ADD( order_buf, " ASC" );
5324 return buffer_release( order_buf );
5328 @brief Build a SELECT statement.
5329 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5330 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5331 @param meta Pointer to the class metadata for the core class.
5332 @param ctx Pointer to the method context.
5333 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5335 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5336 "order_by", "limit", and "offset".
5338 The SELECT statements built here are distinct from those built for the json_query method.
5340 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5341 osrfHash* meta, osrfMethodContext* ctx ) {
5343 const char* locale = osrf_message_get_last_locale();
5345 osrfHash* fields = osrfHashGet( meta, "fields" );
5346 const char* core_class = osrfHashGet( meta, "classname" );
5348 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5350 jsonObject* selhash = NULL;
5351 jsonObject* defaultselhash = NULL;
5353 growing_buffer* sql_buf = buffer_init( 128 );
5354 growing_buffer* select_buf = buffer_init( 128 );
5356 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5357 defaultselhash = jsonNewObjectType( JSON_HASH );
5358 selhash = defaultselhash;
5361 // If there's no SELECT list for the core class, build one
5362 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5363 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5365 // Add every non-virtual field to the field list
5366 osrfHash* field_def = NULL;
5367 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5368 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5369 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5370 const char* field = osrfHashIteratorKey( field_itr );
5371 jsonObjectPush( field_list, jsonNewObject( field ) );
5374 osrfHashIteratorFree( field_itr );
5375 jsonObjectSetKey( selhash, core_class, field_list );
5378 // Build a list of columns for the SELECT clause
5380 const jsonObject* snode = NULL;
5381 jsonIterator* class_itr = jsonNewIterator( selhash );
5382 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5384 // If the class isn't in the IDL, ignore it
5385 const char* cname = class_itr->key;
5386 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5390 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5391 if( strcmp( core_class, class_itr->key )) {
5395 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5396 if( !found->size ) {
5397 jsonObjectFree( found );
5401 jsonObjectFree( found );
5404 const jsonObject* node = NULL;
5405 jsonIterator* select_itr = jsonNewIterator( snode );
5406 while( (node = jsonIteratorNext( select_itr )) ) {
5407 const char* item_str = jsonObjectGetString( node );
5408 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5409 char* fname = osrfHashGet( field, "name" );
5414 if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5420 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5425 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5426 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5429 i18n = osrfHashGet( field, "i18n" );
5431 if( str_is_true( i18n ) ) {
5432 char* pkey = osrfHashGet( idlClass, "primarykey" );
5433 char* tname = osrfHashGet( idlClass, "tablename" );
5435 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5436 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5437 tname, cname, fname, pkey, cname, pkey, locale, fname );
5439 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5442 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5446 jsonIteratorFree( select_itr );
5449 jsonIteratorFree( class_itr );
5451 char* col_list = buffer_release( select_buf );
5452 char* table = oilsGetRelation( meta );
5454 table = strdup( "(null)" );
5456 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5460 // Clear the query stack (as a fail-safe precaution against possible
5461 // leftover garbage); then push the first query frame onto the stack.
5462 clear_query_stack();
5464 if( add_query_core( NULL, core_class ) ) {
5466 osrfAppSessionStatus(
5468 OSRF_STATUS_INTERNALSERVERERROR,
5469 "osrfMethodException",
5471 "Unable to build query frame for core class"
5473 buffer_free( sql_buf );
5474 if( defaultselhash )
5475 jsonObjectFree( defaultselhash );
5479 // Add the JOIN clauses, if any
5481 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5482 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5483 OSRF_BUFFER_ADD( sql_buf, join_clause );
5484 free( join_clause );
5487 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5488 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5490 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5492 // Add the conditions in the WHERE clause
5493 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5495 osrfAppSessionStatus(
5497 OSRF_STATUS_INTERNALSERVERERROR,
5498 "osrfMethodException",
5500 "Severe query error -- see error log for more details"
5502 buffer_free( sql_buf );
5503 if( defaultselhash )
5504 jsonObjectFree( defaultselhash );
5505 clear_query_stack();
5508 buffer_add( sql_buf, pred );
5512 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5513 if( rest_of_query ) {
5514 const jsonObject* order_by = NULL;
5515 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5517 char* order_by_list = NULL;
5519 if( JSON_ARRAY == order_by->type ) {
5520 order_by_list = buildOrderByFromArray( ctx, order_by );
5521 if( !order_by_list ) {
5522 buffer_free( sql_buf );
5523 if( defaultselhash )
5524 jsonObjectFree( defaultselhash );
5525 clear_query_stack();
5528 } else if( JSON_HASH == order_by->type ) {
5529 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5530 // and build a list of ORDER BY expressions.
5531 growing_buffer* order_buf = buffer_init( 128 );
5533 jsonIterator* class_itr = jsonNewIterator( order_by );
5534 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5536 ClassInfo* order_class_info = search_alias( class_itr->key );
5537 if( ! order_class_info )
5538 continue; // class not referenced by FROM clause? Ignore it.
5540 if( JSON_HASH == snode->type ) {
5542 // If the data for the current class is a JSON_HASH, then it is
5543 // keyed on field name.
5545 const jsonObject* onode = NULL;
5546 jsonIterator* order_itr = jsonNewIterator( snode );
5547 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5549 osrfHash* field_def = osrfHashGet(
5550 order_class_info->fields, order_itr->key );
5552 continue; // Field not defined in IDL? Ignore it.
5553 if( str_is_true( osrfHashGet( field_def, "virtual")))
5554 continue; // Field is virtual? Ignore it.
5556 char* field_str = NULL;
5557 char* direction = NULL;
5558 if( onode->type == JSON_HASH ) {
5559 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5560 field_str = searchFieldTransform(
5561 class_itr->key, field_def, onode );
5563 osrfAppSessionStatus(
5565 OSRF_STATUS_INTERNALSERVERERROR,
5566 "osrfMethodException",
5568 "Severe query error in ORDER BY clause -- "
5569 "see error log for more details"
5571 jsonIteratorFree( order_itr );
5572 jsonIteratorFree( class_itr );
5573 buffer_free( order_buf );
5574 buffer_free( sql_buf );
5575 if( defaultselhash )
5576 jsonObjectFree( defaultselhash );
5577 clear_query_stack();
5581 growing_buffer* field_buf = buffer_init( 16 );
5582 buffer_fadd( field_buf, "\"%s\".%s",
5583 class_itr->key, order_itr->key );
5584 field_str = buffer_release( field_buf );
5587 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5588 const char* dir = jsonObjectGetString( order_by );
5589 if(!strncasecmp( dir, "d", 1 )) {
5590 direction = " DESC";
5594 field_str = strdup( order_itr->key );
5595 const char* dir = jsonObjectGetString( onode );
5596 if( !strncasecmp( dir, "d", 1 )) {
5597 direction = " DESC";
5606 buffer_add( order_buf, ", " );
5609 buffer_add( order_buf, field_str );
5613 buffer_add( order_buf, direction );
5615 } // end while; looping over ORDER BY expressions
5617 jsonIteratorFree( order_itr );
5619 } else if( JSON_STRING == snode->type ) {
5620 // We expect a comma-separated list of sort fields.
5621 const char* str = jsonObjectGetString( snode );
5622 if( strchr( str, ';' )) {
5623 // No semicolons allowed. It is theoretically possible for a
5624 // legitimate semicolon to occur within quotes, but it's not likely
5625 // to occur in practice in the context of an ORDER BY list.
5626 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5627 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5629 osrfAppSessionStatus(
5631 OSRF_STATUS_INTERNALSERVERERROR,
5632 "osrfMethodException",
5634 "Possible attempt at SOL injection -- "
5635 "semicolon found in ORDER BY list"
5638 jsonIteratorFree( class_itr );
5639 buffer_free( order_buf );
5640 buffer_free( sql_buf );
5641 if( defaultselhash )
5642 jsonObjectFree( defaultselhash );
5643 clear_query_stack();
5646 buffer_add( order_buf, str );
5650 } // end while; looping over order_by classes
5652 jsonIteratorFree( class_itr );
5653 order_by_list = buffer_release( order_buf );
5656 osrfLogWarning( OSRF_LOG_MARK,
5657 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5658 "no ORDER BY generated" );
5661 if( order_by_list && *order_by_list ) {
5662 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5663 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5666 free( order_by_list );
5669 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5671 const char* str = jsonObjectGetString( limit );
5681 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5683 const char* str = jsonObjectGetString( offset );
5694 if( defaultselhash )
5695 jsonObjectFree( defaultselhash );
5696 clear_query_stack();
5698 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5699 return buffer_release( sql_buf );
5702 int doJSONSearch ( osrfMethodContext* ctx ) {
5703 if(osrfMethodVerifyContext( ctx )) {
5704 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5708 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5712 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5716 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5717 flags |= SELECT_DISTINCT;
5719 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5720 flags |= DISABLE_I18N;
5722 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5723 clear_query_stack(); // a possibly needless precaution
5724 char* sql = buildQuery( ctx, hash, flags );
5725 clear_query_stack();
5732 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5735 dbhandle = writehandle;
5737 dbi_result result = dbi_conn_query( dbhandle, sql );
5740 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5742 if( dbi_result_first_row( result )) {
5743 /* JSONify the result */
5744 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5747 jsonObject* return_val = oilsMakeJSONFromResult( result );
5748 osrfAppRespond( ctx, return_val );
5749 jsonObjectFree( return_val );
5750 } while( dbi_result_next_row( result ));
5753 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5756 osrfAppRespondComplete( ctx, NULL );
5758 /* clean up the query */
5759 dbi_result_free( result );
5764 int errnum = dbi_conn_error( dbhandle, &msg );
5765 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5766 modulename, sql, errnum, msg ? msg : "(No description available)" );
5767 osrfAppSessionStatus(
5769 OSRF_STATUS_INTERNALSERVERERROR,
5770 "osrfMethodException",
5772 "Severe query error -- see error log for more details"
5774 if( !oilsIsDBConnected( dbhandle ))
5775 osrfAppSessionPanic( ctx->session );
5782 // The last parameter, err, is used to report an error condition by updating an int owned by
5783 // the calling code.
5785 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5786 // It is the responsibility of the calling code to initialize *err before the
5787 // call, so that it will be able to make sense of the result.
5789 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5790 // redundant anyway.
5791 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5792 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5795 dbhandle = writehandle;
5797 char* core_class = osrfHashGet( class_meta, "classname" );
5798 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5800 char* pkey = osrfHashGet( class_meta, "primarykey" );
5802 if (!ctx->session->userData)
5803 (void) initSessionCache( ctx );
5805 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5806 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5807 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5809 int i_respond_directly = 0;
5810 int flesh_depth = 0;
5812 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5814 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5819 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5821 dbi_result result = dbi_conn_query( dbhandle, sql );
5822 if( NULL == result ) {
5824 int errnum = dbi_conn_error( dbhandle, &msg );
5825 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5826 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5827 msg ? msg : "(No description available)" );
5828 if( !oilsIsDBConnected( dbhandle ))
5829 osrfAppSessionPanic( ctx->session );
5830 osrfAppSessionStatus(
5832 OSRF_STATUS_INTERNALSERVERERROR,
5833 "osrfMethodException",
5835 "Severe query error -- see error log for more details"
5842 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5845 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5846 jsonObject* row_obj = NULL;
5848 // The following two steps are for verifyObjectPCRUD()'s benefit.
5849 // 1. get the flesh depth
5850 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5852 flesh_depth = (int) jsonObjectGetNumber( _tmp );
5853 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5854 flesh_depth = max_flesh_depth;
5857 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
5858 // over the whole life of this request. This means if we've already set
5859 // up a rs_size_req_%d, do nothing.
5860 // a. Incidentally, we can also use this opportunity to set i_respond_directly
5861 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
5862 if( !rs_size ) { // pointer null, so value not set in hash
5863 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
5864 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
5866 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
5867 unsigned long long result_count = dbi_result_get_numrows( result );
5868 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
5869 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
5872 if( dbi_result_first_row( result )) {
5874 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5875 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5876 // eliminate the duplicates.
5877 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5878 osrfHash* dedup = osrfNewHash();
5880 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5881 char* pkey_val = oilsFMGetString( row_obj, pkey );
5882 if( osrfHashGet( dedup, pkey_val ) ) {
5883 jsonObjectFree( row_obj );
5886 if( !enforce_pcrud || !need_to_verify ||
5887 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
5888 osrfHashSet( dedup, pkey_val, pkey_val );
5889 jsonObjectPush( res_list, row_obj );
5892 } while( dbi_result_next_row( result ));
5893 osrfHashFree( dedup );
5896 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5900 /* clean up the query */
5901 dbi_result_free( result );
5904 // If we're asked to flesh, and there's anything to flesh, then flesh it
5905 // (formerly we would skip fleshing if in pcrud mode, but now we support
5906 // fleshing even in PCRUD).
5907 if( res_list->size ) {
5908 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
5909 jsonObject* flesh_fields;
5910 jsonObject* flesh_blob = NULL;
5911 osrfStringArray* link_fields = NULL;
5912 osrfHash* links = NULL;
5916 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
5917 if( temp_blob && flesh_depth > 0 ) {
5919 flesh_blob = jsonObjectClone( temp_blob );
5920 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
5922 links = osrfHashGet( class_meta, "links" );
5924 // Make an osrfStringArray of the names of fields to be fleshed
5925 if( flesh_fields ) {
5926 if( flesh_fields->size == 1 ) {
5927 const char* _t = jsonObjectGetString(
5928 jsonObjectGetIndex( flesh_fields, 0 ) );
5929 if( !strcmp( _t, "*" ))
5930 link_fields = osrfHashKeys( links );
5933 if( !link_fields ) {
5935 link_fields = osrfNewStringArray( 1 );
5936 jsonIterator* _i = jsonNewIterator( flesh_fields );
5937 while ((_f = jsonIteratorNext( _i ))) {
5938 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5940 jsonIteratorFree( _i );
5943 want_flesh = link_fields ? 1 : 0;
5947 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5949 // Iterate over the JSON_ARRAY of rows
5951 unsigned long res_idx = 0;
5952 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5955 const char* link_field;
5957 // Iterate over the list of fleshable fields
5959 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5961 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5963 osrfHash* kid_link = osrfHashGet( links, link_field );
5965 continue; // Not a link field; skip it
5967 osrfHash* field = osrfHashGet( fields, link_field );
5969 continue; // Not a field at all; skip it (IDL is ill-formed)
5971 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5972 osrfHashGet( kid_link, "class" ));
5974 continue; // The class it links to doesn't exist; skip it
5976 const char* reltype = osrfHashGet( kid_link, "reltype" );
5978 continue; // No reltype; skip it (IDL is ill-formed)
5980 osrfHash* value_field = field;
5982 if( !strcmp( reltype, "has_many" )
5983 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5984 value_field = osrfHashGet(
5985 fields, osrfHashGet( class_meta, "primarykey" ) );
5988 int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
5989 // fleshing pcrud case: we require the controller in need_to_verify mode
5990 if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
5991 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
5995 (unsigned long) atoi( osrfHashGet(field, "array_position") ),
5997 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
6003 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
6005 if( link_map->size > 0 ) {
6006 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
6009 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
6014 osrfHashGet( kid_link, "class" ),
6021 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
6022 osrfHashGet( kid_link, "field" ),
6023 osrfHashGet( kid_link, "class" ),
6024 osrfHashGet( kid_link, "key" ),
6025 osrfHashGet( kid_link, "reltype" )
6028 const char* search_key = jsonObjectGetString(
6029 jsonObjectGetIndex( cur,
6030 atoi( osrfHashGet( value_field, "array_position" ) )
6035 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
6039 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
6041 // construct WHERE clause
6042 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
6045 osrfHashGet( kid_link, "key" ),
6046 jsonNewObject( search_key )
6049 // construct the rest of the query, mostly
6050 // by copying pieces of the previous level of query
6051 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
6052 jsonObjectSetKey( rest_of_query, "flesh",
6053 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
6057 jsonObjectSetKey( rest_of_query, "flesh_fields",
6058 jsonObjectClone( flesh_blob ));
6060 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
6061 jsonObjectSetKey( rest_of_query, "order_by",
6062 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
6066 if( jsonObjectGetKeyConst( query_hash, "select" )) {
6067 jsonObjectSetKey( rest_of_query, "select",
6068 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
6072 // do the query, recursively, to expand the fleshable field
6073 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
6074 where_clause, rest_of_query, err );
6076 jsonObjectFree( where_clause );
6077 jsonObjectFree( rest_of_query );
6080 osrfStringArrayFree( link_fields );
6081 jsonObjectFree( res_list );
6082 jsonObjectFree( flesh_blob );
6086 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
6087 osrfHashGet( kid_link, "class" ), kids->size );
6089 // Traverse the result set
6090 jsonObject* X = NULL;
6091 if( link_map->size > 0 && kids->size > 0 ) {
6093 kids = jsonNewObjectType( JSON_ARRAY );
6095 jsonObject* _k_node;
6096 unsigned long res_idx = 0;
6097 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
6103 (unsigned long) atoi(
6109 osrfHashGet( kid_link, "class" )
6113 osrfStringArrayGetString( link_map, 0 )
6121 } // end while loop traversing X
6124 if (kids->size > 0) {
6126 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
6127 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
6129 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
6130 osrfHashGet( kid_link, "field" ));
6133 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6134 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
6139 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
6141 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
6142 osrfHashGet( kid_link, "field" ) );
6145 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6146 jsonObjectClone( kids )
6151 jsonObjectFree( kids );
6155 jsonObjectFree( kids );
6157 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
6158 osrfHashGet( kid_link, "field" ) );
6159 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
6161 } // end while loop traversing list of fleshable fields
6164 if( i_respond_directly ) {
6165 if ( *methodtype == 'i' ) {
6166 osrfAppRespond( ctx,
6167 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
6169 osrfAppRespond( ctx, cur );
6172 } // end while loop traversing res_list
6173 jsonObjectFree( flesh_blob );
6174 osrfStringArrayFree( link_fields );
6177 if( i_respond_directly ) {
6178 jsonObjectFree( res_list );
6179 return jsonNewObjectType( JSON_ARRAY );
6186 int doUpdate( osrfMethodContext* ctx ) {
6187 if( osrfMethodVerifyContext( ctx )) {
6188 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6193 timeout_needs_resetting = 1;
6195 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6197 jsonObject* target = NULL;
6199 target = jsonObjectGetIndex( ctx->params, 1 );
6201 target = jsonObjectGetIndex( ctx->params, 0 );
6203 if(!verifyObjectClass( ctx, target )) {
6204 osrfAppRespondComplete( ctx, NULL );
6208 if( getXactId( ctx ) == NULL ) {
6209 osrfAppSessionStatus(
6211 OSRF_STATUS_BADREQUEST,
6212 "osrfMethodException",
6214 "No active transaction -- required for UPDATE"
6216 osrfAppRespondComplete( ctx, NULL );
6220 // The following test is harmless but redundant. If a class is
6221 // readonly, we don't register an update method for it.
6222 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6223 osrfAppSessionStatus(
6225 OSRF_STATUS_BADREQUEST,
6226 "osrfMethodException",
6228 "Cannot UPDATE readonly class"
6230 osrfAppRespondComplete( ctx, NULL );
6234 const char* trans_id = getXactId( ctx );
6236 // Set the last_xact_id
6237 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6239 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6240 trans_id, target->classname, index );
6241 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6244 char* pkey = osrfHashGet( meta, "primarykey" );
6245 osrfHash* fields = osrfHashGet( meta, "fields" );
6247 char* id = oilsFMGetString( target, pkey );
6251 "%s updating %s object with %s = %s",
6253 osrfHashGet( meta, "fieldmapper" ),
6258 dbhandle = writehandle;
6259 growing_buffer* sql = buffer_init( 128 );
6260 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6263 osrfHash* field_def = NULL;
6264 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6265 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6267 // Skip virtual fields, and the primary key
6268 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6271 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6275 const char* field_name = osrfHashIteratorKey( field_itr );
6276 if( ! strcmp( field_name, pkey ) )
6279 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6281 int value_is_numeric = 0; // boolean
6283 if( field_object && field_object->classname ) {
6284 value = oilsFMGetString(
6286 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6288 } else if( field_object && JSON_BOOL == field_object->type ) {
6289 if( jsonBoolIsTrue( field_object ) )
6290 value = strdup( "t" );
6292 value = strdup( "f" );
6294 value = jsonObjectToSimpleString( field_object );
6295 if( field_object && JSON_NUMBER == field_object->type )
6296 value_is_numeric = 1;
6299 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6300 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6302 if( !field_object || field_object->type == JSON_NULL ) {
6303 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6304 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6308 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6309 buffer_fadd( sql, " %s = NULL", field_name );
6312 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6316 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6318 const char* numtype = get_datatype( field_def );
6319 if( !strncmp( numtype, "INT", 3 ) ) {
6320 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6321 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6322 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6324 // Must really be intended as a string, so quote it
6325 if( dbi_conn_quote_string( dbhandle, &value )) {
6326 buffer_fadd( sql, " %s = %s", field_name, value );
6328 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6329 modulename, value );
6330 osrfAppSessionStatus(
6332 OSRF_STATUS_INTERNALSERVERERROR,
6333 "osrfMethodException",
6335 "Error quoting string -- please see the error log for more details"
6339 osrfHashIteratorFree( field_itr );
6341 osrfAppRespondComplete( ctx, NULL );
6346 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6349 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6353 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6354 buffer_fadd( sql, " %s = %s", field_name, value );
6356 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6357 osrfAppSessionStatus(
6359 OSRF_STATUS_INTERNALSERVERERROR,
6360 "osrfMethodException",
6362 "Error quoting string -- please see the error log for more details"
6366 osrfHashIteratorFree( field_itr );
6368 osrfAppRespondComplete( ctx, NULL );
6377 osrfHashIteratorFree( field_itr );
6379 jsonObject* obj = jsonNewObject( id );
6381 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6382 dbi_conn_quote_string( dbhandle, &id );
6384 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6386 char* query = buffer_release( sql );
6387 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6389 dbi_result result = dbi_conn_query( dbhandle, query );
6394 jsonObjectFree( obj );
6395 obj = jsonNewObject( NULL );
6397 int errnum = dbi_conn_error( dbhandle, &msg );
6400 "%s ERROR updating %s object with %s = %s: %d %s",
6402 osrfHashGet( meta, "fieldmapper" ),
6406 msg ? msg : "(No description available)"
6408 osrfAppSessionStatus(
6410 OSRF_STATUS_INTERNALSERVERERROR,
6411 "osrfMethodException",
6413 "Error in updating a row -- please see the error log for more details"
6415 if( !oilsIsDBConnected( dbhandle ))
6416 osrfAppSessionPanic( ctx->session );
6419 dbi_result_free( result );
6422 osrfAppRespondComplete( ctx, obj );
6423 jsonObjectFree( obj );
6427 int doDelete( osrfMethodContext* ctx ) {
6428 if( osrfMethodVerifyContext( ctx )) {
6429 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6434 timeout_needs_resetting = 1;
6436 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6438 if( getXactId( ctx ) == NULL ) {
6439 osrfAppSessionStatus(
6441 OSRF_STATUS_BADREQUEST,
6442 "osrfMethodException",
6444 "No active transaction -- required for DELETE"
6446 osrfAppRespondComplete( ctx, NULL );
6450 // The following test is harmless but redundant. If a class is
6451 // readonly, we don't register a delete method for it.
6452 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6453 osrfAppSessionStatus(
6455 OSRF_STATUS_BADREQUEST,
6456 "osrfMethodException",
6458 "Cannot DELETE readonly class"
6460 osrfAppRespondComplete( ctx, NULL );
6464 dbhandle = writehandle;
6466 char* pkey = osrfHashGet( meta, "primarykey" );
6473 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6474 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6475 osrfAppRespondComplete( ctx, NULL );
6479 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6481 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6482 osrfAppRespondComplete( ctx, NULL );
6485 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6490 "%s deleting %s object with %s = %s",
6492 osrfHashGet( meta, "fieldmapper" ),
6497 jsonObject* obj = jsonNewObject( id );
6499 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6500 dbi_conn_quote_string( writehandle, &id );
6502 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6503 osrfHashGet( meta, "tablename" ), pkey, id );
6508 jsonObjectFree( obj );
6509 obj = jsonNewObject( NULL );
6511 int errnum = dbi_conn_error( writehandle, &msg );
6514 "%s ERROR deleting %s object with %s = %s: %d %s",
6516 osrfHashGet( meta, "fieldmapper" ),
6520 msg ? msg : "(No description available)"
6522 osrfAppSessionStatus(
6524 OSRF_STATUS_INTERNALSERVERERROR,
6525 "osrfMethodException",
6527 "Error in deleting a row -- please see the error log for more details"
6529 if( !oilsIsDBConnected( writehandle ))
6530 osrfAppSessionPanic( ctx->session );
6532 dbi_result_free( result );
6536 osrfAppRespondComplete( ctx, obj );
6537 jsonObjectFree( obj );
6542 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6543 @param result An iterator for a result set; we only look at the current row.
6544 @param @meta Pointer to the class metadata for the core class.
6545 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6547 If a column is not defined in the IDL, or if it has no array_position defined for it in
6548 the IDL, or if it is defined as virtual, ignore it.
6550 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6551 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6552 array_position in the IDL.
6554 A field defined in the IDL but not represented in the returned row will leave a hole
6555 in the JSON_ARRAY. In effect it will be treated as a null value.
6557 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6558 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6559 classname corresponding to the @a meta argument.
6561 The calling code is responsible for freeing the the resulting jsonObject by calling
6564 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6565 if( !( result && meta )) return NULL;
6567 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6568 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6569 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6571 osrfHash* fields = osrfHashGet( meta, "fields" );
6573 int columnIndex = 1;
6574 const char* columnName;
6576 /* cycle through the columns in the row returned from the database */
6577 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6579 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6581 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6583 /* determine the field type and storage attributes */
6584 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6585 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6587 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6588 // or if it has no sequence number there, or if it's virtual, skip it.
6589 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6592 if( str_is_true( osrfHashGet( _f, "virtual" )))
6593 continue; // skip this column: IDL says it's virtual
6595 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6596 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6597 continue; // since we assign sequence numbers dynamically as we load the IDL.
6599 fmIndex = atoi( pos );
6600 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6602 continue; // This field is not defined in the IDL
6605 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6606 // sequence number from the IDL (which is likely to be different from the sequence
6607 // of columns in the SELECT clause).
6608 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6609 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6614 case DBI_TYPE_INTEGER :
6616 if( attr & DBI_INTEGER_SIZE8 )
6617 jsonObjectSetIndex( object, fmIndex,
6618 jsonNewNumberObject(
6619 dbi_result_get_longlong_idx( result, columnIndex )));
6621 jsonObjectSetIndex( object, fmIndex,
6622 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6626 case DBI_TYPE_DECIMAL :
6627 jsonObjectSetIndex( object, fmIndex,
6628 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6631 case DBI_TYPE_STRING :
6636 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6641 case DBI_TYPE_DATETIME : {
6643 char dt_string[ 256 ] = "";
6646 // Fetch the date column as a time_t
6647 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6649 // Translate the time_t to a human-readable string
6650 if( !( attr & DBI_DATETIME_DATE )) {
6651 gmtime_r( &_tmp_dt, &gmdt );
6652 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6653 } else if( !( attr & DBI_DATETIME_TIME )) {
6654 localtime_r( &_tmp_dt, &gmdt );
6655 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6657 localtime_r( &_tmp_dt, &gmdt );
6658 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6661 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6665 case DBI_TYPE_BINARY :
6666 osrfLogError( OSRF_LOG_MARK,
6667 "Can't do binary at column %s : index %d", columnName, columnIndex );
6676 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6677 if( !result ) return NULL;
6679 jsonObject* object = jsonNewObject( NULL );
6682 char dt_string[ 256 ];
6686 int columnIndex = 1;
6688 unsigned short type;
6689 const char* columnName;
6691 /* cycle through the column list */
6692 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6694 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6696 fmIndex = -1; // reset the position
6698 /* determine the field type and storage attributes */
6699 type = dbi_result_get_field_type_idx( result, columnIndex );
6700 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6702 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6703 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6708 case DBI_TYPE_INTEGER :
6710 if( attr & DBI_INTEGER_SIZE8 )
6711 jsonObjectSetKey( object, columnName,
6712 jsonNewNumberObject( dbi_result_get_longlong_idx(
6713 result, columnIndex )) );
6715 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6716 dbi_result_get_int_idx( result, columnIndex )) );
6719 case DBI_TYPE_DECIMAL :
6720 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6721 dbi_result_get_double_idx( result, columnIndex )) );
6724 case DBI_TYPE_STRING :
6725 jsonObjectSetKey( object, columnName,
6726 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6729 case DBI_TYPE_DATETIME :
6731 memset( dt_string, '\0', sizeof( dt_string ));
6732 memset( &gmdt, '\0', sizeof( gmdt ));
6734 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6736 if( !( attr & DBI_DATETIME_DATE )) {
6737 gmtime_r( &_tmp_dt, &gmdt );
6738 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6739 } else if( !( attr & DBI_DATETIME_TIME )) {
6740 localtime_r( &_tmp_dt, &gmdt );
6741 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6743 localtime_r( &_tmp_dt, &gmdt );
6744 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6747 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6750 case DBI_TYPE_BINARY :
6751 osrfLogError( OSRF_LOG_MARK,
6752 "Can't do binary at column %s : index %d", columnName, columnIndex );
6756 } // end while loop traversing result
6761 // Interpret a string as true or false
6762 int str_is_true( const char* str ) {
6763 if( NULL == str || strcasecmp( str, "true" ) )
6769 // Interpret a jsonObject as true or false
6770 static int obj_is_true( const jsonObject* obj ) {
6773 else switch( obj->type )
6781 if( strcasecmp( obj->value.s, "true" ) )
6785 case JSON_NUMBER : // Support 1/0 for perl's sake
6786 if( jsonObjectGetNumber( obj ) == 1.0 )
6795 // Translate a numeric code into a text string identifying a type of
6796 // jsonObject. To be used for building error messages.
6797 static const char* json_type( int code ) {
6803 return "JSON_ARRAY";
6805 return "JSON_STRING";
6807 return "JSON_NUMBER";
6813 return "(unrecognized)";
6817 // Extract the "primitive" attribute from an IDL field definition.
6818 // If we haven't initialized the app, then we must be running in
6819 // some kind of testbed. In that case, default to "string".
6820 static const char* get_primitive( osrfHash* field ) {
6821 const char* s = osrfHashGet( field, "primitive" );
6823 if( child_initialized )
6826 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6828 osrfHashGet( field, "name" )
6836 // Extract the "datatype" attribute from an IDL field definition.
6837 // If we haven't initialized the app, then we must be running in
6838 // some kind of testbed. In that case, default to to NUMERIC,
6839 // since we look at the datatype only for numbers.
6840 static const char* get_datatype( osrfHash* field ) {
6841 const char* s = osrfHashGet( field, "datatype" );
6843 if( child_initialized )
6846 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6848 osrfHashGet( field, "name" )
6857 @brief Determine whether a string is potentially a valid SQL identifier.
6858 @param s The identifier to be tested.
6859 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6861 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6862 need to follow all the rules exactly, such as requiring that the first character not
6865 We allow leading and trailing white space. In between, we do not allow punctuation
6866 (except for underscores and dollar signs), control characters, or embedded white space.
6868 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6869 for the foreseeable future such quoted identifiers are not likely to be an issue.
6871 int is_identifier( const char* s) {
6875 // Skip leading white space
6876 while( isspace( (unsigned char) *s ) )
6880 return 0; // Nothing but white space? Not okay.
6882 // Check each character until we reach white space or
6883 // end-of-string. Letters, digits, underscores, and
6884 // dollar signs are okay. With the exception of periods
6885 // (as in schema.identifier), control characters and other
6886 // punctuation characters are not okay. Anything else
6887 // is okay -- it could for example be part of a multibyte
6888 // UTF8 character such as a letter with diacritical marks,
6889 // and those are allowed.
6891 if( isalnum( (unsigned char) *s )
6895 ; // Fine; keep going
6896 else if( ispunct( (unsigned char) *s )
6897 || iscntrl( (unsigned char) *s ) )
6900 } while( *s && ! isspace( (unsigned char) *s ) );
6902 // If we found any white space in the above loop,
6903 // the rest had better be all white space.
6905 while( isspace( (unsigned char) *s ) )
6909 return 0; // White space was embedded within non-white space
6915 @brief Determine whether to accept a character string as a comparison operator.
6916 @param op The candidate comparison operator.
6917 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6919 We don't validate the operator for real. We just make sure that it doesn't contain
6920 any semicolons or white space (with special exceptions for a few specific operators).
6921 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6922 space but it's still not a valid operator, then the database will complain.
6924 Another approach would be to compare the string against a short list of approved operators.
6925 We don't do that because we want to allow custom operators like ">100*", which at this
6926 writing would be difficult or impossible to express otherwise in a JSON query.
6928 int is_good_operator( const char* op ) {
6929 if( !op ) return 0; // Sanity check
6933 if( isspace( (unsigned char) *s ) ) {
6934 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6935 // and IS NOT DISTINCT FROM.
6936 if( !strcasecmp( op, "similar to" ) )
6938 else if( !strcasecmp( op, "is distinct from" ) )
6940 else if( !strcasecmp( op, "is not distinct from" ) )
6945 else if( ';' == *s )
6953 @name Query Frame Management
6955 The following machinery supports a stack of query frames for use by SELECT().
6957 A query frame caches information about one level of a SELECT query. When we enter
6958 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6960 The query frame stores information about the core class, and about any joined classes
6963 The main purpose is to map table aliases to classes and tables, so that a query can
6964 join to the same table more than once. A secondary goal is to reduce the number of
6965 lookups in the IDL by caching the results.
6969 #define STATIC_CLASS_INFO_COUNT 3
6971 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6974 @brief Allocate a ClassInfo as raw memory.
6975 @return Pointer to the newly allocated ClassInfo.
6977 Except for the in_use flag, which is used only by the allocation and deallocation
6978 logic, we don't initialize the ClassInfo here.
6980 static ClassInfo* allocate_class_info( void ) {
6981 // In order to reduce the number of mallocs and frees, we return a static
6982 // instance of ClassInfo, if we can find one that we're not already using.
6983 // We rely on the fact that the compiler will implicitly initialize the
6984 // static instances so that in_use == 0.
6987 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6988 if( ! static_class_info[ i ].in_use ) {
6989 static_class_info[ i ].in_use = 1;
6990 return static_class_info + i;
6994 // The static ones are all in use. Malloc one.
6996 return safe_malloc( sizeof( ClassInfo ) );
7000 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
7001 @param info Pointer to the ClassInfo to be cleared.
7003 static void clear_class_info( ClassInfo* info ) {
7008 // Free any malloc'd strings
7010 if( info->alias != info->alias_store )
7011 free( info->alias );
7013 if( info->class_name != info->class_name_store )
7014 free( info->class_name );
7016 free( info->source_def );
7018 info->alias = info->class_name = info->source_def = NULL;
7023 @brief Free a ClassInfo and everything it owns.
7024 @param info Pointer to the ClassInfo to be freed.
7026 static void free_class_info( ClassInfo* info ) {
7031 clear_class_info( info );
7033 // If it's one of the static instances, just mark it as not in use
7036 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7037 if( info == static_class_info + i ) {
7038 static_class_info[ i ].in_use = 0;
7043 // Otherwise it must have been malloc'd, so free it
7049 @brief Populate an already-allocated ClassInfo.
7050 @param info Pointer to the ClassInfo to be populated.
7051 @param alias Alias for the class. If it is NULL, or an empty string, use the class
7053 @param class Name of the class.
7054 @return Zero if successful, or 1 if not.
7056 Populate the ClassInfo with copies of the alias and class name, and with pointers to
7057 the relevant portions of the IDL for the specified class.
7059 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
7062 osrfLogError( OSRF_LOG_MARK,
7063 "%s ERROR: No ClassInfo available to populate", modulename );
7064 info->alias = info->class_name = info->source_def = NULL;
7065 info->class_def = info->fields = info->links = NULL;
7070 osrfLogError( OSRF_LOG_MARK,
7071 "%s ERROR: No class name provided for lookup", modulename );
7072 info->alias = info->class_name = info->source_def = NULL;
7073 info->class_def = info->fields = info->links = NULL;
7077 // Alias defaults to class name if not supplied
7078 if( ! alias || ! alias[ 0 ] )
7081 // Look up class info in the IDL
7082 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
7084 osrfLogError( OSRF_LOG_MARK,
7085 "%s ERROR: Class %s not defined in IDL", modulename, class );
7086 info->alias = info->class_name = info->source_def = NULL;
7087 info->class_def = info->fields = info->links = NULL;
7089 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
7090 osrfLogError( OSRF_LOG_MARK,
7091 "%s ERROR: Class %s is defined as virtual", modulename, class );
7092 info->alias = info->class_name = info->source_def = NULL;
7093 info->class_def = info->fields = info->links = NULL;
7097 osrfHash* links = osrfHashGet( class_def, "links" );
7099 osrfLogError( OSRF_LOG_MARK,
7100 "%s ERROR: No links defined in IDL for class %s", modulename, class );
7101 info->alias = info->class_name = info->source_def = NULL;
7102 info->class_def = info->fields = info->links = NULL;
7106 osrfHash* fields = osrfHashGet( class_def, "fields" );
7108 osrfLogError( OSRF_LOG_MARK,
7109 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
7110 info->alias = info->class_name = info->source_def = NULL;
7111 info->class_def = info->fields = info->links = NULL;
7115 char* source_def = oilsGetRelation( class_def );
7119 // We got everything we need, so populate the ClassInfo
7120 if( strlen( alias ) > ALIAS_STORE_SIZE )
7121 info->alias = strdup( alias );
7123 strcpy( info->alias_store, alias );
7124 info->alias = info->alias_store;
7127 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
7128 info->class_name = strdup( class );
7130 strcpy( info->class_name_store, class );
7131 info->class_name = info->class_name_store;
7134 info->source_def = source_def;
7136 info->class_def = class_def;
7137 info->links = links;
7138 info->fields = fields;
7143 #define STATIC_FRAME_COUNT 3
7145 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
7148 @brief Allocate a QueryFrame as raw memory.
7149 @return Pointer to the newly allocated QueryFrame.
7151 Except for the in_use flag, which is used only by the allocation and deallocation
7152 logic, we don't initialize the QueryFrame here.
7154 static QueryFrame* allocate_frame( void ) {
7155 // In order to reduce the number of mallocs and frees, we return a static
7156 // instance of QueryFrame, if we can find one that we're not already using.
7157 // We rely on the fact that the compiler will implicitly initialize the
7158 // static instances so that in_use == 0.
7161 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7162 if( ! static_frame[ i ].in_use ) {
7163 static_frame[ i ].in_use = 1;
7164 return static_frame + i;
7168 // The static ones are all in use. Malloc one.
7170 return safe_malloc( sizeof( QueryFrame ) );
7174 @brief Free a QueryFrame, and all the memory it owns.
7175 @param frame Pointer to the QueryFrame to be freed.
7177 static void free_query_frame( QueryFrame* frame ) {
7182 clear_class_info( &frame->core );
7184 // Free the join list
7186 ClassInfo* info = frame->join_list;
7189 free_class_info( info );
7193 frame->join_list = NULL;
7196 // If the frame is a static instance, just mark it as unused
7198 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7199 if( frame == static_frame + i ) {
7200 static_frame[ i ].in_use = 0;
7205 // Otherwise it must have been malloc'd, so free it
7211 @brief Search a given QueryFrame for a specified alias.
7212 @param frame Pointer to the QueryFrame to be searched.
7213 @param target The alias for which to search.
7214 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7216 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7217 if( ! frame || ! target ) {
7221 ClassInfo* found_class = NULL;
7223 if( !strcmp( target, frame->core.alias ) )
7224 return &(frame->core);
7226 ClassInfo* curr_class = frame->join_list;
7227 while( curr_class ) {
7228 if( strcmp( target, curr_class->alias ) )
7229 curr_class = curr_class->next;
7231 found_class = curr_class;
7241 @brief Push a new (blank) QueryFrame onto the stack.
7243 static void push_query_frame( void ) {
7244 QueryFrame* frame = allocate_frame();
7245 frame->join_list = NULL;
7246 frame->next = curr_query;
7248 // Initialize the ClassInfo for the core class
7249 ClassInfo* core = &frame->core;
7250 core->alias = core->class_name = core->source_def = NULL;
7251 core->class_def = core->fields = core->links = NULL;
7257 @brief Pop a QueryFrame off the stack and destroy it.
7259 static void pop_query_frame( void ) {
7264 QueryFrame* popped = curr_query;
7265 curr_query = popped->next;
7267 free_query_frame( popped );
7271 @brief Populate the ClassInfo for the core class.
7272 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7273 class name as an alias.
7274 @param class_name Name of the core class.
7275 @return Zero if successful, or 1 if not.
7277 Populate the ClassInfo of the core class with copies of the alias and class name, and
7278 with pointers to the relevant portions of the IDL for the core class.
7280 static int add_query_core( const char* alias, const char* class_name ) {
7283 if( ! curr_query ) {
7284 osrfLogError( OSRF_LOG_MARK,
7285 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7287 } else if( curr_query->core.alias ) {
7288 osrfLogError( OSRF_LOG_MARK,
7289 "%s ERROR: Core class %s already populated as %s",
7290 modulename, curr_query->core.class_name, curr_query->core.alias );
7294 build_class_info( &curr_query->core, alias, class_name );
7295 if( curr_query->core.alias )
7298 osrfLogError( OSRF_LOG_MARK,
7299 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7305 @brief Search the current QueryFrame for a specified alias.
7306 @param target The alias for which to search.
7307 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7309 static inline ClassInfo* search_alias( const char* target ) {
7310 return search_alias_in_frame( curr_query, target );
7314 @brief Search all levels of query for a specified alias, starting with the current query.
7315 @param target The alias for which to search.
7316 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7318 static ClassInfo* search_all_alias( const char* target ) {
7319 ClassInfo* found_class = NULL;
7320 QueryFrame* curr_frame = curr_query;
7322 while( curr_frame ) {
7323 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7326 curr_frame = curr_frame->next;
7333 @brief Add a class to the list of classes joined to the current query.
7334 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7335 the class name as an alias.
7336 @param classname The name of the class to be added.
7337 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7339 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7341 if( ! classname || ! *classname ) { // sanity check
7342 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7349 const ClassInfo* conflict = search_alias( alias );
7351 osrfLogError( OSRF_LOG_MARK,
7352 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7353 modulename, alias, conflict->class_name );
7357 ClassInfo* info = allocate_class_info();
7359 if( build_class_info( info, alias, classname ) ) {
7360 free_class_info( info );
7364 // Add the new ClassInfo to the join list of the current QueryFrame
7365 info->next = curr_query->join_list;
7366 curr_query->join_list = info;
7372 @brief Destroy all nodes on the query stack.
7374 static void clear_query_stack( void ) {
7380 @brief Implement the set_audit_info method.
7381 @param ctx Pointer to the method context.
7382 @return Zero if successful, or -1 if not.
7384 Issue a SAVEPOINT to the database server.
7389 - workstation id (int)
7391 If user id is not provided the authkey will be used.
7392 For PCRUD the authkey is always used, even if a user is provided.
7394 int setAuditInfo( osrfMethodContext* ctx ) {
7395 if(osrfMethodVerifyContext( ctx )) {
7396 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
7400 // Get the user id from the parameters
7401 const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7403 if( enforce_pcrud || !user_id ) {
7404 timeout_needs_resetting = 1;
7405 const jsonObject* user = verifyUserPCRUD( ctx );
7408 osrfAppRespondComplete( ctx, NULL );
7412 // Not PCRUD and have a user_id?
7413 int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7414 osrfAppRespondComplete( ctx, NULL );
7419 @brief Save a audit info
7420 @param ctx Pointer to the method context.
7421 @param user_id User ID to write as a string
7422 @param ws_id Workstation ID to write as a string
7424 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7425 if( ctx && ctx->session ) {
7426 osrfAppSession* session = ctx->session;
7428 osrfHash* cache = session->userData;
7430 // If the session doesn't already have a hash, create one. Make sure
7431 // that the application session frees the hash when it terminates.
7432 if( NULL == cache ) {
7433 session->userData = cache = osrfNewHash();
7434 osrfHashSetCallback( cache, &sessionDataFree );
7435 ctx->session->userDataFree = &userDataFree;
7438 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7440 osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7442 int errnum = dbi_conn_error( writehandle, &msg );
7445 "%s: Error setting auditor information: %d %s",
7448 msg ? msg : "(No description available)"
7450 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7451 "osrfMethodException", ctx->request, "Error setting auditor info" );
7452 if( !oilsIsDBConnected( writehandle ))
7453 osrfAppSessionPanic( ctx->session );
7456 dbi_result_free( result );
7463 @brief Remove all but safe character from savepoint name
7464 @param sp User-supplied savepoint name
7465 @return sanitized savepoint name, or NULL
7467 The caller is expected to free the returned string. Note that
7468 this function exists only because we can't use PQescapeLiteral
7469 without either forking libdbi or abandoning it.
7471 static char* _sanitize_savepoint_name( const char* sp ) {
7473 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_";
7475 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7476 // and the default value of NAMEDATALEN is 64; that should be long enough
7477 // for our purposes, and it's unlikely that anyone is going to recompile
7478 // PostgreSQL to have a smaller value, so cap the identifier name
7479 // accordingly to avoid the remote chance that someone manages to pass in a
7480 // 12GB savepoint name
7481 const int MAX_LITERAL_NAMELEN = 63;
7484 if (len > MAX_LITERAL_NAMELEN) {
7485 len = MAX_LITERAL_NAMELEN;
7488 char* safeSpName = safe_malloc( len + 1 );
7492 for (j = 0; j < len; j++) {
7493 found = strchr(safe_chars, sp[j]);
7495 safeSpName[ i++ ] = found[0];
7498 safeSpName[ i ] = '\0';