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 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1776 _tmp_params, NULL, &err );
1778 // Get the resulting row
1779 jsonObjectFree( _fparam );
1780 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1781 _fparam = jsonObjectExtractIndex( _list, 0 );
1783 // Referenced row not found
1785 bad_class = osrfHashGet( foreign_link_hash, "class" );
1788 jsonObjectFree( _tmp_params );
1789 jsonObjectFree( _list );
1795 // We had a foreign key pointing to such-and-such a row, but then
1796 // we couldn't fetch that row. The data in the database are in an
1797 // inconsistent state; the database itself may even be corrupted.
1798 growing_buffer* msg = buffer_init( 128 );
1801 "%s: no object of class %s found with primary key %s of %s",
1805 foreign_pkey_value ? foreign_pkey_value : "(null)"
1808 char* m = buffer_release( msg );
1809 osrfAppSessionStatus(
1811 OSRF_STATUS_INTERNALSERVERERROR,
1812 "osrfMethodException",
1818 osrfHashIteratorFree( class_itr );
1819 free( foreign_pkey_value );
1820 jsonObjectFree( param );
1825 free( foreign_pkey_value );
1828 // Examine each context column of the foreign row,
1829 // and add its value to the list of org units.
1831 const char* foreign_field = NULL;
1832 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1833 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1834 osrfStringArrayAdd( context_org_array,
1835 oilsFMGetStringConst( _fparam, foreign_field ));
1836 osrfLogDebug( OSRF_LOG_MARK,
1837 "adding foreign class %s field %s (value: %s) "
1838 "to the context org list",
1841 osrfStringArrayGetString(
1842 context_org_array, context_org_array->size - 1 )
1846 jsonObjectFree( _fparam );
1850 osrfHashIteratorFree( class_itr );
1855 // If there is an owning_user attached to the action, we allow that user and users with
1856 // object perms on the object. CREATE can't use this. We only do this when there is no
1857 // context org for this action, and when we're not ignoring object perms.
1859 *method_type != 'c' &&
1860 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
1861 context_org_array->size == 0
1863 char* owning_user_field = osrfHashGet( pcrud, "owning_user" );
1864 if (owning_user_field) {
1866 if (!param) { // We didn't get it during the context lookup
1867 pkey = osrfHashGet( class, "primarykey" );
1870 // There is no primary key, so we can't do a fresh lookup. Use the row
1871 // image that we already have. If it doesn't have everything we need, too bad.
1873 param = jsonObjectClone( obj );
1874 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1875 } else if( obj->classname ) {
1876 pkey_value = oilsFMGetStringConst( obj, pkey );
1878 param = jsonObjectClone( obj );
1879 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1882 pkey_value = jsonObjectGetString( obj );
1884 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1885 "of %s and retrieving from the database", pkey_value );
1889 // Fetch the row so that we can look at the foreign key(s)
1890 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1891 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1892 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1893 jsonObjectFree( _tmp_params );
1894 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1896 param = jsonObjectExtractIndex( _list, 0 );
1897 jsonObjectFree( _list );
1902 // The row doesn't exist. Complain, and deny access.
1903 osrfLogDebug( OSRF_LOG_MARK,
1904 "Object not found in the database with primary key %s of %s",
1907 growing_buffer* msg = buffer_init( 128 );
1910 "%s: no object found with primary key %s of %s",
1916 char* m = buffer_release( msg );
1917 osrfAppSessionStatus(
1919 OSRF_STATUS_INTERNALSERVERERROR,
1920 "osrfMethodException",
1929 int ownerid = atoi( oilsFMGetStringConst( param, owning_user_field ) );
1931 // Allow the owner to do whatever
1932 if (ownerid == userid)
1936 while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
1941 "Checking object permission [%s] for user %d "
1942 "on object %s (class %s)",
1946 osrfHashGet( class, "classname" )
1949 result = dbi_conn_queryf(
1951 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s') AS has_perm;",
1954 osrfHashGet( class, "classname" ),
1961 "Received a result for object permission [%s] "
1962 "for user %d on object %s (class %s)",
1966 osrfHashGet( class, "classname" )
1969 if( dbi_result_first_row( result )) {
1970 jsonObject* return_val = oilsMakeJSONFromResult( result );
1971 const char* has_perm = jsonObjectGetString(
1972 jsonObjectGetKeyConst( return_val, "has_perm" ));
1976 "Status of object permission [%s] for user %d "
1977 "on object %s (class %s) is %s",
1981 osrfHashGet(class, "classname"),
1985 if( *has_perm == 't' )
1987 jsonObjectFree( return_val );
1990 dbi_result_free( result );
1995 int errnum = dbi_conn_error( writehandle, &msg );
1996 osrfLogWarning( OSRF_LOG_MARK,
1997 "Unable to call check object permissions: %d, %s",
1998 errnum, msg ? msg : "(No description available)" );
1999 if( !oilsIsDBConnected( writehandle ))
2000 osrfAppSessionPanic( ctx->session );
2007 // For every combination of permission and context org unit: call a stored procedure
2008 // to determine if the user has this permission in the context of this org unit.
2009 // If the answer is yes at any point, then we're done, and the user has permission.
2010 // In other words permissions are additive.
2012 while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
2015 osrfStringArray* pcache = NULL;
2016 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
2017 pcache = getPermLocationCache(ctx, perm);
2020 pcache = osrfNewStringArray(0);
2022 result = dbi_conn_queryf(
2024 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
2032 "Received a result for permission [%s] for user %d",
2037 if( dbi_result_first_row( result )) {
2039 jsonObject* return_val = oilsMakeJSONFromResult( result );
2040 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
2041 jsonObjectFree( return_val );
2042 } while( dbi_result_next_row( result ));
2044 setPermLocationCache(ctx, perm, pcache);
2047 dbi_result_free( result );
2053 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
2055 if (rs_size > perm_at_threshold) {
2056 if (osrfStringArrayContains( pcache, context_org )) {
2064 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
2066 !str_is_true( osrfHashGet(pcrud, "global_required") ) ||
2067 osrfHashGet(pcrud, "owning_user")
2072 "Checking object permission [%s] for user %d "
2073 "on object %s (class %s) at org %d",
2077 osrfHashGet( class, "classname" ),
2081 result = dbi_conn_queryf(
2083 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
2086 osrfHashGet( class, "classname" ),
2094 "Received a result for object permission [%s] "
2095 "for user %d on object %s (class %s) at org %d",
2099 osrfHashGet( class, "classname" ),
2103 if( dbi_result_first_row( result )) {
2104 jsonObject* return_val = oilsMakeJSONFromResult( result );
2105 const char* has_perm = jsonObjectGetString(
2106 jsonObjectGetKeyConst( return_val, "has_perm" ));
2110 "Status of object permission [%s] for user %d "
2111 "on object %s (class %s) at org %d is %s",
2115 osrfHashGet(class, "classname"),
2120 if( *has_perm == 't' )
2122 jsonObjectFree( return_val );
2125 dbi_result_free( result );
2130 int errnum = dbi_conn_error( writehandle, &msg );
2131 osrfLogWarning( OSRF_LOG_MARK,
2132 "Unable to call check object permissions: %d, %s",
2133 errnum, msg ? msg : "(No description available)" );
2134 if( !oilsIsDBConnected( writehandle ))
2135 osrfAppSessionPanic( ctx->session );
2139 if (rs_size > perm_at_threshold) break;
2141 osrfLogDebug( OSRF_LOG_MARK,
2142 "Checking non-object permission [%s] for user %d at org %d",
2143 perm, userid, atoi(context_org) );
2144 result = dbi_conn_queryf(
2146 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
2153 osrfLogDebug( OSRF_LOG_MARK,
2154 "Received a result for permission [%s] for user %d at org %d",
2155 perm, userid, atoi( context_org ));
2156 if( dbi_result_first_row( result )) {
2157 jsonObject* return_val = oilsMakeJSONFromResult( result );
2158 const char* has_perm = jsonObjectGetString(
2159 jsonObjectGetKeyConst( return_val, "has_perm" ));
2160 osrfLogDebug( OSRF_LOG_MARK,
2161 "Status of permission [%s] for user %d at org %d is [%s]",
2162 perm, userid, atoi( context_org ), has_perm );
2163 if( *has_perm == 't' )
2165 jsonObjectFree( return_val );
2168 dbi_result_free( result );
2173 int errnum = dbi_conn_error( writehandle, &msg );
2174 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
2175 errnum, msg ? msg : "(No description available)" );
2176 if( !oilsIsDBConnected( writehandle ))
2177 osrfAppSessionPanic( ctx->session );
2186 osrfStringArrayFree( context_org_array );
2192 @brief Look up the root of the org_unit tree.
2193 @param ctx Pointer to the method context.
2194 @return The id of the root org unit, as a character string.
2196 Query actor.org_unit where parent_ou is null, and return the id as a string.
2198 This function assumes that there is only one root org unit, i.e. that we
2199 have a single tree, not a forest.
2201 The calling code is responsible for freeing the returned string.
2203 static const char* org_tree_root( osrfMethodContext* ctx ) {
2205 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
2206 static time_t last_lookup_time = 0;
2207 time_t current_time = time( NULL );
2209 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2210 // We successfully looked this up less than an hour ago.
2211 // It's not likely to have changed since then.
2212 return strdup( cached_root_id );
2214 last_lookup_time = current_time;
2217 jsonObject* where_clause = single_hash( "parent_ou", NULL );
2218 jsonObject* result = doFieldmapperSearch(
2219 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2220 jsonObjectFree( where_clause );
2222 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2225 jsonObjectFree( result );
2227 growing_buffer* msg = buffer_init( 128 );
2228 OSRF_BUFFER_ADD( msg, modulename );
2229 OSRF_BUFFER_ADD( msg,
2230 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2232 char* m = buffer_release( msg );
2233 osrfAppSessionStatus( ctx->session,
2234 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2237 cached_root_id[ 0 ] = '\0';
2241 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2242 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2244 strcpy( cached_root_id, root_org_unit_id );
2245 jsonObjectFree( result );
2246 return cached_root_id;
2250 @brief Create a JSON_HASH with a single key/value pair.
2251 @param key The key of the key/value pair.
2252 @param value the value of the key/value pair.
2253 @return Pointer to a newly created jsonObject of type JSON_HASH.
2255 The value of the key/value is either a string or (if @a value is NULL) a null.
2257 static jsonObject* single_hash( const char* key, const char* value ) {
2259 if( ! key ) key = "";
2261 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2262 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2267 int doCreate( osrfMethodContext* ctx ) {
2268 if(osrfMethodVerifyContext( ctx )) {
2269 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2274 timeout_needs_resetting = 1;
2276 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2277 jsonObject* target = NULL;
2278 jsonObject* options = NULL;
2280 if( enforce_pcrud ) {
2281 target = jsonObjectGetIndex( ctx->params, 1 );
2282 options = jsonObjectGetIndex( ctx->params, 2 );
2284 target = jsonObjectGetIndex( ctx->params, 0 );
2285 options = jsonObjectGetIndex( ctx->params, 1 );
2288 if( !verifyObjectClass( ctx, target )) {
2289 osrfAppRespondComplete( ctx, NULL );
2293 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2295 const char* trans_id = getXactId( ctx );
2297 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2299 osrfAppSessionStatus(
2301 OSRF_STATUS_BADREQUEST,
2302 "osrfMethodException",
2304 "No active transaction -- required for CREATE"
2306 osrfAppRespondComplete( ctx, NULL );
2310 // The following test is harmless but redundant. If a class is
2311 // readonly, we don't register a create method for it.
2312 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2313 osrfAppSessionStatus(
2315 OSRF_STATUS_BADREQUEST,
2316 "osrfMethodException",
2318 "Cannot INSERT readonly class"
2320 osrfAppRespondComplete( ctx, NULL );
2324 // Set the last_xact_id
2325 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2327 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2328 trans_id, target->classname, index);
2329 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2332 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2334 dbhandle = writehandle;
2336 osrfHash* fields = osrfHashGet( meta, "fields" );
2337 char* pkey = osrfHashGet( meta, "primarykey" );
2338 char* seq = osrfHashGet( meta, "sequence" );
2340 growing_buffer* table_buf = buffer_init( 128 );
2341 growing_buffer* col_buf = buffer_init( 128 );
2342 growing_buffer* val_buf = buffer_init( 128 );
2344 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2345 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2346 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2347 buffer_add( val_buf,"VALUES (" );
2351 osrfHash* field = NULL;
2352 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2353 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2355 const char* field_name = osrfHashIteratorKey( field_itr );
2357 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2360 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2363 if( field_object && field_object->classname ) {
2364 value = oilsFMGetString(
2366 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2368 } else if( field_object && JSON_BOOL == field_object->type ) {
2369 if( jsonBoolIsTrue( field_object ) )
2370 value = strdup( "t" );
2372 value = strdup( "f" );
2374 value = jsonObjectToSimpleString( field_object );
2380 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2381 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2384 buffer_add( col_buf, field_name );
2386 if( !field_object || field_object->type == JSON_NULL ) {
2387 buffer_add( val_buf, "DEFAULT" );
2389 } else if( !strcmp( get_primitive( field ), "number" )) {
2390 const char* numtype = get_datatype( field );
2391 if( !strcmp( numtype, "INT8" )) {
2392 buffer_fadd( val_buf, "%lld", atoll( value ));
2394 } else if( !strcmp( numtype, "INT" )) {
2395 buffer_fadd( val_buf, "%d", atoi( value ));
2397 } else if( !strcmp( numtype, "NUMERIC" )) {
2398 buffer_fadd( val_buf, "%f", atof( value ));
2401 if( dbi_conn_quote_string( writehandle, &value )) {
2402 OSRF_BUFFER_ADD( val_buf, value );
2405 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2406 osrfAppSessionStatus(
2408 OSRF_STATUS_INTERNALSERVERERROR,
2409 "osrfMethodException",
2411 "Error quoting string -- please see the error log for more details"
2414 buffer_free( table_buf );
2415 buffer_free( col_buf );
2416 buffer_free( val_buf );
2417 osrfAppRespondComplete( ctx, NULL );
2425 osrfHashIteratorFree( field_itr );
2427 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2428 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2430 char* table_str = buffer_release( table_buf );
2431 char* col_str = buffer_release( col_buf );
2432 char* val_str = buffer_release( val_buf );
2433 growing_buffer* sql = buffer_init( 128 );
2434 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2439 char* query = buffer_release( sql );
2441 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2443 jsonObject* obj = NULL;
2446 dbi_result result = dbi_conn_query( writehandle, query );
2448 obj = jsonNewObject( NULL );
2450 int errnum = dbi_conn_error( writehandle, &msg );
2453 "%s ERROR inserting %s object using query [%s]: %d %s",
2455 osrfHashGet(meta, "fieldmapper"),
2458 msg ? msg : "(No description available)"
2460 osrfAppSessionStatus(
2462 OSRF_STATUS_INTERNALSERVERERROR,
2463 "osrfMethodException",
2465 "INSERT error -- please see the error log for more details"
2467 if( !oilsIsDBConnected( writehandle ))
2468 osrfAppSessionPanic( ctx->session );
2471 dbi_result_free( result );
2473 char* id = oilsFMGetString( target, pkey );
2475 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2476 growing_buffer* _id = buffer_init( 10 );
2477 buffer_fadd( _id, "%lld", new_id );
2478 id = buffer_release( _id );
2481 // Find quietness specification, if present
2482 const char* quiet_str = NULL;
2484 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2486 quiet_str = jsonObjectGetString( quiet_obj );
2489 if( str_is_true( quiet_str )) { // if quietness is specified
2490 obj = jsonNewObject( id );
2494 // Fetch the row that we just inserted, so that we can return it to the client
2495 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2496 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2499 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2503 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2505 jsonObjectFree( list );
2506 jsonObjectFree( where_clause );
2513 osrfAppRespondComplete( ctx, obj );
2514 jsonObjectFree( obj );
2519 @brief Implement the retrieve method.
2520 @param ctx Pointer to the method context.
2521 @param err Pointer through which to return an error code.
2522 @return If successful, a pointer to the result to be returned to the client;
2525 From the method's class, fetch a row with a specified value in the primary key. This
2526 method relies on the database design convention that a primary key consists of a single
2530 - authkey (PCRUD only)
2531 - value of the primary key for the desired row, for building the WHERE clause
2532 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2534 Return to client: One row from the query.
2536 int doRetrieve( osrfMethodContext* ctx ) {
2537 if(osrfMethodVerifyContext( ctx )) {
2538 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2543 timeout_needs_resetting = 1;
2548 if( enforce_pcrud ) {
2553 // Get the class metadata
2554 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2556 // Get the value of the primary key, from a method parameter
2557 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2561 "%s retrieving %s object with primary key value of %s",
2563 osrfHashGet( class_def, "fieldmapper" ),
2564 jsonObjectGetString( id_obj )
2567 // Build a WHERE clause based on the key value
2568 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2571 osrfHashGet( class_def, "primarykey" ), // name of key column
2572 jsonObjectClone( id_obj ) // value of key column
2575 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2579 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2581 jsonObjectFree( where_clause );
2583 osrfAppRespondComplete( ctx, NULL );
2587 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2588 jsonObjectFree( list );
2590 if( enforce_pcrud ) {
2591 // no result, skip this entirely
2592 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2593 jsonObjectFree( obj );
2595 growing_buffer* msg = buffer_init( 128 );
2596 OSRF_BUFFER_ADD( msg, modulename );
2597 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2599 char* m = buffer_release( msg );
2600 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2604 osrfAppRespondComplete( ctx, NULL );
2609 // doFieldmapperSearch() now does the responding for us
2610 //osrfAppRespondComplete( ctx, obj );
2611 osrfAppRespondComplete( ctx, NULL );
2613 jsonObjectFree( obj );
2618 @brief Translate a numeric value to a string representation for the database.
2619 @param field Pointer to the IDL field definition.
2620 @param value Pointer to a jsonObject holding the value of a field.
2621 @return Pointer to a newly allocated string.
2623 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2624 its contents are numeric. A non-numeric string is likely to result in invalid SQL.
2626 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2628 The calling code is responsible for freeing the resulting string by calling free().
2630 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2631 growing_buffer* val_buf = buffer_init( 32 );
2633 // If the value is a number and the DB field is numeric, no quotes needed
2634 if( value->type == JSON_NUMBER && !strcmp( get_primitive( field ), "number") ) {
2635 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2637 // Presumably this was really intended to be a string, so quote it
2638 char* str = jsonObjectToSimpleString( value );
2639 if( dbi_conn_quote_string( dbhandle, &str )) {
2640 OSRF_BUFFER_ADD( val_buf, str );
2643 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2645 buffer_free( val_buf );
2650 return buffer_release( val_buf );
2653 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2654 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2655 growing_buffer* sql_buf = buffer_init( 32 );
2661 osrfHashGet( field, "name" )
2665 buffer_add( sql_buf, "IN (" );
2666 } else if( !strcasecmp( op,"not in" )) {
2667 buffer_add( sql_buf, "NOT IN (" );
2669 buffer_add( sql_buf, "IN (" );
2672 if( node->type == JSON_HASH ) {
2673 // subquery predicate
2674 char* subpred = buildQuery( ctx, node, SUBSELECT );
2676 buffer_free( sql_buf );
2680 buffer_add( sql_buf, subpred );
2683 } else if( node->type == JSON_ARRAY ) {
2684 // literal value list
2685 int in_item_index = 0;
2686 int in_item_first = 1;
2687 const jsonObject* in_item;
2688 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2693 buffer_add( sql_buf, ", " );
2696 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2697 osrfLogError( OSRF_LOG_MARK,
2698 "%s: Expected string or number within IN list; found %s",
2699 modulename, json_type( in_item->type ) );
2700 buffer_free( sql_buf );
2704 // Append the literal value -- quoted if not a number
2705 if( JSON_NUMBER == in_item->type ) {
2706 char* val = jsonNumberToDBString( field, in_item );
2707 OSRF_BUFFER_ADD( sql_buf, val );
2710 } else if( !strcmp( get_primitive( field ), "number" )) {
2711 char* val = jsonNumberToDBString( field, in_item );
2712 OSRF_BUFFER_ADD( sql_buf, val );
2716 char* key_string = jsonObjectToSimpleString( in_item );
2717 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2718 OSRF_BUFFER_ADD( sql_buf, key_string );
2721 osrfLogError( OSRF_LOG_MARK,
2722 "%s: Error quoting key string [%s]", modulename, key_string );
2724 buffer_free( sql_buf );
2730 if( in_item_first ) {
2731 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2732 buffer_free( sql_buf );
2736 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2737 modulename, json_type( node->type ));
2738 buffer_free( sql_buf );
2742 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2744 return buffer_release( sql_buf );
2747 // Receive a JSON_ARRAY representing a function call. The first
2748 // entry in the array is the function name. The rest are parameters.
2749 static char* searchValueTransform( const jsonObject* array ) {
2751 if( array->size < 1 ) {
2752 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2756 // Get the function name
2757 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2758 if( func_item->type != JSON_STRING ) {
2759 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2760 modulename, json_type( func_item->type ));
2764 growing_buffer* sql_buf = buffer_init( 32 );
2766 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2767 OSRF_BUFFER_ADD( sql_buf, "( " );
2769 // Get the parameters
2770 int func_item_index = 1; // We already grabbed the zeroth entry
2771 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2773 // Add a separator comma, if we need one
2774 if( func_item_index > 2 )
2775 buffer_add( sql_buf, ", " );
2777 // Add the current parameter
2778 if( func_item->type == JSON_NULL ) {
2779 buffer_add( sql_buf, "NULL" );
2781 if( func_item->type == JSON_BOOL ) {
2782 if( jsonBoolIsTrue(func_item) ) {
2783 buffer_add( sql_buf, "TRUE" );
2785 buffer_add( sql_buf, "FALSE" );
2788 char* val = jsonObjectToSimpleString( func_item );
2789 if( dbi_conn_quote_string( dbhandle, &val )) {
2790 OSRF_BUFFER_ADD( sql_buf, val );
2793 osrfLogError( OSRF_LOG_MARK,
2794 "%s: Error quoting key string [%s]", modulename, val );
2795 buffer_free( sql_buf );
2803 buffer_add( sql_buf, " )" );
2805 return buffer_release( sql_buf );
2808 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2809 const jsonObject* node, const char* op ) {
2811 if( ! is_good_operator( op ) ) {
2812 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2816 char* val = searchValueTransform( node );
2820 const char* right_percent = "";
2821 const char* real_op = op;
2823 if( !strcasecmp( op, "startwith") ) {
2825 right_percent = "|| '%'";
2828 growing_buffer* sql_buf = buffer_init( 32 );
2831 "\"%s\".%s %s %s%s",
2833 osrfHashGet( field, "name" ),
2841 return buffer_release( sql_buf );
2844 // class_alias is a class name or other table alias
2845 // field is a field definition as stored in the IDL
2846 // node comes from the method parameter, and may represent an entry in the SELECT list
2847 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2848 const jsonObject* node ) {
2849 growing_buffer* sql_buf = buffer_init( 32 );
2851 const char* field_transform = jsonObjectGetString(
2852 jsonObjectGetKeyConst( node, "transform" ) );
2853 const char* transform_subcolumn = jsonObjectGetString(
2854 jsonObjectGetKeyConst( node, "result_field" ) );
2856 if( transform_subcolumn ) {
2857 if( ! is_identifier( transform_subcolumn ) ) {
2858 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2859 modulename, transform_subcolumn );
2860 buffer_free( sql_buf );
2863 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2866 if( field_transform ) {
2868 if( ! is_identifier( field_transform ) ) {
2869 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2870 modulename, field_transform );
2871 buffer_free( sql_buf );
2875 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2876 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2877 field_transform, class_alias, osrfHashGet( field, "name" ));
2879 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2880 field_transform, class_alias, osrfHashGet( field, "name" ));
2883 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2886 if( array->type != JSON_ARRAY ) {
2887 osrfLogError( OSRF_LOG_MARK,
2888 "%s: Expected JSON_ARRAY for function params; found %s",
2889 modulename, json_type( array->type ) );
2890 buffer_free( sql_buf );
2893 int func_item_index = 0;
2894 jsonObject* func_item;
2895 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2897 char* val = jsonObjectToSimpleString( func_item );
2900 buffer_add( sql_buf, ",NULL" );
2901 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2902 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2903 OSRF_BUFFER_ADD( sql_buf, val );
2905 osrfLogError( OSRF_LOG_MARK,
2906 "%s: Error quoting key string [%s]", modulename, val );
2908 buffer_free( sql_buf );
2915 buffer_add( sql_buf, " )" );
2918 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2921 if( transform_subcolumn )
2922 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2924 return buffer_release( sql_buf );
2927 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2928 const jsonObject* node, const char* op ) {
2930 if( ! is_good_operator( op ) ) {
2931 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2935 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2936 if( ! field_transform )
2939 int extra_parens = 0; // boolean
2941 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2943 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2945 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2947 free( field_transform );
2951 } else if( value_obj->type == JSON_ARRAY ) {
2952 value = searchValueTransform( value_obj );
2954 osrfLogError( OSRF_LOG_MARK,
2955 "%s: Error building value transform for field transform", modulename );
2956 free( field_transform );
2959 } else if( value_obj->type == JSON_HASH ) {
2960 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2962 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2964 free( field_transform );
2968 } else if( value_obj->type == JSON_NUMBER ) {
2969 value = jsonNumberToDBString( field, value_obj );
2970 } else if( value_obj->type == JSON_NULL ) {
2971 osrfLogError( OSRF_LOG_MARK,
2972 "%s: Error building predicate for field transform: null value", modulename );
2973 free( field_transform );
2975 } else if( value_obj->type == JSON_BOOL ) {
2976 osrfLogError( OSRF_LOG_MARK,
2977 "%s: Error building predicate for field transform: boolean value", modulename );
2978 free( field_transform );
2981 if( !strcmp( get_primitive( field ), "number") ) {
2982 value = jsonNumberToDBString( field, value_obj );
2984 value = jsonObjectToSimpleString( value_obj );
2985 if( !dbi_conn_quote_string( dbhandle, &value )) {
2986 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2987 modulename, value );
2989 free( field_transform );
2995 const char* left_parens = "";
2996 const char* right_parens = "";
2998 if( extra_parens ) {
3003 const char* right_percent = "";
3004 const char* real_op = op;
3006 if( !strcasecmp( op, "startwith") ) {
3008 right_percent = "|| '%'";
3011 growing_buffer* sql_buf = buffer_init( 32 );
3015 "%s%s %s %s %s%s %s%s",
3027 free( field_transform );
3029 return buffer_release( sql_buf );
3032 static char* searchSimplePredicate( const char* op, const char* class_alias,
3033 osrfHash* field, const jsonObject* node ) {
3035 if( ! is_good_operator( op ) ) {
3036 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
3042 // Get the value to which we are comparing the specified column
3043 if( node->type != JSON_NULL ) {
3044 if( node->type == JSON_NUMBER ) {
3045 val = jsonNumberToDBString( field, node );
3046 } else if( !strcmp( get_primitive( field ), "number" ) ) {
3047 val = jsonNumberToDBString( field, node );
3049 val = jsonObjectToSimpleString( node );
3054 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
3055 // Value is not numeric; enclose it in quotes
3056 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
3057 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3064 // Compare to a null value
3065 val = strdup( "NULL" );
3066 if( strcmp( op, "=" ))
3072 const char* right_percent = "";
3073 const char* real_op = op;
3075 if( !strcasecmp( op, "startwith") ) {
3077 right_percent = "|| '%'";
3080 growing_buffer* sql_buf = buffer_init( 32 );
3081 buffer_fadd( sql_buf, "\"%s\".%s %s %s%s", class_alias, osrfHashGet(field, "name"), real_op, val, right_percent );
3082 char* pred = buffer_release( sql_buf );
3089 static char* searchBETWEENPredicate( const char* class_alias,
3090 osrfHash* field, const jsonObject* node ) {
3092 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
3093 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
3095 if( NULL == y_node ) {
3096 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
3099 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
3100 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
3107 if( !strcmp( get_primitive( field ), "number") ) {
3108 x_string = jsonNumberToDBString( field, x_node );
3109 y_string = jsonNumberToDBString( field, y_node );
3112 x_string = jsonObjectToSimpleString( x_node );
3113 y_string = jsonObjectToSimpleString( y_node );
3114 if( !(dbi_conn_quote_string( dbhandle, &x_string )
3115 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
3116 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
3117 modulename, x_string, y_string );
3124 growing_buffer* sql_buf = buffer_init( 32 );
3125 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
3126 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
3130 return buffer_release( sql_buf );
3133 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
3134 jsonObject* node, osrfMethodContext* ctx ) {
3137 if( node->type == JSON_ARRAY ) { // equality IN search
3138 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
3139 } else if( node->type == JSON_HASH ) { // other search
3140 jsonIterator* pred_itr = jsonNewIterator( node );
3141 if( !jsonIteratorHasNext( pred_itr ) ) {
3142 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
3143 modulename, osrfHashGet(field, "name" ));
3145 jsonObject* pred_node = jsonIteratorNext( pred_itr );
3147 // Verify that there are no additional predicates
3148 if( jsonIteratorHasNext( pred_itr ) ) {
3149 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
3150 modulename, osrfHashGet(field, "name" ));
3151 } else if( !(strcasecmp( pred_itr->key,"between" )) )
3152 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
3153 else if( !(strcasecmp( pred_itr->key,"in" ))
3154 || !(strcasecmp( pred_itr->key,"not in" )) )
3155 pred = searchINPredicate(
3156 class_info->alias, field, pred_node, pred_itr->key, ctx );
3157 else if( pred_node->type == JSON_ARRAY )
3158 pred = searchFunctionPredicate(
3159 class_info->alias, field, pred_node, pred_itr->key );
3160 else if( pred_node->type == JSON_HASH )
3161 pred = searchFieldTransformPredicate(
3162 class_info, field, pred_node, pred_itr->key );
3164 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
3166 jsonIteratorFree( pred_itr );
3168 } else if( node->type == JSON_NULL ) { // IS NULL search
3169 growing_buffer* _p = buffer_init( 64 );
3172 "\"%s\".%s IS NULL",
3174 osrfHashGet( field, "name" )
3176 pred = buffer_release( _p );
3177 } else { // equality search
3178 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3197 field : call_number,
3213 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3215 const jsonObject* working_hash;
3216 jsonObject* freeable_hash = NULL;
3218 if( join_hash->type == JSON_HASH ) {
3219 working_hash = join_hash;
3220 } else if( join_hash->type == JSON_STRING ) {
3221 // turn it into a JSON_HASH by creating a wrapper
3222 // around a copy of the original
3223 const char* _tmp = jsonObjectGetString( join_hash );
3224 freeable_hash = jsonNewObjectType( JSON_HASH );
3225 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3226 working_hash = freeable_hash;
3230 "%s: JOIN failed; expected JSON object type not found",
3236 growing_buffer* join_buf = buffer_init( 128 );
3237 const char* leftclass = left_info->class_name;
3239 jsonObject* snode = NULL;
3240 jsonIterator* search_itr = jsonNewIterator( working_hash );
3242 while ( (snode = jsonIteratorNext( search_itr )) ) {
3243 const char* right_alias = search_itr->key;
3245 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3247 class = right_alias;
3249 const ClassInfo* right_info = add_joined_class( right_alias, class );
3253 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3257 jsonIteratorFree( search_itr );
3258 buffer_free( join_buf );
3260 jsonObjectFree( freeable_hash );
3263 osrfHash* links = right_info->links;
3264 const char* table = right_info->source_def;
3266 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3267 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3269 if( field && !fkey ) {
3270 // Look up the corresponding join column in the IDL.
3271 // The link must be defined in the child table,
3272 // and point to the right parent table.
3273 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3274 const char* reltype = NULL;
3275 const char* other_class = NULL;
3276 reltype = osrfHashGet( idl_link, "reltype" );
3277 if( reltype && strcmp( reltype, "has_many" ) )
3278 other_class = osrfHashGet( idl_link, "class" );
3279 if( other_class && !strcmp( other_class, leftclass ) )
3280 fkey = osrfHashGet( idl_link, "key" );
3284 "%s: JOIN failed. No link defined from %s.%s to %s",
3290 buffer_free( join_buf );
3292 jsonObjectFree( freeable_hash );
3293 jsonIteratorFree( search_itr );
3297 } else if( !field && fkey ) {
3298 // Look up the corresponding join column in the IDL.
3299 // The link must be defined in the child table,
3300 // and point to the right parent table.
3301 osrfHash* left_links = left_info->links;
3302 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3303 const char* reltype = NULL;
3304 const char* other_class = NULL;
3305 reltype = osrfHashGet( idl_link, "reltype" );
3306 if( reltype && strcmp( reltype, "has_many" ) )
3307 other_class = osrfHashGet( idl_link, "class" );
3308 if( other_class && !strcmp( other_class, class ) )
3309 field = osrfHashGet( idl_link, "key" );
3313 "%s: JOIN failed. No link defined from %s.%s to %s",
3319 buffer_free( join_buf );
3321 jsonObjectFree( freeable_hash );
3322 jsonIteratorFree( search_itr );
3326 } else if( !field && !fkey ) {
3327 osrfHash* left_links = left_info->links;
3329 // For each link defined for the left class:
3330 // see if the link references the joined class
3331 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3332 osrfHash* curr_link = NULL;
3333 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3334 const char* other_class = osrfHashGet( curr_link, "class" );
3335 if( other_class && !strcmp( other_class, class ) ) {
3337 // In the IDL, the parent class doesn't always know then names of the child
3338 // columns that are pointing to it, so don't use that end of the link
3339 const char* reltype = osrfHashGet( curr_link, "reltype" );
3340 if( reltype && strcmp( reltype, "has_many" ) ) {
3341 // Found a link between the classes
3342 fkey = osrfHashIteratorKey( itr );
3343 field = osrfHashGet( curr_link, "key" );
3348 osrfHashIteratorFree( itr );
3350 if( !field || !fkey ) {
3351 // Do another such search, with the classes reversed
3353 // For each link defined for the joined class:
3354 // see if the link references the left class
3355 osrfHashIterator* itr = osrfNewHashIterator( links );
3356 osrfHash* curr_link = NULL;
3357 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3358 const char* other_class = osrfHashGet( curr_link, "class" );
3359 if( other_class && !strcmp( other_class, leftclass ) ) {
3361 // In the IDL, the parent class doesn't know then names of the child
3362 // columns that are pointing to it, so don't use that end of the link
3363 const char* reltype = osrfHashGet( curr_link, "reltype" );
3364 if( reltype && strcmp( reltype, "has_many" ) ) {
3365 // Found a link between the classes
3366 field = osrfHashIteratorKey( itr );
3367 fkey = osrfHashGet( curr_link, "key" );
3372 osrfHashIteratorFree( itr );
3375 if( !field || !fkey ) {
3378 "%s: JOIN failed. No link defined between %s and %s",
3383 buffer_free( join_buf );
3385 jsonObjectFree( freeable_hash );
3386 jsonIteratorFree( search_itr );
3391 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3393 if( !strcasecmp( type,"left" )) {
3394 buffer_add( join_buf, " LEFT JOIN" );
3395 } else if( !strcasecmp( type,"right" )) {
3396 buffer_add( join_buf, " RIGHT JOIN" );
3397 } else if( !strcasecmp( type,"full" )) {
3398 buffer_add( join_buf, " FULL JOIN" );
3400 buffer_add( join_buf, " INNER JOIN" );
3403 buffer_add( join_buf, " INNER JOIN" );
3406 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3407 table, right_alias, right_alias, field, left_info->alias, fkey );
3409 // Add any other join conditions as specified by "filter"
3410 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3412 const char* filter_op = jsonObjectGetString(
3413 jsonObjectGetKeyConst( snode, "filter_op" ) );
3414 if( filter_op && !strcasecmp( "or",filter_op )) {
3415 buffer_add( join_buf, " OR " );
3417 buffer_add( join_buf, " AND " );
3420 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3422 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3423 OSRF_BUFFER_ADD( join_buf, jpred );
3428 "%s: JOIN failed. Invalid conditional expression.",
3431 jsonIteratorFree( search_itr );
3432 buffer_free( join_buf );
3434 jsonObjectFree( freeable_hash );
3439 buffer_add( join_buf, " ) " );
3441 // Recursively add a nested join, if one is present
3442 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3444 char* jpred = searchJOIN( join_filter, right_info );
3446 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3447 OSRF_BUFFER_ADD( join_buf, jpred );
3450 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3451 jsonIteratorFree( search_itr );
3452 buffer_free( join_buf );
3454 jsonObjectFree( freeable_hash );
3461 jsonObjectFree( freeable_hash );
3462 jsonIteratorFree( search_itr );
3464 return buffer_release( join_buf );
3469 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3470 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3471 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3473 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3475 search_hash is the JSON expression of the conditions.
3476 meta is the class definition from the IDL, for the relevant table.
3477 opjoin_type indicates whether multiple conditions, if present, should be
3478 connected by AND or OR.
3479 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3480 to pass it to other functions -- and all they do with it is to use the session
3481 and request members to send error messages back to the client.
3485 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3486 int opjoin_type, osrfMethodContext* ctx ) {
3490 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3491 "opjoin_type = %d, ctx addr = %p",
3494 class_info->class_def,
3499 growing_buffer* sql_buf = buffer_init( 128 );
3501 jsonObject* node = NULL;
3504 if( search_hash->type == JSON_ARRAY ) {
3505 if( 0 == search_hash->size ) {
3508 "%s: Invalid predicate structure: empty JSON array",
3511 buffer_free( sql_buf );
3515 unsigned long i = 0;
3516 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3520 if( opjoin_type == OR_OP_JOIN )
3521 buffer_add( sql_buf, " OR " );
3523 buffer_add( sql_buf, " AND " );
3526 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3528 buffer_free( sql_buf );
3532 buffer_fadd( sql_buf, "( %s )", subpred );
3536 } else if( search_hash->type == JSON_HASH ) {
3537 osrfLogDebug( OSRF_LOG_MARK,
3538 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3539 jsonIterator* search_itr = jsonNewIterator( search_hash );
3540 if( !jsonIteratorHasNext( search_itr ) ) {
3543 "%s: Invalid predicate structure: empty JSON object",
3546 jsonIteratorFree( search_itr );
3547 buffer_free( sql_buf );
3551 while( (node = jsonIteratorNext( search_itr )) ) {
3556 if( opjoin_type == OR_OP_JOIN )
3557 buffer_add( sql_buf, " OR " );
3559 buffer_add( sql_buf, " AND " );
3562 if( '+' == search_itr->key[ 0 ] ) {
3564 // This plus sign prefixes a class name or other table alias;
3565 // make sure the table alias is in scope
3566 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3567 if( ! alias_info ) {
3570 "%s: Invalid table alias \"%s\" in WHERE clause",
3574 jsonIteratorFree( search_itr );
3575 buffer_free( sql_buf );
3579 if( node->type == JSON_STRING ) {
3580 // It's the name of a column; make sure it belongs to the class
3581 const char* fieldname = jsonObjectGetString( node );
3582 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3585 "%s: Invalid column name \"%s\" in WHERE clause "
3586 "for table alias \"%s\"",
3591 jsonIteratorFree( search_itr );
3592 buffer_free( sql_buf );
3596 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3598 // It's something more complicated
3599 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3601 jsonIteratorFree( search_itr );
3602 buffer_free( sql_buf );
3606 buffer_fadd( sql_buf, "( %s )", subpred );
3609 } else if( '-' == search_itr->key[ 0 ] ) {
3610 if( !strcasecmp( "-or", search_itr->key )) {
3611 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3613 jsonIteratorFree( search_itr );
3614 buffer_free( sql_buf );
3618 buffer_fadd( sql_buf, "( %s )", subpred );
3620 } else if( !strcasecmp( "-and", search_itr->key )) {
3621 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3623 jsonIteratorFree( search_itr );
3624 buffer_free( sql_buf );
3628 buffer_fadd( sql_buf, "( %s )", subpred );
3630 } else if( !strcasecmp("-not",search_itr->key) ) {
3631 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3633 jsonIteratorFree( search_itr );
3634 buffer_free( sql_buf );
3638 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3640 } else if( !strcasecmp( "-exists", search_itr->key )) {
3641 char* subpred = buildQuery( ctx, node, SUBSELECT );
3643 jsonIteratorFree( search_itr );
3644 buffer_free( sql_buf );
3648 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3650 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3651 char* subpred = buildQuery( ctx, node, SUBSELECT );
3653 jsonIteratorFree( search_itr );
3654 buffer_free( sql_buf );
3658 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3660 } else { // Invalid "minus" operator
3663 "%s: Invalid operator \"%s\" in WHERE clause",
3667 jsonIteratorFree( search_itr );
3668 buffer_free( sql_buf );
3674 const char* class = class_info->class_name;
3675 osrfHash* fields = class_info->fields;
3676 osrfHash* field = osrfHashGet( fields, search_itr->key );
3679 const char* table = class_info->source_def;
3682 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3685 table ? table : "?",
3688 jsonIteratorFree( search_itr );
3689 buffer_free( sql_buf );
3693 char* subpred = searchPredicate( class_info, field, node, ctx );
3695 buffer_free( sql_buf );
3696 jsonIteratorFree( search_itr );
3700 buffer_add( sql_buf, subpred );
3704 jsonIteratorFree( search_itr );
3707 // ERROR ... only hash and array allowed at this level
3708 char* predicate_string = jsonObjectToJSON( search_hash );
3711 "%s: Invalid predicate structure: %s",
3715 buffer_free( sql_buf );
3716 free( predicate_string );
3720 return buffer_release( sql_buf );
3723 /* Build a JSON_ARRAY of field names for a given table alias
3725 static jsonObject* defaultSelectList( const char* table_alias ) {
3730 ClassInfo* class_info = search_all_alias( table_alias );
3731 if( ! class_info ) {
3734 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3741 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3742 osrfHash* field_def = NULL;
3743 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3744 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3745 const char* field_name = osrfHashIteratorKey( field_itr );
3746 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3747 jsonObjectPush( array, jsonNewObject( field_name ) );
3750 osrfHashIteratorFree( field_itr );
3755 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3756 // The jsonObject must be a JSON_HASH with an single entry for "union",
3757 // "intersect", or "except". The data associated with this key must be an
3758 // array of hashes, each hash being a query.
3759 // Also allowed but currently ignored: entries for "order_by" and "alias".
3760 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3762 if( ! combo || combo->type != JSON_HASH )
3763 return NULL; // should be impossible; validated by caller
3765 const jsonObject* query_array = NULL; // array of subordinate queries
3766 const char* op = NULL; // name of operator, e.g. UNION
3767 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3768 int op_count = 0; // for detecting conflicting operators
3769 int excepting = 0; // boolean
3770 int all = 0; // boolean
3771 jsonObject* order_obj = NULL;
3773 // Identify the elements in the hash
3774 jsonIterator* query_itr = jsonNewIterator( combo );
3775 jsonObject* curr_obj = NULL;
3776 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3777 if( ! strcmp( "union", query_itr->key ) ) {
3780 query_array = curr_obj;
3781 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3784 query_array = curr_obj;
3785 } else if( ! strcmp( "except", query_itr->key ) ) {
3789 query_array = curr_obj;
3790 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3793 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3796 order_obj = curr_obj;
3797 } else if( ! strcmp( "alias", query_itr->key ) ) {
3798 if( curr_obj->type != JSON_STRING ) {
3799 jsonIteratorFree( query_itr );
3802 alias = jsonObjectGetString( curr_obj );
3803 } else if( ! strcmp( "all", query_itr->key ) ) {
3804 if( obj_is_true( curr_obj ) )
3808 osrfAppSessionStatus(
3810 OSRF_STATUS_INTERNALSERVERERROR,
3811 "osrfMethodException",
3813 "Malformed query; unexpected entry in query object"
3817 "%s: Unexpected entry for \"%s\" in%squery",
3822 jsonIteratorFree( query_itr );
3826 jsonIteratorFree( query_itr );
3828 // More sanity checks
3829 if( ! query_array ) {
3831 osrfAppSessionStatus(
3833 OSRF_STATUS_INTERNALSERVERERROR,
3834 "osrfMethodException",
3836 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3840 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3843 return NULL; // should be impossible...
3844 } else if( op_count > 1 ) {
3846 osrfAppSessionStatus(
3848 OSRF_STATUS_INTERNALSERVERERROR,
3849 "osrfMethodException",
3851 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3855 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3859 } if( query_array->type != JSON_ARRAY ) {
3861 osrfAppSessionStatus(
3863 OSRF_STATUS_INTERNALSERVERERROR,
3864 "osrfMethodException",
3866 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3870 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3873 json_type( query_array->type )
3876 } if( query_array->size < 2 ) {
3878 osrfAppSessionStatus(
3880 OSRF_STATUS_INTERNALSERVERERROR,
3881 "osrfMethodException",
3883 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3887 "%s:%srequires multiple queries as operands",
3892 } else if( excepting && query_array->size > 2 ) {
3894 osrfAppSessionStatus(
3896 OSRF_STATUS_INTERNALSERVERERROR,
3897 "osrfMethodException",
3899 "EXCEPT operator has too many queries as operands"
3903 "%s:EXCEPT operator has too many queries as operands",
3907 } else if( order_obj && ! alias ) {
3909 osrfAppSessionStatus(
3911 OSRF_STATUS_INTERNALSERVERERROR,
3912 "osrfMethodException",
3914 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3918 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3924 // So far so good. Now build the SQL.
3925 growing_buffer* sql = buffer_init( 256 );
3927 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3928 // Add a layer of parentheses
3929 if( flags & SUBCOMBO )
3930 OSRF_BUFFER_ADD( sql, "( " );
3932 // Traverse the query array. Each entry should be a hash.
3933 int first = 1; // boolean
3935 jsonObject* query = NULL;
3936 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3937 if( query->type != JSON_HASH ) {
3939 osrfAppSessionStatus(
3941 OSRF_STATUS_INTERNALSERVERERROR,
3942 "osrfMethodException",
3944 "Malformed query under UNION, INTERSECT or EXCEPT"
3948 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3951 json_type( query->type )
3960 OSRF_BUFFER_ADD( sql, op );
3962 OSRF_BUFFER_ADD( sql, "ALL " );
3965 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3969 "%s: Error building query under%s",
3977 OSRF_BUFFER_ADD( sql, query_str );
3980 if( flags & SUBCOMBO )
3981 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3983 if( !(flags & SUBSELECT) )
3984 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3986 return buffer_release( sql );
3989 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3990 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3991 // or "except" to indicate the type of query.
3992 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3996 osrfAppSessionStatus(
3998 OSRF_STATUS_INTERNALSERVERERROR,
3999 "osrfMethodException",
4001 "Malformed query; no query object"
4003 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
4005 } else if( query->type != JSON_HASH ) {
4007 osrfAppSessionStatus(
4009 OSRF_STATUS_INTERNALSERVERERROR,
4010 "osrfMethodException",
4012 "Malformed query object"
4016 "%s: Query object is %s instead of JSON_HASH",
4018 json_type( query->type )
4023 // Determine what kind of query it purports to be, and dispatch accordingly.
4024 if( jsonObjectGetKeyConst( query, "union" ) ||
4025 jsonObjectGetKeyConst( query, "intersect" ) ||
4026 jsonObjectGetKeyConst( query, "except" )) {
4027 return doCombo( ctx, query, flags );
4029 // It is presumably a SELECT query
4031 // Push a node onto the stack for the current query. Every level of
4032 // subquery gets its own QueryFrame on the Stack.
4035 // Build an SQL SELECT statement
4038 jsonObjectGetKey( query, "select" ),
4039 jsonObjectGetKeyConst( query, "from" ),
4040 jsonObjectGetKeyConst( query, "where" ),
4041 jsonObjectGetKeyConst( query, "having" ),
4042 jsonObjectGetKeyConst( query, "order_by" ),
4043 jsonObjectGetKeyConst( query, "limit" ),
4044 jsonObjectGetKeyConst( query, "offset" ),
4053 /* method context */ osrfMethodContext* ctx,
4055 /* SELECT */ jsonObject* selhash,
4056 /* FROM */ const jsonObject* join_hash,
4057 /* WHERE */ const jsonObject* search_hash,
4058 /* HAVING */ const jsonObject* having_hash,
4059 /* ORDER BY */ const jsonObject* order_hash,
4060 /* LIMIT */ const jsonObject* limit,
4061 /* OFFSET */ const jsonObject* offset,
4062 /* flags */ int flags
4064 const char* locale = osrf_message_get_last_locale();
4066 // general tmp objects
4067 const jsonObject* tmp_const;
4068 jsonObject* selclass = NULL;
4069 jsonObject* snode = NULL;
4070 jsonObject* onode = NULL;
4072 char* string = NULL;
4073 int from_function = 0;
4078 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
4080 // punt if there's no FROM clause
4081 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
4084 "%s: FROM clause is missing or empty",
4088 osrfAppSessionStatus(
4090 OSRF_STATUS_INTERNALSERVERERROR,
4091 "osrfMethodException",
4093 "FROM clause is missing or empty in JSON query"
4098 // the core search class
4099 const char* core_class = NULL;
4101 // get the core class -- the only key of the top level FROM clause, or a string
4102 if( join_hash->type == JSON_HASH ) {
4103 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
4104 snode = jsonIteratorNext( tmp_itr );
4106 // Populate the current QueryFrame with information
4107 // about the core class
4108 if( add_query_core( NULL, tmp_itr->key ) ) {
4110 osrfAppSessionStatus(
4112 OSRF_STATUS_INTERNALSERVERERROR,
4113 "osrfMethodException",
4115 "Unable to look up core class"
4119 core_class = curr_query->core.class_name;
4122 jsonObject* extra = jsonIteratorNext( tmp_itr );
4124 jsonIteratorFree( tmp_itr );
4127 // There shouldn't be more than one entry in join_hash
4131 "%s: Malformed FROM clause: extra entry in JSON_HASH",
4135 osrfAppSessionStatus(
4137 OSRF_STATUS_INTERNALSERVERERROR,
4138 "osrfMethodException",
4140 "Malformed FROM clause in JSON query"
4142 return NULL; // Malformed join_hash; extra entry
4144 } else if( join_hash->type == JSON_ARRAY ) {
4145 // We're selecting from a function, not from a table
4147 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
4150 } else if( join_hash->type == JSON_STRING ) {
4151 // Populate the current QueryFrame with information
4152 // about the core class
4153 core_class = jsonObjectGetString( join_hash );
4155 if( add_query_core( NULL, core_class ) ) {
4157 osrfAppSessionStatus(
4159 OSRF_STATUS_INTERNALSERVERERROR,
4160 "osrfMethodException",
4162 "Unable to look up core class"
4170 "%s: FROM clause is unexpected JSON type: %s",
4172 json_type( join_hash->type )
4175 osrfAppSessionStatus(
4177 OSRF_STATUS_INTERNALSERVERERROR,
4178 "osrfMethodException",
4180 "Ill-formed FROM clause in JSON query"
4185 // Build the join clause, if any, while filling out the list
4186 // of joined classes in the current QueryFrame.
4187 char* join_clause = NULL;
4188 if( join_hash && ! from_function ) {
4190 join_clause = searchJOIN( join_hash, &curr_query->core );
4191 if( ! join_clause ) {
4193 osrfAppSessionStatus(
4195 OSRF_STATUS_INTERNALSERVERERROR,
4196 "osrfMethodException",
4198 "Unable to construct JOIN clause(s)"
4204 // For in case we don't get a select list
4205 jsonObject* defaultselhash = NULL;
4207 // if there is no select list, build a default select list ...
4208 if( !selhash && !from_function ) {
4209 jsonObject* default_list = defaultSelectList( core_class );
4210 if( ! default_list ) {
4212 osrfAppSessionStatus(
4214 OSRF_STATUS_INTERNALSERVERERROR,
4215 "osrfMethodException",
4217 "Unable to build default SELECT clause in JSON query"
4219 free( join_clause );
4224 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4225 jsonObjectSetKey( selhash, core_class, default_list );
4228 // The SELECT clause can be encoded only by a hash
4229 if( !from_function && selhash->type != JSON_HASH ) {
4232 "%s: Expected JSON_HASH for SELECT clause; found %s",
4234 json_type( selhash->type )
4238 osrfAppSessionStatus(
4240 OSRF_STATUS_INTERNALSERVERERROR,
4241 "osrfMethodException",
4243 "Malformed SELECT clause in JSON query"
4245 free( join_clause );
4249 // If you see a null or wild card specifier for the core class, or an
4250 // empty array, replace it with a default SELECT list
4251 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4253 int default_needed = 0; // boolean
4254 if( JSON_STRING == tmp_const->type
4255 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4257 else if( JSON_NULL == tmp_const->type )
4260 if( default_needed ) {
4261 // Build a default SELECT list
4262 jsonObject* default_list = defaultSelectList( core_class );
4263 if( ! default_list ) {
4265 osrfAppSessionStatus(
4267 OSRF_STATUS_INTERNALSERVERERROR,
4268 "osrfMethodException",
4270 "Can't build default SELECT clause in JSON query"
4272 free( join_clause );
4277 jsonObjectSetKey( selhash, core_class, default_list );
4281 // temp buffers for the SELECT list and GROUP BY clause
4282 growing_buffer* select_buf = buffer_init( 128 );
4283 growing_buffer* group_buf = buffer_init( 128 );
4285 int aggregate_found = 0; // boolean
4287 // Build a select list
4288 if( from_function ) // From a function we select everything
4289 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4292 // Build the SELECT list as SQL
4296 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4297 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4299 const char* cname = selclass_itr->key;
4301 // Make sure the target relation is in the FROM clause.
4303 // At this point join_hash is a step down from the join_hash we
4304 // received as a parameter. If the original was a JSON_STRING,
4305 // then json_hash is now NULL. If the original was a JSON_HASH,
4306 // then json_hash is now the first (and only) entry in it,
4307 // denoting the core class. We've already excluded the
4308 // possibility that the original was a JSON_ARRAY, because in
4309 // that case from_function would be non-NULL, and we wouldn't
4312 // If the current table alias isn't in scope, bail out
4313 ClassInfo* class_info = search_alias( cname );
4314 if( ! class_info ) {
4317 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4322 osrfAppSessionStatus(
4324 OSRF_STATUS_INTERNALSERVERERROR,
4325 "osrfMethodException",
4327 "Selected class not in FROM clause in JSON query"
4329 jsonIteratorFree( selclass_itr );
4330 buffer_free( select_buf );
4331 buffer_free( group_buf );
4332 if( defaultselhash )
4333 jsonObjectFree( defaultselhash );
4334 free( join_clause );
4338 if( selclass->type != JSON_ARRAY ) {
4341 "%s: Malformed SELECT list for class \"%s\"; not an array",
4346 osrfAppSessionStatus(
4348 OSRF_STATUS_INTERNALSERVERERROR,
4349 "osrfMethodException",
4351 "Selected class not in FROM clause in JSON query"
4354 jsonIteratorFree( selclass_itr );
4355 buffer_free( select_buf );
4356 buffer_free( group_buf );
4357 if( defaultselhash )
4358 jsonObjectFree( defaultselhash );
4359 free( join_clause );
4363 // Look up some attributes of the current class
4364 osrfHash* idlClass = class_info->class_def;
4365 osrfHash* class_field_set = class_info->fields;
4366 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4367 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4369 if( 0 == selclass->size ) {
4372 "%s: No columns selected from \"%s\"",
4378 // stitch together the column list for the current table alias...
4379 unsigned long field_idx = 0;
4380 jsonObject* selfield = NULL;
4381 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4383 // If we need a separator comma, add one
4387 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4390 // if the field specification is a string, add it to the list
4391 if( selfield->type == JSON_STRING ) {
4393 // Look up the field in the IDL
4394 const char* col_name = jsonObjectGetString( selfield );
4395 osrfHash* field_def = NULL;
4397 if (!osrfStringArrayContains(
4399 osrfHashGet( class_field_set, col_name ),
4400 "suppress_controller"),
4403 field_def = osrfHashGet( class_field_set, col_name );
4406 // No such field in current class
4409 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4415 osrfAppSessionStatus(
4417 OSRF_STATUS_INTERNALSERVERERROR,
4418 "osrfMethodException",
4420 "Selected column not defined in JSON query"
4422 jsonIteratorFree( selclass_itr );
4423 buffer_free( select_buf );
4424 buffer_free( group_buf );
4425 if( defaultselhash )
4426 jsonObjectFree( defaultselhash );
4427 free( join_clause );
4429 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4430 // Virtual field not allowed
4433 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4439 osrfAppSessionStatus(
4441 OSRF_STATUS_INTERNALSERVERERROR,
4442 "osrfMethodException",
4444 "Selected column may not be virtual in JSON query"
4446 jsonIteratorFree( selclass_itr );
4447 buffer_free( select_buf );
4448 buffer_free( group_buf );
4449 if( defaultselhash )
4450 jsonObjectFree( defaultselhash );
4451 free( join_clause );
4457 if( flags & DISABLE_I18N )
4460 i18n = osrfHashGet( field_def, "i18n" );
4462 if( str_is_true( i18n ) ) {
4463 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4464 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4465 class_tname, cname, col_name, class_pkey,
4466 cname, class_pkey, locale, col_name );
4468 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4469 cname, col_name, col_name );
4472 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4473 cname, col_name, col_name );
4476 // ... but it could be an object, in which case we check for a Field Transform
4477 } else if( selfield->type == JSON_HASH ) {
4479 const char* col_name = jsonObjectGetString(
4480 jsonObjectGetKeyConst( selfield, "column" ) );
4482 // Get the field definition from the IDL
4483 osrfHash* field_def = NULL;
4484 if (!osrfStringArrayContains(
4486 osrfHashGet( class_field_set, col_name ),
4487 "suppress_controller"),
4490 field_def = osrfHashGet( class_field_set, col_name );
4494 // No such field in current class
4497 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4503 osrfAppSessionStatus(
4505 OSRF_STATUS_INTERNALSERVERERROR,
4506 "osrfMethodException",
4508 "Selected column is not defined in JSON query"
4510 jsonIteratorFree( selclass_itr );
4511 buffer_free( select_buf );
4512 buffer_free( group_buf );
4513 if( defaultselhash )
4514 jsonObjectFree( defaultselhash );
4515 free( join_clause );
4517 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4518 // No such field in current class
4521 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4527 osrfAppSessionStatus(
4529 OSRF_STATUS_INTERNALSERVERERROR,
4530 "osrfMethodException",
4532 "Selected column is virtual in JSON query"
4534 jsonIteratorFree( selclass_itr );
4535 buffer_free( select_buf );
4536 buffer_free( group_buf );
4537 if( defaultselhash )
4538 jsonObjectFree( defaultselhash );
4539 free( join_clause );
4543 // Decide what to use as a column alias
4545 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4546 _alias = jsonObjectGetString( tmp_const );
4547 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4548 _alias = jsonObjectGetString( tmp_const );
4549 } else { // Use field name as the alias
4553 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4554 char* transform_str = searchFieldTransform(
4555 class_info->alias, field_def, selfield );
4556 if( transform_str ) {
4557 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4558 free( transform_str );
4561 osrfAppSessionStatus(
4563 OSRF_STATUS_INTERNALSERVERERROR,
4564 "osrfMethodException",
4566 "Unable to generate transform function in JSON query"
4568 jsonIteratorFree( selclass_itr );
4569 buffer_free( select_buf );
4570 buffer_free( group_buf );
4571 if( defaultselhash )
4572 jsonObjectFree( defaultselhash );
4573 free( join_clause );
4580 if( flags & DISABLE_I18N )
4583 i18n = osrfHashGet( field_def, "i18n" );
4585 if( str_is_true( i18n ) ) {
4586 buffer_fadd( select_buf,
4587 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4588 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4589 class_tname, cname, col_name, class_pkey, cname,
4590 class_pkey, locale, _alias );
4592 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4593 cname, col_name, _alias );
4596 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4597 cname, col_name, _alias );
4604 "%s: Selected item is unexpected JSON type: %s",
4606 json_type( selfield->type )
4609 osrfAppSessionStatus(
4611 OSRF_STATUS_INTERNALSERVERERROR,
4612 "osrfMethodException",
4614 "Ill-formed SELECT item in JSON query"
4616 jsonIteratorFree( selclass_itr );
4617 buffer_free( select_buf );
4618 buffer_free( group_buf );
4619 if( defaultselhash )
4620 jsonObjectFree( defaultselhash );
4621 free( join_clause );
4625 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4626 if( obj_is_true( agg_obj ) )
4627 aggregate_found = 1;
4629 // Append a comma (except for the first one)
4630 // and add the column to a GROUP BY clause
4634 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4636 buffer_fadd( group_buf, " %d", sel_pos );
4640 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4642 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( selfield, "aggregate");
4643 if ( ! obj_is_true( aggregate_obj ) ) {
4647 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4650 buffer_fadd(group_buf, " %d", sel_pos);
4653 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4657 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4660 _column = searchFieldTransform(class_info->alias, field, selfield);
4661 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4662 OSRF_BUFFER_ADD(group_buf, _column);
4663 _column = searchFieldTransform(class_info->alias, field, selfield);
4670 } // end while -- iterating across SELECT columns
4672 } // end while -- iterating across classes
4674 jsonIteratorFree( selclass_itr );
4677 char* col_list = buffer_release( select_buf );
4679 // Make sure the SELECT list isn't empty. This can happen, for example,
4680 // if we try to build a default SELECT clause from a non-core table.
4683 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4685 osrfAppSessionStatus(
4687 OSRF_STATUS_INTERNALSERVERERROR,
4688 "osrfMethodException",
4690 "SELECT list is empty"
4693 buffer_free( group_buf );
4694 if( defaultselhash )
4695 jsonObjectFree( defaultselhash );
4696 free( join_clause );
4702 table = searchValueTransform( join_hash );
4704 table = strdup( curr_query->core.source_def );
4708 osrfAppSessionStatus(
4710 OSRF_STATUS_INTERNALSERVERERROR,
4711 "osrfMethodException",
4713 "Unable to identify table for core class"
4716 buffer_free( group_buf );
4717 if( defaultselhash )
4718 jsonObjectFree( defaultselhash );
4719 free( join_clause );
4723 // Put it all together
4724 growing_buffer* sql_buf = buffer_init( 128 );
4725 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4729 // Append the join clause, if any
4731 buffer_add(sql_buf, join_clause );
4732 free( join_clause );
4735 char* order_by_list = NULL;
4736 char* having_buf = NULL;
4738 if( !from_function ) {
4740 // Build a WHERE clause, if there is one
4742 buffer_add( sql_buf, " WHERE " );
4744 // and it's on the WHERE clause
4745 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4748 osrfAppSessionStatus(
4750 OSRF_STATUS_INTERNALSERVERERROR,
4751 "osrfMethodException",
4753 "Severe query error in WHERE predicate -- see error log for more details"
4756 buffer_free( group_buf );
4757 buffer_free( sql_buf );
4758 if( defaultselhash )
4759 jsonObjectFree( defaultselhash );
4763 buffer_add( sql_buf, pred );
4767 // Build a HAVING clause, if there is one
4770 // and it's on the the WHERE clause
4771 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4773 if( ! having_buf ) {
4775 osrfAppSessionStatus(
4777 OSRF_STATUS_INTERNALSERVERERROR,
4778 "osrfMethodException",
4780 "Severe query error in HAVING predicate -- see error log for more details"
4783 buffer_free( group_buf );
4784 buffer_free( sql_buf );
4785 if( defaultselhash )
4786 jsonObjectFree( defaultselhash );
4791 // Build an ORDER BY clause, if there is one
4792 if( NULL == order_hash )
4793 ; // No ORDER BY? do nothing
4794 else if( JSON_ARRAY == order_hash->type ) {
4795 order_by_list = buildOrderByFromArray( ctx, order_hash );
4796 if( !order_by_list ) {
4798 buffer_free( group_buf );
4799 buffer_free( sql_buf );
4800 if( defaultselhash )
4801 jsonObjectFree( defaultselhash );
4804 } else if( JSON_HASH == order_hash->type ) {
4805 // This hash is keyed on class alias. Each class has either
4806 // an array of field names or a hash keyed on field name.
4807 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4808 jsonIterator* class_itr = jsonNewIterator( order_hash );
4809 while( (snode = jsonIteratorNext( class_itr )) ) {
4811 ClassInfo* order_class_info = search_alias( class_itr->key );
4812 if( ! order_class_info ) {
4813 osrfLogError( OSRF_LOG_MARK,
4814 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4815 modulename, class_itr->key );
4817 osrfAppSessionStatus(
4819 OSRF_STATUS_INTERNALSERVERERROR,
4820 "osrfMethodException",
4822 "Invalid class referenced in ORDER BY clause -- "
4823 "see error log for more details"
4825 jsonIteratorFree( class_itr );
4826 buffer_free( order_buf );
4828 buffer_free( group_buf );
4829 buffer_free( sql_buf );
4830 if( defaultselhash )
4831 jsonObjectFree( defaultselhash );
4835 osrfHash* field_list_def = order_class_info->fields;
4837 if( snode->type == JSON_HASH ) {
4839 // Hash is keyed on field names from the current class. For each field
4840 // there is another layer of hash to define the sorting details, if any,
4841 // or a string to indicate direction of sorting.
4842 jsonIterator* order_itr = jsonNewIterator( snode );
4843 while( (onode = jsonIteratorNext( order_itr )) ) {
4845 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4847 osrfLogError( OSRF_LOG_MARK,
4848 "%s: Invalid field \"%s\" in ORDER BY clause",
4849 modulename, order_itr->key );
4851 osrfAppSessionStatus(
4853 OSRF_STATUS_INTERNALSERVERERROR,
4854 "osrfMethodException",
4856 "Invalid field in ORDER BY clause -- "
4857 "see error log for more details"
4859 jsonIteratorFree( order_itr );
4860 jsonIteratorFree( class_itr );
4861 buffer_free( order_buf );
4863 buffer_free( group_buf );
4864 buffer_free( sql_buf );
4865 if( defaultselhash )
4866 jsonObjectFree( defaultselhash );
4868 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4869 osrfLogError( OSRF_LOG_MARK,
4870 "%s: Virtual field \"%s\" in ORDER BY clause",
4871 modulename, order_itr->key );
4873 osrfAppSessionStatus(
4875 OSRF_STATUS_INTERNALSERVERERROR,
4876 "osrfMethodException",
4878 "Virtual field in ORDER BY clause -- "
4879 "see error log for more details"
4881 jsonIteratorFree( order_itr );
4882 jsonIteratorFree( class_itr );
4883 buffer_free( order_buf );
4885 buffer_free( group_buf );
4886 buffer_free( sql_buf );
4887 if( defaultselhash )
4888 jsonObjectFree( defaultselhash );
4892 const char* direction = NULL;
4893 if( onode->type == JSON_HASH ) {
4894 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4895 string = searchFieldTransform(
4897 osrfHashGet( field_list_def, order_itr->key ),
4901 if( ctx ) osrfAppSessionStatus(
4903 OSRF_STATUS_INTERNALSERVERERROR,
4904 "osrfMethodException",
4906 "Severe query error in ORDER BY clause -- "
4907 "see error log for more details"
4909 jsonIteratorFree( order_itr );
4910 jsonIteratorFree( class_itr );
4912 buffer_free( group_buf );
4913 buffer_free( order_buf);
4914 buffer_free( sql_buf );
4915 if( defaultselhash )
4916 jsonObjectFree( defaultselhash );
4920 growing_buffer* field_buf = buffer_init( 16 );
4921 buffer_fadd( field_buf, "\"%s\".%s",
4922 class_itr->key, order_itr->key );
4923 string = buffer_release( field_buf );
4926 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4927 const char* dir = jsonObjectGetString( tmp_const );
4928 if(!strncasecmp( dir, "d", 1 )) {
4929 direction = " DESC";
4935 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4936 osrfLogError( OSRF_LOG_MARK,
4937 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4938 modulename, json_type( onode->type ) );
4940 osrfAppSessionStatus(
4942 OSRF_STATUS_INTERNALSERVERERROR,
4943 "osrfMethodException",
4945 "Malformed ORDER BY clause -- see error log for more details"
4947 jsonIteratorFree( order_itr );
4948 jsonIteratorFree( class_itr );
4950 buffer_free( group_buf );
4951 buffer_free( order_buf );
4952 buffer_free( sql_buf );
4953 if( defaultselhash )
4954 jsonObjectFree( defaultselhash );
4958 string = strdup( order_itr->key );
4959 const char* dir = jsonObjectGetString( onode );
4960 if( !strncasecmp( dir, "d", 1 )) {
4961 direction = " DESC";
4968 OSRF_BUFFER_ADD( order_buf, ", " );
4970 order_buf = buffer_init( 128 );
4972 OSRF_BUFFER_ADD( order_buf, string );
4976 OSRF_BUFFER_ADD( order_buf, direction );
4980 jsonIteratorFree( order_itr );
4982 } else if( snode->type == JSON_ARRAY ) {
4984 // Array is a list of fields from the current class
4985 unsigned long order_idx = 0;
4986 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4988 const char* _f = jsonObjectGetString( onode );
4990 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4992 osrfLogError( OSRF_LOG_MARK,
4993 "%s: Invalid field \"%s\" in ORDER BY clause",
4996 osrfAppSessionStatus(
4998 OSRF_STATUS_INTERNALSERVERERROR,
4999 "osrfMethodException",
5001 "Invalid field in ORDER BY clause -- "
5002 "see error log for more details"
5004 jsonIteratorFree( class_itr );
5005 buffer_free( order_buf );
5007 buffer_free( group_buf );
5008 buffer_free( sql_buf );
5009 if( defaultselhash )
5010 jsonObjectFree( defaultselhash );
5012 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5013 osrfLogError( OSRF_LOG_MARK,
5014 "%s: Virtual field \"%s\" in ORDER BY clause",
5017 osrfAppSessionStatus(
5019 OSRF_STATUS_INTERNALSERVERERROR,
5020 "osrfMethodException",
5022 "Virtual field in ORDER BY clause -- "
5023 "see error log for more details"
5025 jsonIteratorFree( class_itr );
5026 buffer_free( order_buf );
5028 buffer_free( group_buf );
5029 buffer_free( sql_buf );
5030 if( defaultselhash )
5031 jsonObjectFree( defaultselhash );
5036 OSRF_BUFFER_ADD( order_buf, ", " );
5038 order_buf = buffer_init( 128 );
5040 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
5044 // IT'S THE OOOOOOOOOOOLD STYLE!
5046 osrfLogError( OSRF_LOG_MARK,
5047 "%s: Possible SQL injection attempt; direct order by is not allowed",
5050 osrfAppSessionStatus(
5052 OSRF_STATUS_INTERNALSERVERERROR,
5053 "osrfMethodException",
5055 "Severe query error -- see error log for more details"
5060 buffer_free( group_buf );
5061 buffer_free( order_buf );
5062 buffer_free( sql_buf );
5063 if( defaultselhash )
5064 jsonObjectFree( defaultselhash );
5065 jsonIteratorFree( class_itr );
5069 jsonIteratorFree( class_itr );
5071 order_by_list = buffer_release( order_buf );
5073 osrfLogError( OSRF_LOG_MARK,
5074 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
5075 modulename, json_type( order_hash->type ) );
5077 osrfAppSessionStatus(
5079 OSRF_STATUS_INTERNALSERVERERROR,
5080 "osrfMethodException",
5082 "Malformed ORDER BY clause -- see error log for more details"
5085 buffer_free( group_buf );
5086 buffer_free( sql_buf );
5087 if( defaultselhash )
5088 jsonObjectFree( defaultselhash );
5093 string = buffer_release( group_buf );
5095 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
5096 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
5097 OSRF_BUFFER_ADD( sql_buf, string );
5102 if( having_buf && *having_buf ) {
5103 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
5104 OSRF_BUFFER_ADD( sql_buf, having_buf );
5108 if( order_by_list ) {
5110 if( *order_by_list ) {
5111 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5112 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5115 free( order_by_list );
5119 const char* str = jsonObjectGetString( limit );
5120 if (str) { // limit could be JSON_NULL, etc.
5121 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
5126 const char* str = jsonObjectGetString( offset );
5128 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
5132 if( !(flags & SUBSELECT) )
5133 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5135 if( defaultselhash )
5136 jsonObjectFree( defaultselhash );
5138 return buffer_release( sql_buf );
5140 } // end of SELECT()
5143 @brief Build a list of ORDER BY expressions.
5144 @param ctx Pointer to the method context.
5145 @param order_array Pointer to a JSON_ARRAY of field specifications.
5146 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
5147 Each expression may be either a column reference or a function call whose first parameter
5148 is a column reference.
5150 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
5151 It may optionally include entries for "direction" and/or "transform".
5153 The calling code is responsible for freeing the returned string.
5155 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
5156 if( ! order_array ) {
5157 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
5160 osrfAppSessionStatus(
5162 OSRF_STATUS_INTERNALSERVERERROR,
5163 "osrfMethodException",
5165 "Logic error: ORDER BY clause expected, not found; "
5166 "see error log for more details"
5169 } else if( order_array->type != JSON_ARRAY ) {
5170 osrfLogError( OSRF_LOG_MARK,
5171 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
5173 osrfAppSessionStatus(
5175 OSRF_STATUS_INTERNALSERVERERROR,
5176 "osrfMethodException",
5178 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
5182 growing_buffer* order_buf = buffer_init( 128 );
5183 int first = 1; // boolean
5185 jsonObject* order_spec;
5186 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
5188 if( JSON_HASH != order_spec->type ) {
5189 osrfLogError( OSRF_LOG_MARK,
5190 "%s: Malformed field specification in ORDER BY clause; "
5191 "expected JSON_HASH, found %s",
5192 modulename, json_type( order_spec->type ) );
5194 osrfAppSessionStatus(
5196 OSRF_STATUS_INTERNALSERVERERROR,
5197 "osrfMethodException",
5199 "Malformed ORDER BY clause -- see error log for more details"
5201 buffer_free( order_buf );
5205 const char* class_alias =
5206 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5208 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5210 jsonObject* compare_to = jsonObjectGetKey( order_spec, "compare" );
5212 if( !field || !class_alias ) {
5213 osrfLogError( OSRF_LOG_MARK,
5214 "%s: Missing class or field name in field specification of ORDER BY clause",
5217 osrfAppSessionStatus(
5219 OSRF_STATUS_INTERNALSERVERERROR,
5220 "osrfMethodException",
5222 "Malformed ORDER BY clause -- see error log for more details"
5224 buffer_free( order_buf );
5228 const ClassInfo* order_class_info = search_alias( class_alias );
5229 if( ! order_class_info ) {
5230 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5231 "not in FROM clause, skipping it", modulename, class_alias );
5235 // Add a separating comma, except at the beginning
5239 OSRF_BUFFER_ADD( order_buf, ", " );
5241 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5243 osrfLogError( OSRF_LOG_MARK,
5244 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5245 modulename, class_alias, field );
5247 osrfAppSessionStatus(
5249 OSRF_STATUS_INTERNALSERVERERROR,
5250 "osrfMethodException",
5252 "Invalid field referenced in ORDER BY clause -- "
5253 "see error log for more details"
5257 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5258 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5259 modulename, field );
5261 osrfAppSessionStatus(
5263 OSRF_STATUS_INTERNALSERVERERROR,
5264 "osrfMethodException",
5266 "Virtual field in ORDER BY clause -- see error log for more details"
5268 buffer_free( order_buf );
5272 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5273 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5274 if( ! transform_str ) {
5276 osrfAppSessionStatus(
5278 OSRF_STATUS_INTERNALSERVERERROR,
5279 "osrfMethodException",
5281 "Severe query error in ORDER BY clause -- "
5282 "see error log for more details"
5284 buffer_free( order_buf );
5288 OSRF_BUFFER_ADD( order_buf, transform_str );
5289 free( transform_str );
5290 } else if( compare_to ) {
5291 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5292 if( ! compare_str ) {
5294 osrfAppSessionStatus(
5296 OSRF_STATUS_INTERNALSERVERERROR,
5297 "osrfMethodException",
5299 "Severe query error in ORDER BY clause -- "
5300 "see error log for more details"
5302 buffer_free( order_buf );
5306 buffer_fadd( order_buf, "(%s)", compare_str );
5307 free( compare_str );
5310 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5312 const char* direction =
5313 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5315 if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5316 OSRF_BUFFER_ADD( order_buf, " DESC" );
5318 OSRF_BUFFER_ADD( order_buf, " ASC" );
5322 return buffer_release( order_buf );
5326 @brief Build a SELECT statement.
5327 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5328 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5329 @param meta Pointer to the class metadata for the core class.
5330 @param ctx Pointer to the method context.
5331 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5333 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5334 "order_by", "limit", and "offset".
5336 The SELECT statements built here are distinct from those built for the json_query method.
5338 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5339 osrfHash* meta, osrfMethodContext* ctx ) {
5341 const char* locale = osrf_message_get_last_locale();
5343 osrfHash* fields = osrfHashGet( meta, "fields" );
5344 const char* core_class = osrfHashGet( meta, "classname" );
5346 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5348 jsonObject* selhash = NULL;
5349 jsonObject* defaultselhash = NULL;
5351 growing_buffer* sql_buf = buffer_init( 128 );
5352 growing_buffer* select_buf = buffer_init( 128 );
5354 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5355 defaultselhash = jsonNewObjectType( JSON_HASH );
5356 selhash = defaultselhash;
5359 // If there's no SELECT list for the core class, build one
5360 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5361 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5363 // Add every non-virtual field to the field list
5364 osrfHash* field_def = NULL;
5365 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5366 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5367 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5368 const char* field = osrfHashIteratorKey( field_itr );
5369 jsonObjectPush( field_list, jsonNewObject( field ) );
5372 osrfHashIteratorFree( field_itr );
5373 jsonObjectSetKey( selhash, core_class, field_list );
5376 // Build a list of columns for the SELECT clause
5378 const jsonObject* snode = NULL;
5379 jsonIterator* class_itr = jsonNewIterator( selhash );
5380 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5382 // If the class isn't in the IDL, ignore it
5383 const char* cname = class_itr->key;
5384 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5388 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5389 if( strcmp( core_class, class_itr->key )) {
5393 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5394 if( !found->size ) {
5395 jsonObjectFree( found );
5399 jsonObjectFree( found );
5402 const jsonObject* node = NULL;
5403 jsonIterator* select_itr = jsonNewIterator( snode );
5404 while( (node = jsonIteratorNext( select_itr )) ) {
5405 const char* item_str = jsonObjectGetString( node );
5406 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5407 char* fname = osrfHashGet( field, "name" );
5412 if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5418 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5423 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5424 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5427 i18n = osrfHashGet( field, "i18n" );
5429 if( str_is_true( i18n ) ) {
5430 char* pkey = osrfHashGet( idlClass, "primarykey" );
5431 char* tname = osrfHashGet( idlClass, "tablename" );
5433 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5434 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5435 tname, cname, fname, pkey, cname, pkey, locale, fname );
5437 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5440 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5444 jsonIteratorFree( select_itr );
5447 jsonIteratorFree( class_itr );
5449 char* col_list = buffer_release( select_buf );
5450 char* table = oilsGetRelation( meta );
5452 table = strdup( "(null)" );
5454 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5458 // Clear the query stack (as a fail-safe precaution against possible
5459 // leftover garbage); then push the first query frame onto the stack.
5460 clear_query_stack();
5462 if( add_query_core( NULL, core_class ) ) {
5464 osrfAppSessionStatus(
5466 OSRF_STATUS_INTERNALSERVERERROR,
5467 "osrfMethodException",
5469 "Unable to build query frame for core class"
5471 buffer_free( sql_buf );
5472 if( defaultselhash )
5473 jsonObjectFree( defaultselhash );
5477 // Add the JOIN clauses, if any
5479 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5480 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5481 OSRF_BUFFER_ADD( sql_buf, join_clause );
5482 free( join_clause );
5485 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5486 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5488 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5490 // Add the conditions in the WHERE clause
5491 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5493 osrfAppSessionStatus(
5495 OSRF_STATUS_INTERNALSERVERERROR,
5496 "osrfMethodException",
5498 "Severe query error -- see error log for more details"
5500 buffer_free( sql_buf );
5501 if( defaultselhash )
5502 jsonObjectFree( defaultselhash );
5503 clear_query_stack();
5506 buffer_add( sql_buf, pred );
5510 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5511 if( rest_of_query ) {
5512 const jsonObject* order_by = NULL;
5513 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5515 char* order_by_list = NULL;
5517 if( JSON_ARRAY == order_by->type ) {
5518 order_by_list = buildOrderByFromArray( ctx, order_by );
5519 if( !order_by_list ) {
5520 buffer_free( sql_buf );
5521 if( defaultselhash )
5522 jsonObjectFree( defaultselhash );
5523 clear_query_stack();
5526 } else if( JSON_HASH == order_by->type ) {
5527 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5528 // and build a list of ORDER BY expressions.
5529 growing_buffer* order_buf = buffer_init( 128 );
5531 jsonIterator* class_itr = jsonNewIterator( order_by );
5532 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5534 ClassInfo* order_class_info = search_alias( class_itr->key );
5535 if( ! order_class_info )
5536 continue; // class not referenced by FROM clause? Ignore it.
5538 if( JSON_HASH == snode->type ) {
5540 // If the data for the current class is a JSON_HASH, then it is
5541 // keyed on field name.
5543 const jsonObject* onode = NULL;
5544 jsonIterator* order_itr = jsonNewIterator( snode );
5545 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5547 osrfHash* field_def = osrfHashGet(
5548 order_class_info->fields, order_itr->key );
5550 continue; // Field not defined in IDL? Ignore it.
5551 if( str_is_true( osrfHashGet( field_def, "virtual")))
5552 continue; // Field is virtual? Ignore it.
5554 char* field_str = NULL;
5555 char* direction = NULL;
5556 if( onode->type == JSON_HASH ) {
5557 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5558 field_str = searchFieldTransform(
5559 class_itr->key, field_def, onode );
5561 osrfAppSessionStatus(
5563 OSRF_STATUS_INTERNALSERVERERROR,
5564 "osrfMethodException",
5566 "Severe query error in ORDER BY clause -- "
5567 "see error log for more details"
5569 jsonIteratorFree( order_itr );
5570 jsonIteratorFree( class_itr );
5571 buffer_free( order_buf );
5572 buffer_free( sql_buf );
5573 if( defaultselhash )
5574 jsonObjectFree( defaultselhash );
5575 clear_query_stack();
5579 growing_buffer* field_buf = buffer_init( 16 );
5580 buffer_fadd( field_buf, "\"%s\".%s",
5581 class_itr->key, order_itr->key );
5582 field_str = buffer_release( field_buf );
5585 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5586 const char* dir = jsonObjectGetString( order_by );
5587 if(!strncasecmp( dir, "d", 1 )) {
5588 direction = " DESC";
5592 field_str = strdup( order_itr->key );
5593 const char* dir = jsonObjectGetString( onode );
5594 if( !strncasecmp( dir, "d", 1 )) {
5595 direction = " DESC";
5604 buffer_add( order_buf, ", " );
5607 buffer_add( order_buf, field_str );
5611 buffer_add( order_buf, direction );
5613 } // end while; looping over ORDER BY expressions
5615 jsonIteratorFree( order_itr );
5617 } else if( JSON_STRING == snode->type ) {
5618 // We expect a comma-separated list of sort fields.
5619 const char* str = jsonObjectGetString( snode );
5620 if( strchr( str, ';' )) {
5621 // No semicolons allowed. It is theoretically possible for a
5622 // legitimate semicolon to occur within quotes, but it's not likely
5623 // to occur in practice in the context of an ORDER BY list.
5624 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5625 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5627 osrfAppSessionStatus(
5629 OSRF_STATUS_INTERNALSERVERERROR,
5630 "osrfMethodException",
5632 "Possible attempt at SOL injection -- "
5633 "semicolon found in ORDER BY list"
5636 jsonIteratorFree( class_itr );
5637 buffer_free( order_buf );
5638 buffer_free( sql_buf );
5639 if( defaultselhash )
5640 jsonObjectFree( defaultselhash );
5641 clear_query_stack();
5644 buffer_add( order_buf, str );
5648 } // end while; looping over order_by classes
5650 jsonIteratorFree( class_itr );
5651 order_by_list = buffer_release( order_buf );
5654 osrfLogWarning( OSRF_LOG_MARK,
5655 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5656 "no ORDER BY generated" );
5659 if( order_by_list && *order_by_list ) {
5660 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5661 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5664 free( order_by_list );
5667 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5669 const char* str = jsonObjectGetString( limit );
5679 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5681 const char* str = jsonObjectGetString( offset );
5692 if( defaultselhash )
5693 jsonObjectFree( defaultselhash );
5694 clear_query_stack();
5696 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5697 return buffer_release( sql_buf );
5700 int doJSONSearch ( osrfMethodContext* ctx ) {
5701 if(osrfMethodVerifyContext( ctx )) {
5702 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5706 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5710 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5714 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5715 flags |= SELECT_DISTINCT;
5717 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5718 flags |= DISABLE_I18N;
5720 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5721 clear_query_stack(); // a possibly needless precaution
5722 char* sql = buildQuery( ctx, hash, flags );
5723 clear_query_stack();
5730 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5733 dbhandle = writehandle;
5735 dbi_result result = dbi_conn_query( dbhandle, sql );
5738 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5740 if( dbi_result_first_row( result )) {
5741 /* JSONify the result */
5742 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5745 jsonObject* return_val = oilsMakeJSONFromResult( result );
5746 osrfAppRespond( ctx, return_val );
5747 jsonObjectFree( return_val );
5748 } while( dbi_result_next_row( result ));
5751 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5754 osrfAppRespondComplete( ctx, NULL );
5756 /* clean up the query */
5757 dbi_result_free( result );
5762 int errnum = dbi_conn_error( dbhandle, &msg );
5763 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5764 modulename, sql, errnum, msg ? msg : "(No description available)" );
5765 osrfAppSessionStatus(
5767 OSRF_STATUS_INTERNALSERVERERROR,
5768 "osrfMethodException",
5770 "Severe query error -- see error log for more details"
5772 if( !oilsIsDBConnected( dbhandle ))
5773 osrfAppSessionPanic( ctx->session );
5780 // The last parameter, err, is used to report an error condition by updating an int owned by
5781 // the calling code.
5783 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5784 // It is the responsibility of the calling code to initialize *err before the
5785 // call, so that it will be able to make sense of the result.
5787 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5788 // redundant anyway.
5789 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5790 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5793 dbhandle = writehandle;
5795 char* core_class = osrfHashGet( class_meta, "classname" );
5796 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5798 char* pkey = osrfHashGet( class_meta, "primarykey" );
5800 if (!ctx->session->userData)
5801 (void) initSessionCache( ctx );
5803 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5804 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5805 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5807 int i_respond_directly = 0;
5808 int flesh_depth = 0;
5810 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5812 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5817 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5819 dbi_result result = dbi_conn_query( dbhandle, sql );
5820 if( NULL == result ) {
5822 int errnum = dbi_conn_error( dbhandle, &msg );
5823 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5824 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5825 msg ? msg : "(No description available)" );
5826 if( !oilsIsDBConnected( dbhandle ))
5827 osrfAppSessionPanic( ctx->session );
5828 osrfAppSessionStatus(
5830 OSRF_STATUS_INTERNALSERVERERROR,
5831 "osrfMethodException",
5833 "Severe query error -- see error log for more details"
5840 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5843 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5844 jsonObject* row_obj = NULL;
5846 // The following two steps are for verifyObjectPCRUD()'s benefit.
5847 // 1. get the flesh depth
5848 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5850 flesh_depth = (int) jsonObjectGetNumber( _tmp );
5851 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5852 flesh_depth = max_flesh_depth;
5855 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
5856 // over the whole life of this request. This means if we've already set
5857 // up a rs_size_req_%d, do nothing.
5858 // a. Incidentally, we can also use this opportunity to set i_respond_directly
5859 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
5860 if( !rs_size ) { // pointer null, so value not set in hash
5861 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
5862 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
5864 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
5865 unsigned long long result_count = dbi_result_get_numrows( result );
5866 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
5867 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
5870 if( dbi_result_first_row( result )) {
5872 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5873 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5874 // eliminate the duplicates.
5875 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5876 osrfHash* dedup = osrfNewHash();
5878 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5879 char* pkey_val = oilsFMGetString( row_obj, pkey );
5880 if( osrfHashGet( dedup, pkey_val ) ) {
5881 jsonObjectFree( row_obj );
5884 if( !enforce_pcrud || !need_to_verify ||
5885 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
5886 osrfHashSet( dedup, pkey_val, pkey_val );
5887 jsonObjectPush( res_list, row_obj );
5890 } while( dbi_result_next_row( result ));
5891 osrfHashFree( dedup );
5894 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5898 /* clean up the query */
5899 dbi_result_free( result );
5902 // If we're asked to flesh, and there's anything to flesh, then flesh it
5903 // (formerly we would skip fleshing if in pcrud mode, but now we support
5904 // fleshing even in PCRUD).
5905 if( res_list->size ) {
5906 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
5907 jsonObject* flesh_fields;
5908 jsonObject* flesh_blob = NULL;
5909 osrfStringArray* link_fields = NULL;
5910 osrfHash* links = NULL;
5914 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
5915 if( temp_blob && flesh_depth > 0 ) {
5917 flesh_blob = jsonObjectClone( temp_blob );
5918 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
5920 links = osrfHashGet( class_meta, "links" );
5922 // Make an osrfStringArray of the names of fields to be fleshed
5923 if( flesh_fields ) {
5924 if( flesh_fields->size == 1 ) {
5925 const char* _t = jsonObjectGetString(
5926 jsonObjectGetIndex( flesh_fields, 0 ) );
5927 if( !strcmp( _t, "*" ))
5928 link_fields = osrfHashKeys( links );
5931 if( !link_fields ) {
5933 link_fields = osrfNewStringArray( 1 );
5934 jsonIterator* _i = jsonNewIterator( flesh_fields );
5935 while ((_f = jsonIteratorNext( _i ))) {
5936 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5938 jsonIteratorFree( _i );
5941 want_flesh = link_fields ? 1 : 0;
5945 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5947 // Iterate over the JSON_ARRAY of rows
5949 unsigned long res_idx = 0;
5950 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5953 const char* link_field;
5955 // Iterate over the list of fleshable fields
5957 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5959 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5961 osrfHash* kid_link = osrfHashGet( links, link_field );
5963 continue; // Not a link field; skip it
5965 osrfHash* field = osrfHashGet( fields, link_field );
5967 continue; // Not a field at all; skip it (IDL is ill-formed)
5969 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5970 osrfHashGet( kid_link, "class" ));
5972 continue; // The class it links to doesn't exist; skip it
5974 const char* reltype = osrfHashGet( kid_link, "reltype" );
5976 continue; // No reltype; skip it (IDL is ill-formed)
5978 osrfHash* value_field = field;
5980 if( !strcmp( reltype, "has_many" )
5981 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5982 value_field = osrfHashGet(
5983 fields, osrfHashGet( class_meta, "primarykey" ) );
5986 int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
5987 // fleshing pcrud case: we require the controller in need_to_verify mode
5988 if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
5989 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
5993 (unsigned long) atoi( osrfHashGet(field, "array_position") ),
5995 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
6001 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
6003 if( link_map->size > 0 ) {
6004 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
6007 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
6012 osrfHashGet( kid_link, "class" ),
6019 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
6020 osrfHashGet( kid_link, "field" ),
6021 osrfHashGet( kid_link, "class" ),
6022 osrfHashGet( kid_link, "key" ),
6023 osrfHashGet( kid_link, "reltype" )
6026 const char* search_key = jsonObjectGetString(
6027 jsonObjectGetIndex( cur,
6028 atoi( osrfHashGet( value_field, "array_position" ) )
6033 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
6037 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
6039 // construct WHERE clause
6040 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
6043 osrfHashGet( kid_link, "key" ),
6044 jsonNewObject( search_key )
6047 // construct the rest of the query, mostly
6048 // by copying pieces of the previous level of query
6049 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
6050 jsonObjectSetKey( rest_of_query, "flesh",
6051 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
6055 jsonObjectSetKey( rest_of_query, "flesh_fields",
6056 jsonObjectClone( flesh_blob ));
6058 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
6059 jsonObjectSetKey( rest_of_query, "order_by",
6060 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
6064 if( jsonObjectGetKeyConst( query_hash, "select" )) {
6065 jsonObjectSetKey( rest_of_query, "select",
6066 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
6070 // do the query, recursively, to expand the fleshable field
6071 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
6072 where_clause, rest_of_query, err );
6074 jsonObjectFree( where_clause );
6075 jsonObjectFree( rest_of_query );
6078 osrfStringArrayFree( link_fields );
6079 jsonObjectFree( res_list );
6080 jsonObjectFree( flesh_blob );
6084 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
6085 osrfHashGet( kid_link, "class" ), kids->size );
6087 // Traverse the result set
6088 jsonObject* X = NULL;
6089 if( link_map->size > 0 && kids->size > 0 ) {
6091 kids = jsonNewObjectType( JSON_ARRAY );
6093 jsonObject* _k_node;
6094 unsigned long res_idx = 0;
6095 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
6101 (unsigned long) atoi(
6107 osrfHashGet( kid_link, "class" )
6111 osrfStringArrayGetString( link_map, 0 )
6119 } // end while loop traversing X
6122 if (kids->size > 0) {
6124 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
6125 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
6127 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
6128 osrfHashGet( kid_link, "field" ));
6131 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6132 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
6137 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
6139 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
6140 osrfHashGet( kid_link, "field" ) );
6143 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6144 jsonObjectClone( kids )
6149 jsonObjectFree( kids );
6153 jsonObjectFree( kids );
6155 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
6156 osrfHashGet( kid_link, "field" ) );
6157 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
6159 } // end while loop traversing list of fleshable fields
6162 if( i_respond_directly ) {
6163 if ( *methodtype == 'i' ) {
6164 osrfAppRespond( ctx,
6165 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
6167 osrfAppRespond( ctx, cur );
6170 } // end while loop traversing res_list
6171 jsonObjectFree( flesh_blob );
6172 osrfStringArrayFree( link_fields );
6175 if( i_respond_directly ) {
6176 jsonObjectFree( res_list );
6177 return jsonNewObjectType( JSON_ARRAY );
6184 int doUpdate( osrfMethodContext* ctx ) {
6185 if( osrfMethodVerifyContext( ctx )) {
6186 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6191 timeout_needs_resetting = 1;
6193 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6195 jsonObject* target = NULL;
6197 target = jsonObjectGetIndex( ctx->params, 1 );
6199 target = jsonObjectGetIndex( ctx->params, 0 );
6201 if(!verifyObjectClass( ctx, target )) {
6202 osrfAppRespondComplete( ctx, NULL );
6206 if( getXactId( ctx ) == NULL ) {
6207 osrfAppSessionStatus(
6209 OSRF_STATUS_BADREQUEST,
6210 "osrfMethodException",
6212 "No active transaction -- required for UPDATE"
6214 osrfAppRespondComplete( ctx, NULL );
6218 // The following test is harmless but redundant. If a class is
6219 // readonly, we don't register an update method for it.
6220 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6221 osrfAppSessionStatus(
6223 OSRF_STATUS_BADREQUEST,
6224 "osrfMethodException",
6226 "Cannot UPDATE readonly class"
6228 osrfAppRespondComplete( ctx, NULL );
6232 const char* trans_id = getXactId( ctx );
6234 // Set the last_xact_id
6235 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6237 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6238 trans_id, target->classname, index );
6239 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6242 char* pkey = osrfHashGet( meta, "primarykey" );
6243 osrfHash* fields = osrfHashGet( meta, "fields" );
6245 char* id = oilsFMGetString( target, pkey );
6249 "%s updating %s object with %s = %s",
6251 osrfHashGet( meta, "fieldmapper" ),
6256 dbhandle = writehandle;
6257 growing_buffer* sql = buffer_init( 128 );
6258 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6261 osrfHash* field_def = NULL;
6262 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6263 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6265 // Skip virtual fields, and the primary key
6266 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6269 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6273 const char* field_name = osrfHashIteratorKey( field_itr );
6274 if( ! strcmp( field_name, pkey ) )
6277 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6279 int value_is_numeric = 0; // boolean
6281 if( field_object && field_object->classname ) {
6282 value = oilsFMGetString(
6284 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6286 } else if( field_object && JSON_BOOL == field_object->type ) {
6287 if( jsonBoolIsTrue( field_object ) )
6288 value = strdup( "t" );
6290 value = strdup( "f" );
6292 value = jsonObjectToSimpleString( field_object );
6293 if( field_object && JSON_NUMBER == field_object->type )
6294 value_is_numeric = 1;
6297 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6298 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6300 if( !field_object || field_object->type == JSON_NULL ) {
6301 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6302 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6306 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6307 buffer_fadd( sql, " %s = NULL", field_name );
6310 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6314 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6316 const char* numtype = get_datatype( field_def );
6317 if( !strncmp( numtype, "INT", 3 ) ) {
6318 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6319 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6320 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6322 // Must really be intended as a string, so quote it
6323 if( dbi_conn_quote_string( dbhandle, &value )) {
6324 buffer_fadd( sql, " %s = %s", field_name, value );
6326 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6327 modulename, value );
6328 osrfAppSessionStatus(
6330 OSRF_STATUS_INTERNALSERVERERROR,
6331 "osrfMethodException",
6333 "Error quoting string -- please see the error log for more details"
6337 osrfHashIteratorFree( field_itr );
6339 osrfAppRespondComplete( ctx, NULL );
6344 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6347 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6351 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6352 buffer_fadd( sql, " %s = %s", field_name, value );
6354 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6355 osrfAppSessionStatus(
6357 OSRF_STATUS_INTERNALSERVERERROR,
6358 "osrfMethodException",
6360 "Error quoting string -- please see the error log for more details"
6364 osrfHashIteratorFree( field_itr );
6366 osrfAppRespondComplete( ctx, NULL );
6375 osrfHashIteratorFree( field_itr );
6377 jsonObject* obj = jsonNewObject( id );
6379 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6380 dbi_conn_quote_string( dbhandle, &id );
6382 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6384 char* query = buffer_release( sql );
6385 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6387 dbi_result result = dbi_conn_query( dbhandle, query );
6392 jsonObjectFree( obj );
6393 obj = jsonNewObject( NULL );
6395 int errnum = dbi_conn_error( dbhandle, &msg );
6398 "%s ERROR updating %s object with %s = %s: %d %s",
6400 osrfHashGet( meta, "fieldmapper" ),
6404 msg ? msg : "(No description available)"
6406 osrfAppSessionStatus(
6408 OSRF_STATUS_INTERNALSERVERERROR,
6409 "osrfMethodException",
6411 "Error in updating a row -- please see the error log for more details"
6413 if( !oilsIsDBConnected( dbhandle ))
6414 osrfAppSessionPanic( ctx->session );
6417 dbi_result_free( result );
6420 osrfAppRespondComplete( ctx, obj );
6421 jsonObjectFree( obj );
6425 int doDelete( osrfMethodContext* ctx ) {
6426 if( osrfMethodVerifyContext( ctx )) {
6427 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6432 timeout_needs_resetting = 1;
6434 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6436 if( getXactId( ctx ) == NULL ) {
6437 osrfAppSessionStatus(
6439 OSRF_STATUS_BADREQUEST,
6440 "osrfMethodException",
6442 "No active transaction -- required for DELETE"
6444 osrfAppRespondComplete( ctx, NULL );
6448 // The following test is harmless but redundant. If a class is
6449 // readonly, we don't register a delete method for it.
6450 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6451 osrfAppSessionStatus(
6453 OSRF_STATUS_BADREQUEST,
6454 "osrfMethodException",
6456 "Cannot DELETE readonly class"
6458 osrfAppRespondComplete( ctx, NULL );
6462 dbhandle = writehandle;
6464 char* pkey = osrfHashGet( meta, "primarykey" );
6471 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6472 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6473 osrfAppRespondComplete( ctx, NULL );
6477 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6479 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6480 osrfAppRespondComplete( ctx, NULL );
6483 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6488 "%s deleting %s object with %s = %s",
6490 osrfHashGet( meta, "fieldmapper" ),
6495 jsonObject* obj = jsonNewObject( id );
6497 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6498 dbi_conn_quote_string( writehandle, &id );
6500 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6501 osrfHashGet( meta, "tablename" ), pkey, id );
6506 jsonObjectFree( obj );
6507 obj = jsonNewObject( NULL );
6509 int errnum = dbi_conn_error( writehandle, &msg );
6512 "%s ERROR deleting %s object with %s = %s: %d %s",
6514 osrfHashGet( meta, "fieldmapper" ),
6518 msg ? msg : "(No description available)"
6520 osrfAppSessionStatus(
6522 OSRF_STATUS_INTERNALSERVERERROR,
6523 "osrfMethodException",
6525 "Error in deleting a row -- please see the error log for more details"
6527 if( !oilsIsDBConnected( writehandle ))
6528 osrfAppSessionPanic( ctx->session );
6530 dbi_result_free( result );
6534 osrfAppRespondComplete( ctx, obj );
6535 jsonObjectFree( obj );
6540 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6541 @param result An iterator for a result set; we only look at the current row.
6542 @param @meta Pointer to the class metadata for the core class.
6543 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6545 If a column is not defined in the IDL, or if it has no array_position defined for it in
6546 the IDL, or if it is defined as virtual, ignore it.
6548 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6549 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6550 array_position in the IDL.
6552 A field defined in the IDL but not represented in the returned row will leave a hole
6553 in the JSON_ARRAY. In effect it will be treated as a null value.
6555 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6556 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6557 classname corresponding to the @a meta argument.
6559 The calling code is responsible for freeing the the resulting jsonObject by calling
6562 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6563 if( !( result && meta )) return NULL;
6565 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6566 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6567 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6569 osrfHash* fields = osrfHashGet( meta, "fields" );
6571 int columnIndex = 1;
6572 const char* columnName;
6574 /* cycle through the columns in the row returned from the database */
6575 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6577 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6579 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6581 /* determine the field type and storage attributes */
6582 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6583 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6585 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6586 // or if it has no sequence number there, or if it's virtual, skip it.
6587 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6590 if( str_is_true( osrfHashGet( _f, "virtual" )))
6591 continue; // skip this column: IDL says it's virtual
6593 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6594 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6595 continue; // since we assign sequence numbers dynamically as we load the IDL.
6597 fmIndex = atoi( pos );
6598 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6600 continue; // This field is not defined in the IDL
6603 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6604 // sequence number from the IDL (which is likely to be different from the sequence
6605 // of columns in the SELECT clause).
6606 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6607 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6612 case DBI_TYPE_INTEGER :
6614 if( attr & DBI_INTEGER_SIZE8 )
6615 jsonObjectSetIndex( object, fmIndex,
6616 jsonNewNumberObject(
6617 dbi_result_get_longlong_idx( result, columnIndex )));
6619 jsonObjectSetIndex( object, fmIndex,
6620 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6624 case DBI_TYPE_DECIMAL :
6625 jsonObjectSetIndex( object, fmIndex,
6626 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6629 case DBI_TYPE_STRING :
6634 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6639 case DBI_TYPE_DATETIME : {
6641 char dt_string[ 256 ] = "";
6644 // Fetch the date column as a time_t
6645 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6647 // Translate the time_t to a human-readable string
6648 if( !( attr & DBI_DATETIME_DATE )) {
6649 gmtime_r( &_tmp_dt, &gmdt );
6650 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6651 } else if( !( attr & DBI_DATETIME_TIME )) {
6652 localtime_r( &_tmp_dt, &gmdt );
6653 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6655 localtime_r( &_tmp_dt, &gmdt );
6656 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6659 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6663 case DBI_TYPE_BINARY :
6664 osrfLogError( OSRF_LOG_MARK,
6665 "Can't do binary at column %s : index %d", columnName, columnIndex );
6674 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6675 if( !result ) return NULL;
6677 jsonObject* object = jsonNewObject( NULL );
6680 char dt_string[ 256 ];
6684 int columnIndex = 1;
6686 unsigned short type;
6687 const char* columnName;
6689 /* cycle through the column list */
6690 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6692 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6694 fmIndex = -1; // reset the position
6696 /* determine the field type and storage attributes */
6697 type = dbi_result_get_field_type_idx( result, columnIndex );
6698 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6700 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6701 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6706 case DBI_TYPE_INTEGER :
6708 if( attr & DBI_INTEGER_SIZE8 )
6709 jsonObjectSetKey( object, columnName,
6710 jsonNewNumberObject( dbi_result_get_longlong_idx(
6711 result, columnIndex )) );
6713 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6714 dbi_result_get_int_idx( result, columnIndex )) );
6717 case DBI_TYPE_DECIMAL :
6718 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6719 dbi_result_get_double_idx( result, columnIndex )) );
6722 case DBI_TYPE_STRING :
6723 jsonObjectSetKey( object, columnName,
6724 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6727 case DBI_TYPE_DATETIME :
6729 memset( dt_string, '\0', sizeof( dt_string ));
6730 memset( &gmdt, '\0', sizeof( gmdt ));
6732 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6734 if( !( attr & DBI_DATETIME_DATE )) {
6735 gmtime_r( &_tmp_dt, &gmdt );
6736 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6737 } else if( !( attr & DBI_DATETIME_TIME )) {
6738 localtime_r( &_tmp_dt, &gmdt );
6739 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6741 localtime_r( &_tmp_dt, &gmdt );
6742 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6745 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6748 case DBI_TYPE_BINARY :
6749 osrfLogError( OSRF_LOG_MARK,
6750 "Can't do binary at column %s : index %d", columnName, columnIndex );
6754 } // end while loop traversing result
6759 // Interpret a string as true or false
6760 int str_is_true( const char* str ) {
6761 if( NULL == str || strcasecmp( str, "true" ) )
6767 // Interpret a jsonObject as true or false
6768 static int obj_is_true( const jsonObject* obj ) {
6771 else switch( obj->type )
6779 if( strcasecmp( obj->value.s, "true" ) )
6783 case JSON_NUMBER : // Support 1/0 for perl's sake
6784 if( jsonObjectGetNumber( obj ) == 1.0 )
6793 // Translate a numeric code into a text string identifying a type of
6794 // jsonObject. To be used for building error messages.
6795 static const char* json_type( int code ) {
6801 return "JSON_ARRAY";
6803 return "JSON_STRING";
6805 return "JSON_NUMBER";
6811 return "(unrecognized)";
6815 // Extract the "primitive" attribute from an IDL field definition.
6816 // If we haven't initialized the app, then we must be running in
6817 // some kind of testbed. In that case, default to "string".
6818 static const char* get_primitive( osrfHash* field ) {
6819 const char* s = osrfHashGet( field, "primitive" );
6821 if( child_initialized )
6824 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6826 osrfHashGet( field, "name" )
6834 // Extract the "datatype" attribute from an IDL field definition.
6835 // If we haven't initialized the app, then we must be running in
6836 // some kind of testbed. In that case, default to to NUMERIC,
6837 // since we look at the datatype only for numbers.
6838 static const char* get_datatype( osrfHash* field ) {
6839 const char* s = osrfHashGet( field, "datatype" );
6841 if( child_initialized )
6844 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6846 osrfHashGet( field, "name" )
6855 @brief Determine whether a string is potentially a valid SQL identifier.
6856 @param s The identifier to be tested.
6857 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6859 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6860 need to follow all the rules exactly, such as requiring that the first character not
6863 We allow leading and trailing white space. In between, we do not allow punctuation
6864 (except for underscores and dollar signs), control characters, or embedded white space.
6866 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6867 for the foreseeable future such quoted identifiers are not likely to be an issue.
6869 int is_identifier( const char* s) {
6873 // Skip leading white space
6874 while( isspace( (unsigned char) *s ) )
6878 return 0; // Nothing but white space? Not okay.
6880 // Check each character until we reach white space or
6881 // end-of-string. Letters, digits, underscores, and
6882 // dollar signs are okay. With the exception of periods
6883 // (as in schema.identifier), control characters and other
6884 // punctuation characters are not okay. Anything else
6885 // is okay -- it could for example be part of a multibyte
6886 // UTF8 character such as a letter with diacritical marks,
6887 // and those are allowed.
6889 if( isalnum( (unsigned char) *s )
6893 ; // Fine; keep going
6894 else if( ispunct( (unsigned char) *s )
6895 || iscntrl( (unsigned char) *s ) )
6898 } while( *s && ! isspace( (unsigned char) *s ) );
6900 // If we found any white space in the above loop,
6901 // the rest had better be all white space.
6903 while( isspace( (unsigned char) *s ) )
6907 return 0; // White space was embedded within non-white space
6913 @brief Determine whether to accept a character string as a comparison operator.
6914 @param op The candidate comparison operator.
6915 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6917 We don't validate the operator for real. We just make sure that it doesn't contain
6918 any semicolons or white space (with special exceptions for a few specific operators).
6919 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6920 space but it's still not a valid operator, then the database will complain.
6922 Another approach would be to compare the string against a short list of approved operators.
6923 We don't do that because we want to allow custom operators like ">100*", which at this
6924 writing would be difficult or impossible to express otherwise in a JSON query.
6926 int is_good_operator( const char* op ) {
6927 if( !op ) return 0; // Sanity check
6931 if( isspace( (unsigned char) *s ) ) {
6932 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6933 // and IS NOT DISTINCT FROM.
6934 if( !strcasecmp( op, "similar to" ) )
6936 else if( !strcasecmp( op, "is distinct from" ) )
6938 else if( !strcasecmp( op, "is not distinct from" ) )
6943 else if( ';' == *s )
6951 @name Query Frame Management
6953 The following machinery supports a stack of query frames for use by SELECT().
6955 A query frame caches information about one level of a SELECT query. When we enter
6956 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6958 The query frame stores information about the core class, and about any joined classes
6961 The main purpose is to map table aliases to classes and tables, so that a query can
6962 join to the same table more than once. A secondary goal is to reduce the number of
6963 lookups in the IDL by caching the results.
6967 #define STATIC_CLASS_INFO_COUNT 3
6969 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6972 @brief Allocate a ClassInfo as raw memory.
6973 @return Pointer to the newly allocated ClassInfo.
6975 Except for the in_use flag, which is used only by the allocation and deallocation
6976 logic, we don't initialize the ClassInfo here.
6978 static ClassInfo* allocate_class_info( void ) {
6979 // In order to reduce the number of mallocs and frees, we return a static
6980 // instance of ClassInfo, if we can find one that we're not already using.
6981 // We rely on the fact that the compiler will implicitly initialize the
6982 // static instances so that in_use == 0.
6985 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6986 if( ! static_class_info[ i ].in_use ) {
6987 static_class_info[ i ].in_use = 1;
6988 return static_class_info + i;
6992 // The static ones are all in use. Malloc one.
6994 return safe_malloc( sizeof( ClassInfo ) );
6998 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6999 @param info Pointer to the ClassInfo to be cleared.
7001 static void clear_class_info( ClassInfo* info ) {
7006 // Free any malloc'd strings
7008 if( info->alias != info->alias_store )
7009 free( info->alias );
7011 if( info->class_name != info->class_name_store )
7012 free( info->class_name );
7014 free( info->source_def );
7016 info->alias = info->class_name = info->source_def = NULL;
7021 @brief Free a ClassInfo and everything it owns.
7022 @param info Pointer to the ClassInfo to be freed.
7024 static void free_class_info( ClassInfo* info ) {
7029 clear_class_info( info );
7031 // If it's one of the static instances, just mark it as not in use
7034 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7035 if( info == static_class_info + i ) {
7036 static_class_info[ i ].in_use = 0;
7041 // Otherwise it must have been malloc'd, so free it
7047 @brief Populate an already-allocated ClassInfo.
7048 @param info Pointer to the ClassInfo to be populated.
7049 @param alias Alias for the class. If it is NULL, or an empty string, use the class
7051 @param class Name of the class.
7052 @return Zero if successful, or 1 if not.
7054 Populate the ClassInfo with copies of the alias and class name, and with pointers to
7055 the relevant portions of the IDL for the specified class.
7057 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
7060 osrfLogError( OSRF_LOG_MARK,
7061 "%s ERROR: No ClassInfo available to populate", modulename );
7062 info->alias = info->class_name = info->source_def = NULL;
7063 info->class_def = info->fields = info->links = NULL;
7068 osrfLogError( OSRF_LOG_MARK,
7069 "%s ERROR: No class name provided for lookup", modulename );
7070 info->alias = info->class_name = info->source_def = NULL;
7071 info->class_def = info->fields = info->links = NULL;
7075 // Alias defaults to class name if not supplied
7076 if( ! alias || ! alias[ 0 ] )
7079 // Look up class info in the IDL
7080 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
7082 osrfLogError( OSRF_LOG_MARK,
7083 "%s ERROR: Class %s not defined in IDL", modulename, class );
7084 info->alias = info->class_name = info->source_def = NULL;
7085 info->class_def = info->fields = info->links = NULL;
7087 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
7088 osrfLogError( OSRF_LOG_MARK,
7089 "%s ERROR: Class %s is defined as virtual", modulename, class );
7090 info->alias = info->class_name = info->source_def = NULL;
7091 info->class_def = info->fields = info->links = NULL;
7095 osrfHash* links = osrfHashGet( class_def, "links" );
7097 osrfLogError( OSRF_LOG_MARK,
7098 "%s ERROR: No links defined in IDL for class %s", modulename, class );
7099 info->alias = info->class_name = info->source_def = NULL;
7100 info->class_def = info->fields = info->links = NULL;
7104 osrfHash* fields = osrfHashGet( class_def, "fields" );
7106 osrfLogError( OSRF_LOG_MARK,
7107 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
7108 info->alias = info->class_name = info->source_def = NULL;
7109 info->class_def = info->fields = info->links = NULL;
7113 char* source_def = oilsGetRelation( class_def );
7117 // We got everything we need, so populate the ClassInfo
7118 if( strlen( alias ) > ALIAS_STORE_SIZE )
7119 info->alias = strdup( alias );
7121 strcpy( info->alias_store, alias );
7122 info->alias = info->alias_store;
7125 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
7126 info->class_name = strdup( class );
7128 strcpy( info->class_name_store, class );
7129 info->class_name = info->class_name_store;
7132 info->source_def = source_def;
7134 info->class_def = class_def;
7135 info->links = links;
7136 info->fields = fields;
7141 #define STATIC_FRAME_COUNT 3
7143 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
7146 @brief Allocate a QueryFrame as raw memory.
7147 @return Pointer to the newly allocated QueryFrame.
7149 Except for the in_use flag, which is used only by the allocation and deallocation
7150 logic, we don't initialize the QueryFrame here.
7152 static QueryFrame* allocate_frame( void ) {
7153 // In order to reduce the number of mallocs and frees, we return a static
7154 // instance of QueryFrame, if we can find one that we're not already using.
7155 // We rely on the fact that the compiler will implicitly initialize the
7156 // static instances so that in_use == 0.
7159 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7160 if( ! static_frame[ i ].in_use ) {
7161 static_frame[ i ].in_use = 1;
7162 return static_frame + i;
7166 // The static ones are all in use. Malloc one.
7168 return safe_malloc( sizeof( QueryFrame ) );
7172 @brief Free a QueryFrame, and all the memory it owns.
7173 @param frame Pointer to the QueryFrame to be freed.
7175 static void free_query_frame( QueryFrame* frame ) {
7180 clear_class_info( &frame->core );
7182 // Free the join list
7184 ClassInfo* info = frame->join_list;
7187 free_class_info( info );
7191 frame->join_list = NULL;
7194 // If the frame is a static instance, just mark it as unused
7196 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7197 if( frame == static_frame + i ) {
7198 static_frame[ i ].in_use = 0;
7203 // Otherwise it must have been malloc'd, so free it
7209 @brief Search a given QueryFrame for a specified alias.
7210 @param frame Pointer to the QueryFrame to be searched.
7211 @param target The alias for which to search.
7212 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7214 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7215 if( ! frame || ! target ) {
7219 ClassInfo* found_class = NULL;
7221 if( !strcmp( target, frame->core.alias ) )
7222 return &(frame->core);
7224 ClassInfo* curr_class = frame->join_list;
7225 while( curr_class ) {
7226 if( strcmp( target, curr_class->alias ) )
7227 curr_class = curr_class->next;
7229 found_class = curr_class;
7239 @brief Push a new (blank) QueryFrame onto the stack.
7241 static void push_query_frame( void ) {
7242 QueryFrame* frame = allocate_frame();
7243 frame->join_list = NULL;
7244 frame->next = curr_query;
7246 // Initialize the ClassInfo for the core class
7247 ClassInfo* core = &frame->core;
7248 core->alias = core->class_name = core->source_def = NULL;
7249 core->class_def = core->fields = core->links = NULL;
7255 @brief Pop a QueryFrame off the stack and destroy it.
7257 static void pop_query_frame( void ) {
7262 QueryFrame* popped = curr_query;
7263 curr_query = popped->next;
7265 free_query_frame( popped );
7269 @brief Populate the ClassInfo for the core class.
7270 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7271 class name as an alias.
7272 @param class_name Name of the core class.
7273 @return Zero if successful, or 1 if not.
7275 Populate the ClassInfo of the core class with copies of the alias and class name, and
7276 with pointers to the relevant portions of the IDL for the core class.
7278 static int add_query_core( const char* alias, const char* class_name ) {
7281 if( ! curr_query ) {
7282 osrfLogError( OSRF_LOG_MARK,
7283 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7285 } else if( curr_query->core.alias ) {
7286 osrfLogError( OSRF_LOG_MARK,
7287 "%s ERROR: Core class %s already populated as %s",
7288 modulename, curr_query->core.class_name, curr_query->core.alias );
7292 build_class_info( &curr_query->core, alias, class_name );
7293 if( curr_query->core.alias )
7296 osrfLogError( OSRF_LOG_MARK,
7297 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7303 @brief Search the current QueryFrame for a specified alias.
7304 @param target The alias for which to search.
7305 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7307 static inline ClassInfo* search_alias( const char* target ) {
7308 return search_alias_in_frame( curr_query, target );
7312 @brief Search all levels of query for a specified alias, starting with the current query.
7313 @param target The alias for which to search.
7314 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7316 static ClassInfo* search_all_alias( const char* target ) {
7317 ClassInfo* found_class = NULL;
7318 QueryFrame* curr_frame = curr_query;
7320 while( curr_frame ) {
7321 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7324 curr_frame = curr_frame->next;
7331 @brief Add a class to the list of classes joined to the current query.
7332 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7333 the class name as an alias.
7334 @param classname The name of the class to be added.
7335 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7337 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7339 if( ! classname || ! *classname ) { // sanity check
7340 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7347 const ClassInfo* conflict = search_alias( alias );
7349 osrfLogError( OSRF_LOG_MARK,
7350 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7351 modulename, alias, conflict->class_name );
7355 ClassInfo* info = allocate_class_info();
7357 if( build_class_info( info, alias, classname ) ) {
7358 free_class_info( info );
7362 // Add the new ClassInfo to the join list of the current QueryFrame
7363 info->next = curr_query->join_list;
7364 curr_query->join_list = info;
7370 @brief Destroy all nodes on the query stack.
7372 static void clear_query_stack( void ) {
7378 @brief Implement the set_audit_info method.
7379 @param ctx Pointer to the method context.
7380 @return Zero if successful, or -1 if not.
7382 Issue a SAVEPOINT to the database server.
7387 - workstation id (int)
7389 If user id is not provided the authkey will be used.
7390 For PCRUD the authkey is always used, even if a user is provided.
7392 int setAuditInfo( osrfMethodContext* ctx ) {
7393 if(osrfMethodVerifyContext( ctx )) {
7394 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
7398 // Get the user id from the parameters
7399 const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7401 if( enforce_pcrud || !user_id ) {
7402 timeout_needs_resetting = 1;
7403 const jsonObject* user = verifyUserPCRUD( ctx );
7406 osrfAppRespondComplete( ctx, NULL );
7410 // Not PCRUD and have a user_id?
7411 int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7412 osrfAppRespondComplete( ctx, NULL );
7417 @brief Save a audit info
7418 @param ctx Pointer to the method context.
7419 @param user_id User ID to write as a string
7420 @param ws_id Workstation ID to write as a string
7422 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7423 if( ctx && ctx->session ) {
7424 osrfAppSession* session = ctx->session;
7426 osrfHash* cache = session->userData;
7428 // If the session doesn't already have a hash, create one. Make sure
7429 // that the application session frees the hash when it terminates.
7430 if( NULL == cache ) {
7431 session->userData = cache = osrfNewHash();
7432 osrfHashSetCallback( cache, &sessionDataFree );
7433 ctx->session->userDataFree = &userDataFree;
7436 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7438 osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7440 int errnum = dbi_conn_error( writehandle, &msg );
7443 "%s: Error setting auditor information: %d %s",
7446 msg ? msg : "(No description available)"
7448 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7449 "osrfMethodException", ctx->request, "Error setting auditor info" );
7450 if( !oilsIsDBConnected( writehandle ))
7451 osrfAppSessionPanic( ctx->session );
7454 dbi_result_free( result );
7461 @brief Remove all but safe character from savepoint name
7462 @param sp User-supplied savepoint name
7463 @return sanitized savepoint name, or NULL
7465 The caller is expected to free the returned string. Note that
7466 this function exists only because we can't use PQescapeLiteral
7467 without either forking libdbi or abandoning it.
7469 static char* _sanitize_savepoint_name( const char* sp ) {
7471 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_";
7473 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7474 // and the default value of NAMEDATALEN is 64; that should be long enough
7475 // for our purposes, and it's unlikely that anyone is going to recompile
7476 // PostgreSQL to have a smaller value, so cap the identifier name
7477 // accordingly to avoid the remote chance that someone manages to pass in a
7478 // 12GB savepoint name
7479 const int MAX_LITERAL_NAMELEN = 63;
7482 if (len > MAX_LITERAL_NAMELEN) {
7483 len = MAX_LITERAL_NAMELEN;
7486 char* safeSpName = safe_malloc( len + 1 );
7490 for (j = 0; j < len; j++) {
7491 found = strchr(safe_chars, sp[j]);
7493 safeSpName[ i++ ] = found[0];
7496 safeSpName[ i ] = '\0';