3 @brief Utility routines for translating JSON into SQL.
11 #include "opensrf/utils.h"
12 #include "opensrf/log.h"
13 #include "opensrf/osrf_application.h"
14 #include "openils/oils_utils.h"
15 #include "openils/oils_sql.h"
17 // The next four macros are OR'd together as needed to form a set
18 // of bitflags. SUBCOMBO enables an extra pair of parentheses when
19 // nesting one UNION, INTERSECT or EXCEPT inside another.
20 // SUBSELECT tells us we're in a subquery, so don't add the
21 // terminal semicolon yet.
24 #define DISABLE_I18N 2
25 #define SELECT_DISTINCT 1
30 struct ClassInfoStruct;
31 typedef struct ClassInfoStruct ClassInfo;
33 #define ALIAS_STORE_SIZE 16
34 #define CLASS_NAME_STORE_SIZE 16
36 struct ClassInfoStruct {
40 osrfHash* class_def; // Points into IDL
41 osrfHash* fields; // Points into IDL
42 osrfHash* links; // Points into IDL
44 // The remaining members are private and internal. Client code should not
45 // access them directly.
47 ClassInfo* next; // Supports linked list of joined classes
48 int in_use; // boolean
50 // We usually store the alias and class name in the following arrays, and
51 // point the corresponding pointers at them. When the string is too big
52 // for the array (which will probably never happen in practice), we strdup it.
54 char alias_store[ ALIAS_STORE_SIZE + 1 ];
55 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
58 struct QueryFrameStruct;
59 typedef struct QueryFrameStruct QueryFrame;
61 struct QueryFrameStruct {
63 ClassInfo* join_list; // linked list of classes joined to the core class
64 QueryFrame* next; // implements stack as linked list
65 int in_use; // boolean
68 static int timeout_needs_resetting;
69 static time_t time_next_reset;
71 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
73 static void setXactId( osrfMethodContext* ctx );
74 static inline const char* getXactId( osrfMethodContext* ctx );
75 static inline void clearXactId( osrfMethodContext* ctx );
77 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
78 jsonObject* where_hash, jsonObject* query_hash, int* err );
79 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
80 static jsonObject* oilsMakeJSONFromResult( dbi_result );
82 static char* searchSimplePredicate ( const char* op, const char* class_alias,
83 osrfHash* field, const jsonObject* node );
84 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
85 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject* );
86 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*,
88 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
89 static char* searchINPredicate ( const char*, osrfHash*,
90 jsonObject*, const char*, osrfMethodContext* );
91 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
92 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
93 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo*, int, osrfMethodContext* );
94 static char* buildSELECT( const jsonObject*, jsonObject* rest_of_query,
95 osrfHash* meta, osrfMethodContext* ctx );
96 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array );
98 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
100 char* SELECT ( osrfMethodContext*, jsonObject*, const jsonObject*, const jsonObject*,
101 const jsonObject*, const jsonObject*, const jsonObject*, const jsonObject*, int );
103 static osrfStringArray* getPermLocationCache( osrfMethodContext*, const char* );
104 static void setPermLocationCache( osrfMethodContext*, const char*, osrfStringArray* );
106 void userDataFree( void* );
107 static void sessionDataFree( char*, void* );
108 static void pcacheFree( char*, void* );
109 static int obj_is_true( const jsonObject* obj );
110 static const char* json_type( int code );
111 static const char* get_primitive( osrfHash* field );
112 static const char* get_datatype( osrfHash* field );
113 static void pop_query_frame( void );
114 static void push_query_frame( void );
115 static int add_query_core( const char* alias, const char* class_name );
116 static inline ClassInfo* search_alias( const char* target );
117 static ClassInfo* search_all_alias( const char* target );
118 static ClassInfo* add_joined_class( const char* alias, const char* classname );
119 static void clear_query_stack( void );
121 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
122 static const jsonObject* verifyUserPCRUDfull( osrfMethodContext*, int );
123 static int verifyObjectPCRUD( osrfMethodContext*, osrfHash*, const jsonObject*, int );
124 static const char* org_tree_root( osrfMethodContext* ctx );
125 static jsonObject* single_hash( const char* key, const char* value );
127 static int child_initialized = 0; /* boolean */
129 static dbi_conn writehandle; /* our MASTER db connection */
130 static dbi_conn dbhandle; /* our CURRENT db connection */
131 //static osrfHash * readHandles;
133 // The following points to the top of a stack of QueryFrames. It's a little
134 // confusing because the top level of the query is at the bottom of the stack.
135 static QueryFrame* curr_query = NULL;
137 static dbi_conn writehandle; /* our MASTER db connection */
138 static dbi_conn dbhandle; /* our CURRENT db connection */
139 //static osrfHash * readHandles;
141 static int max_flesh_depth = 100;
143 static int perm_at_threshold = 5;
144 static int enforce_pcrud = 0; // Boolean
145 static char* modulename = NULL;
147 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id);
149 static char* _sanitize_savepoint_name( const char* sp );
152 @brief Connect to the database.
153 @return A database connection if successful, or NULL if not.
155 dbi_conn oilsConnectDB( const char* mod_name ) {
157 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
158 if( dbi_initialize( NULL ) == -1 ) {
159 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
162 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
164 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
165 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
166 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
167 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
168 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
169 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
171 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
172 dbi_conn handle = dbi_conn_new( driver );
175 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
178 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
180 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
181 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
183 if( host ) dbi_conn_set_option( handle, "host", host );
184 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
185 if( user ) dbi_conn_set_option( handle, "username", user );
186 if( pw ) dbi_conn_set_option( handle, "password", pw );
187 if( db ) dbi_conn_set_option( handle, "dbname", db );
195 if( dbi_conn_connect( handle ) < 0 ) {
197 if( dbi_conn_connect( handle ) < 0 ) {
199 dbi_conn_error( handle, &msg );
200 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
201 msg ? msg : "(No description available)" );
206 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
212 @brief Select some options.
213 @param module_name: Name of the server.
214 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
216 This source file is used (at this writing) to implement three different servers:
217 - open-ils.reporter-store
221 These servers behave mostly the same, but they implement different combinations of
222 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
224 Here we use the server name in messages to identify which kind of server issued them.
225 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
227 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
229 module_name = "open-ils.cstore"; // bulletproofing with a default
234 modulename = strdup( module_name );
235 enforce_pcrud = do_pcrud;
236 max_flesh_depth = flesh_depth;
240 @brief Install a database connection.
241 @param conn Pointer to a database connection.
243 In some contexts, @a conn may merely provide a driver so that we can process strings
244 properly, without providing an open database connection.
246 void oilsSetDBConnection( dbi_conn conn ) {
247 dbhandle = writehandle = conn;
251 @brief Determine whether a database connection is alive.
252 @param handle Handle for a database connection.
253 @return 1 if the connection is alive, or zero if it isn't.
255 int oilsIsDBConnected( dbi_conn handle ) {
256 // Do an innocuous SELECT. If it succeeds, the database connection is still good.
257 dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
259 dbi_result_free( result );
262 // This is a terrible, horrible, no good, very bad kludge.
263 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
264 // but because (due to a previous error) the database is ignoring all commands,
265 // even innocuous SELECTs, until the current transaction is rolled back. The only
266 // known way to detect this condition via the dbi library is by looking at the error
267 // message. This approach will break if the language or wording of the message ever
269 // Note: the dbi_conn_ping function purports to determine whether the database
270 // connection is live, but at this writing this function is unreliable and useless.
271 static const char* ok_msg = "ERROR: current transaction is aborted, commands "
272 "ignored until end of transaction block\n";
274 dbi_conn_error( handle, &msg );
275 if( strcmp( msg, ok_msg )) {
276 osrfLogError( OSRF_LOG_MARK, "Database connection isn't working" );
279 return 1; // ignoring SELECT due to previous error; that's okay
284 @brief Get a table name, view name, or subquery for use in a FROM clause.
285 @param class Pointer to the IDL class entry.
286 @return A table name, a view name, or a subquery in parentheses.
288 In some cases the IDL defines a class, not with a table name or a view name, but with
289 a SELECT statement, which may be used as a subquery.
291 char* oilsGetRelation( osrfHash* classdef ) {
293 char* source_def = NULL;
294 const char* tabledef = osrfHashGet( classdef, "tablename" );
297 source_def = strdup( tabledef ); // Return the name of a table or view
299 tabledef = osrfHashGet( classdef, "source_definition" );
301 // Return a subquery, enclosed in parentheses
302 source_def = safe_malloc( strlen( tabledef ) + 3 );
303 source_def[ 0 ] = '(';
304 strcpy( source_def + 1, tabledef );
305 strcat( source_def, ")" );
307 // Not found: return an error
308 const char* classname = osrfHashGet( classdef, "classname" );
313 "%s ERROR No tablename or source_definition for class \"%s\"",
324 @brief Add datatypes from the database to the fields in the IDL.
325 @param handle Handle for a database connection
326 @return Zero if successful, or 1 upon error.
328 For each relevant class in the IDL: ask the database for the datatype of every field.
329 In particular, determine which fields are text fields and which fields are numeric
330 fields, so that we know whether to enclose their values in quotes.
332 int oilsExtendIDL( dbi_conn handle ) {
333 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
334 osrfHash* class = NULL;
335 growing_buffer* query_buf = buffer_init( 64 );
336 int results_found = 0; // boolean
338 // For each class in the IDL...
339 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
340 const char* classname = osrfHashIteratorKey( class_itr );
341 osrfHash* fields = osrfHashGet( class, "fields" );
343 // If the class is virtual, ignore it
344 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
345 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
349 char* tabledef = oilsGetRelation( class );
351 continue; // No such relation -- a query of it would be doomed to failure
353 buffer_reset( query_buf );
354 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
358 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
359 modulename, OSRF_BUFFER_C_STR( query_buf ) );
361 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
366 const char* columnName;
367 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
369 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
372 /* fetch the fieldmapper index */
373 osrfHash* _f = osrfHashGet(fields, columnName);
376 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
378 /* determine the field type and storage attributes */
380 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
382 case DBI_TYPE_INTEGER : {
384 if( !osrfHashGet(_f, "primitive") )
385 osrfHashSet(_f, "number", "primitive");
387 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
388 if( attr & DBI_INTEGER_SIZE8 )
389 osrfHashSet( _f, "INT8", "datatype" );
391 osrfHashSet( _f, "INT", "datatype" );
394 case DBI_TYPE_DECIMAL :
395 if( !osrfHashGet( _f, "primitive" ))
396 osrfHashSet( _f, "number", "primitive" );
398 osrfHashSet( _f, "NUMERIC", "datatype" );
401 case DBI_TYPE_STRING :
402 if( !osrfHashGet( _f, "primitive" ))
403 osrfHashSet( _f, "string", "primitive" );
405 osrfHashSet( _f,"TEXT", "datatype" );
408 case DBI_TYPE_DATETIME :
409 if( !osrfHashGet( _f, "primitive" ))
410 osrfHashSet( _f, "string", "primitive" );
412 osrfHashSet( _f, "TIMESTAMP", "datatype" );
415 case DBI_TYPE_BINARY :
416 if( !osrfHashGet( _f, "primitive" ))
417 osrfHashSet( _f, "string", "primitive" );
419 osrfHashSet( _f, "BYTEA", "datatype" );
424 "Setting [%s] to primitive [%s] and datatype [%s]...",
426 osrfHashGet( _f, "primitive" ),
427 osrfHashGet( _f, "datatype" )
431 } // end while loop for traversing columns of result
432 dbi_result_free( result );
435 int errnum = dbi_conn_error( handle, &msg );
436 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
437 errnum, msg ? msg : "(No description available)" );
438 // We don't check the database connection here. It's routine to get failures at
439 // this point; we routinely try to query tables that don't exist, because they
440 // are defined in the IDL but not in the database.
442 } // end for each class in IDL
444 buffer_free( query_buf );
445 osrfHashIteratorFree( class_itr );
446 child_initialized = 1;
448 if( !results_found ) {
449 osrfLogError( OSRF_LOG_MARK,
450 "No results found for any class -- bad database connection?" );
452 } else if( ! oilsIsDBConnected( handle )) {
453 osrfLogError( OSRF_LOG_MARK,
454 "Unable to extend IDL: database connection isn't working" );
462 @brief Free an osrfHash that stores a transaction ID.
463 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
465 This function is a callback, to be called by the application session when it ends.
466 The application session stores the osrfHash via an opaque pointer.
468 If the osrfHash contains an entry for the key "xact_id", it means that an
469 uncommitted transaction is pending. Roll it back.
471 void userDataFree( void* blob ) {
472 osrfHash* hash = (osrfHash*) blob;
473 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
474 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
476 int errnum = dbi_conn_error( writehandle, &msg );
477 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
478 errnum, msg ? msg : "(No description available)" );
482 if( !dbi_conn_query( writehandle, "SELECT auditor.clear_audit_info();" ) ) {
484 int errnum = dbi_conn_error( writehandle, &msg );
485 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform audit info clearing: %d %s",
486 errnum, msg ? msg : "(No description available)" );
490 osrfHashFree( hash );
494 @name Managing session data
495 @brief Maintain data stored via the userData pointer of the application session.
497 Currently, session-level data is stored in an osrfHash. Other arrangements are
498 possible, and some would be more efficient. The application session calls a
499 callback function to free userData before terminating.
501 Currently, the only data we store at the session level is the transaction id. By this
502 means we can ensure that any pending transactions are rolled back before the application
508 @brief Free an item in the application session's userData.
509 @param key The name of a key for an osrfHash.
510 @param item An opaque pointer to the item associated with the key.
512 We store an osrfHash as userData with the application session, and arrange (by
513 installing userDataFree() as a different callback) for the session to free that
514 osrfHash before terminating.
516 This function is a callback for freeing items in the osrfHash. Currently we store
518 - Transaction id of a pending transaction; a character string. Key: "xact_id".
519 - Authkey; a character string. Key: "authkey".
520 - User object from the authentication server; a jsonObject. Key: "user_login".
522 If we ever store anything else in userData, we will need to revisit this function so
523 that it will free whatever else needs freeing.
525 static void sessionDataFree( char* key, void* item ) {
526 if( !strcmp( key, "xact_id" ) || !strcmp( key, "authkey" ) || !strncmp( key, "rs_size_", 8) )
528 else if( !strcmp( key, "user_login" ) )
529 jsonObjectFree( (jsonObject*) item );
530 else if( !strcmp( key, "pcache" ) )
531 osrfHashFree( (osrfHash*) item );
534 static void pcacheFree( char* key, void* item ) {
535 osrfStringArrayFree( (osrfStringArray*) item );
539 @brief Initialize session cache.
540 @param ctx Pointer to the method context.
542 Create a cache for the session by making the session's userData member point
543 to an osrfHash instance.
545 static osrfHash* initSessionCache( osrfMethodContext* ctx ) {
546 ctx->session->userData = osrfNewHash();
547 osrfHashSetCallback( (osrfHash*) ctx->session->userData, &sessionDataFree );
548 ctx->session->userDataFree = &userDataFree;
549 return ctx->session->userData;
553 @brief Save a transaction id.
554 @param ctx Pointer to the method context.
556 Save the session_id of the current application session as a transaction id.
558 static void setXactId( osrfMethodContext* ctx ) {
559 if( ctx && ctx->session ) {
560 osrfAppSession* session = ctx->session;
562 osrfHash* cache = session->userData;
564 // If the session doesn't already have a hash, create one. Make sure
565 // that the application session frees the hash when it terminates.
567 cache = initSessionCache( ctx );
569 // Save the transaction id in the hash, with the key "xact_id"
570 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
575 @brief Get the transaction ID for the current transaction, if any.
576 @param ctx Pointer to the method context.
577 @return Pointer to the transaction ID.
579 The return value points to an internal buffer, and will become invalid upon issuing
580 a commit or rollback.
582 static inline const char* getXactId( osrfMethodContext* ctx ) {
583 if( ctx && ctx->session && ctx->session->userData )
584 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
590 @brief Clear the current transaction id.
591 @param ctx Pointer to the method context.
593 static inline void clearXactId( osrfMethodContext* ctx ) {
594 if( ctx && ctx->session && ctx->session->userData )
595 osrfHashRemove( ctx->session->userData, "xact_id" );
600 @brief Stash the location for a particular perm in the sessionData cache
601 @param ctx Pointer to the method context.
602 @param perm Name of the permission we're looking at
603 @param array StringArray of perm location ids
605 static void setPermLocationCache( osrfMethodContext* ctx, const char* perm, osrfStringArray* locations ) {
606 if( ctx && ctx->session ) {
607 osrfAppSession* session = ctx->session;
609 osrfHash* cache = session->userData;
611 // If the session doesn't already have a hash, create one. Make sure
612 // that the application session frees the hash when it terminates.
614 cache = initSessionCache( ctx );
616 osrfHash* pcache = osrfHashGet(cache, "pcache");
618 if( NULL == pcache ) {
619 pcache = osrfNewHash();
620 osrfHashSetCallback( pcache, &pcacheFree );
621 osrfHashSet( cache, pcache, "pcache" );
624 if( perm && locations )
625 osrfHashSet( pcache, locations, strdup(perm) );
630 @brief Grab stashed location for a particular perm in the sessionData cache
631 @param ctx Pointer to the method context.
632 @param perm Name of the permission we're looking at
634 static osrfStringArray* getPermLocationCache( osrfMethodContext* ctx, const char* perm ) {
635 if( ctx && ctx->session ) {
636 osrfAppSession* session = ctx->session;
637 osrfHash* cache = session->userData;
639 osrfHash* pcache = osrfHashGet(cache, "pcache");
641 return osrfHashGet( pcache, perm );
650 @brief Save the user's login in the userData for the current application session.
651 @param ctx Pointer to the method context.
652 @param user_login Pointer to the user login object to be cached (we cache the original,
655 If @a user_login is NULL, remove the user login if one is already cached.
657 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
658 if( ctx && ctx->session ) {
659 osrfAppSession* session = ctx->session;
661 osrfHash* cache = session->userData;
663 // If the session doesn't already have a hash, create one. Make sure
664 // that the application session frees the hash when it terminates.
666 cache = initSessionCache( ctx );
669 osrfHashSet( cache, user_login, "user_login" );
671 osrfHashRemove( cache, "user_login" );
676 @brief Get the user login object for the current application session, if any.
677 @param ctx Pointer to the method context.
678 @return Pointer to the user login object if found; otherwise NULL.
680 The user login object was returned from the authentication server, and then cached so
681 we don't have to call the authentication server again for the same user.
683 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
684 if( ctx && ctx->session && ctx->session->userData )
685 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
691 @brief Save a copy of an authkey in the userData of the current application session.
692 @param ctx Pointer to the method context.
693 @param authkey The authkey to be saved.
695 If @a authkey is NULL, remove the authkey if one is already cached.
697 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
698 if( ctx && ctx->session && authkey ) {
699 osrfAppSession* session = ctx->session;
700 osrfHash* cache = session->userData;
702 // If the session doesn't already have a hash, create one. Make sure
703 // that the application session frees the hash when it terminates.
705 cache = initSessionCache( ctx );
707 // Save the transaction id in the hash, with the key "xact_id"
708 if( authkey && *authkey )
709 osrfHashSet( cache, strdup( authkey ), "authkey" );
711 osrfHashRemove( cache, "authkey" );
716 @brief Reset the login timeout.
717 @param authkey The authentication key for the current login session.
718 @param now The current time.
719 @return Zero if successful, or 1 if not.
721 Tell the authentication server to reset the timeout so that the login session won't
722 expire for a while longer.
724 We could dispense with the @a now parameter by calling time(). But we just called
725 time() in order to decide whether to reset the timeout, so we might as well reuse
726 the result instead of calling time() again.
728 static int reset_timeout( const char* authkey, time_t now ) {
729 jsonObject* auth_object = jsonNewObject( authkey );
731 // Ask the authentication server to reset the timeout. It returns an event
732 // indicating success or failure.
733 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
734 "open-ils.auth.session.reset_timeout", auth_object );
735 jsonObjectFree( auth_object );
737 if( !result || result->type != JSON_HASH ) {
738 osrfLogError( OSRF_LOG_MARK,
739 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
740 jsonObjectFree( result );
741 return 1; // Not the right sort of object returned
744 const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
745 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
746 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
747 jsonObjectFree( result );
748 return 1; // Return code from method not available
751 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
752 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
754 desc = "(No reason available)"; // failsafe; shouldn't happen
755 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
756 jsonObjectFree( result );
760 // Revise our local proxy for the timeout deadline
761 // by a smallish fraction of the timeout interval
762 const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
764 timeout = "1"; // failsafe; shouldn't happen
765 time_next_reset = now + atoi( timeout ) / 15;
767 jsonObjectFree( result );
768 return 0; // Successfully reset timeout
772 @brief Get the authkey string for the current application session, if any.
773 @param ctx Pointer to the method context.
774 @return Pointer to the cached authkey if found; otherwise NULL.
776 If present, the authkey string was cached from a previous method call.
778 static const char* getAuthkey( osrfMethodContext* ctx ) {
779 if( ctx && ctx->session && ctx->session->userData ) {
780 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
781 // LFW recent changes mean the userData hash gets set up earlier, but
782 // doesn't necessarily have an authkey yet
786 // Possibly reset the authentication timeout to keep the login alive. We do so
787 // no more than once per method call, and not at all if it has been only a short
788 // time since the last reset.
790 // Here we reset explicitly, if at all. We also implicitly reset the timeout
791 // whenever we call the "open-ils.auth.session.retrieve" method.
792 if( timeout_needs_resetting ) {
793 time_t now = time( NULL );
794 if( now >= time_next_reset && reset_timeout( authkey, now ) )
795 authkey = NULL; // timeout has apparently expired already
798 timeout_needs_resetting = 0;
806 @brief Implement the transaction.begin method.
807 @param ctx Pointer to the method context.
808 @return Zero if successful, or -1 upon error.
810 Start a transaction. Save a transaction ID for future reference.
813 - authkey (PCRUD only)
815 Return to client: Transaction ID
817 int beginTransaction( osrfMethodContext* ctx ) {
818 if(osrfMethodVerifyContext( ctx )) {
819 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
823 if( enforce_pcrud ) {
824 timeout_needs_resetting = 1;
825 const jsonObject* user = verifyUserPCRUD( ctx );
830 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
833 int errnum = dbi_conn_error( writehandle, &msg );
834 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
835 modulename, errnum, msg ? msg : "(No description available)" );
836 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
837 "osrfMethodException", ctx->request, "Error starting transaction" );
838 if( !oilsIsDBConnected( writehandle ))
839 osrfAppSessionPanic( ctx->session );
842 dbi_result_free( result );
844 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
845 osrfAppRespondComplete( ctx, ret );
846 jsonObjectFree( ret );
852 @brief Implement the savepoint.set method.
853 @param ctx Pointer to the method context.
854 @return Zero if successful, or -1 if not.
856 Issue a SAVEPOINT to the database server.
859 - authkey (PCRUD only)
862 Return to client: Savepoint name
864 int setSavepoint( osrfMethodContext* ctx ) {
865 if(osrfMethodVerifyContext( ctx )) {
866 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
871 if( enforce_pcrud ) {
873 timeout_needs_resetting = 1;
874 const jsonObject* user = verifyUserPCRUD( ctx );
879 // Verify that a transaction is pending
880 const char* trans_id = getXactId( ctx );
881 if( NULL == trans_id ) {
882 osrfAppSessionStatus(
884 OSRF_STATUS_INTERNALSERVERERROR,
885 "osrfMethodException",
887 "No active transaction -- required for savepoints"
892 // Get the savepoint name from the method params
893 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
896 osrfLogWarning(OSRF_LOG_MARK, "savepoint.set called with no name");
900 char *safeSpName = _sanitize_savepoint_name( spName );
902 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", safeSpName );
906 int errnum = dbi_conn_error( writehandle, &msg );
909 "%s: Error creating savepoint %s in transaction %s: %d %s",
914 msg ? msg : "(No description available)"
916 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
917 "osrfMethodException", ctx->request, "Error creating savepoint" );
918 if( !oilsIsDBConnected( writehandle ))
919 osrfAppSessionPanic( ctx->session );
922 dbi_result_free( result );
923 jsonObject* ret = jsonNewObject( spName );
924 osrfAppRespondComplete( ctx, ret );
925 jsonObjectFree( ret );
931 @brief Implement the savepoint.release method.
932 @param ctx Pointer to the method context.
933 @return Zero if successful, or -1 if not.
935 Issue a RELEASE SAVEPOINT to the database server.
938 - authkey (PCRUD only)
941 Return to client: Savepoint name
943 int releaseSavepoint( osrfMethodContext* ctx ) {
944 if(osrfMethodVerifyContext( ctx )) {
945 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
950 if( enforce_pcrud ) {
952 timeout_needs_resetting = 1;
953 const jsonObject* user = verifyUserPCRUD( ctx );
958 // Verify that a transaction is pending
959 const char* trans_id = getXactId( ctx );
960 if( NULL == trans_id ) {
961 osrfAppSessionStatus(
963 OSRF_STATUS_INTERNALSERVERERROR,
964 "osrfMethodException",
966 "No active transaction -- required for savepoints"
971 // Get the savepoint name from the method params
972 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
975 osrfLogWarning(OSRF_LOG_MARK, "savepoint.release called with no name");
979 char *safeSpName = _sanitize_savepoint_name( spName );
981 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", safeSpName );
985 int errnum = dbi_conn_error( writehandle, &msg );
988 "%s: Error releasing savepoint %s in transaction %s: %d %s",
993 msg ? msg : "(No description available)"
995 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
996 "osrfMethodException", ctx->request, "Error releasing savepoint" );
997 if( !oilsIsDBConnected( writehandle ))
998 osrfAppSessionPanic( ctx->session );
1001 dbi_result_free( result );
1002 jsonObject* ret = jsonNewObject( spName );
1003 osrfAppRespondComplete( ctx, ret );
1004 jsonObjectFree( ret );
1010 @brief Implement the savepoint.rollback method.
1011 @param ctx Pointer to the method context.
1012 @return Zero if successful, or -1 if not.
1014 Issue a ROLLBACK TO SAVEPOINT to the database server.
1017 - authkey (PCRUD only)
1020 Return to client: Savepoint name
1022 int rollbackSavepoint( osrfMethodContext* ctx ) {
1023 if(osrfMethodVerifyContext( ctx )) {
1024 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1029 if( enforce_pcrud ) {
1031 timeout_needs_resetting = 1;
1032 const jsonObject* user = verifyUserPCRUD( ctx );
1037 // Verify that a transaction is pending
1038 const char* trans_id = getXactId( ctx );
1039 if( NULL == trans_id ) {
1040 osrfAppSessionStatus(
1042 OSRF_STATUS_INTERNALSERVERERROR,
1043 "osrfMethodException",
1045 "No active transaction -- required for savepoints"
1050 // Get the savepoint name from the method params
1051 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1054 osrfLogWarning(OSRF_LOG_MARK, "savepoint.rollback called with no name");
1058 char *safeSpName = _sanitize_savepoint_name( spName );
1060 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", safeSpName );
1064 int errnum = dbi_conn_error( writehandle, &msg );
1067 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
1072 msg ? msg : "(No description available)"
1074 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1075 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1076 if( !oilsIsDBConnected( writehandle ))
1077 osrfAppSessionPanic( ctx->session );
1080 dbi_result_free( result );
1081 jsonObject* ret = jsonNewObject( spName );
1082 osrfAppRespondComplete( ctx, ret );
1083 jsonObjectFree( ret );
1089 @brief Implement the transaction.commit method.
1090 @param ctx Pointer to the method context.
1091 @return Zero if successful, or -1 if not.
1093 Issue a COMMIT to the database server.
1096 - authkey (PCRUD only)
1098 Return to client: Transaction ID.
1100 int commitTransaction( osrfMethodContext* ctx ) {
1101 if(osrfMethodVerifyContext( ctx )) {
1102 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1106 if( enforce_pcrud ) {
1107 timeout_needs_resetting = 1;
1108 const jsonObject* user = verifyUserPCRUD( ctx );
1113 // Verify that a transaction is pending
1114 const char* trans_id = getXactId( ctx );
1115 if( NULL == trans_id ) {
1116 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1117 "osrfMethodException", ctx->request, "No active transaction to commit" );
1121 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1124 int errnum = dbi_conn_error( writehandle, &msg );
1125 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1126 modulename, errnum, msg ? msg : "(No description available)" );
1127 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1128 "osrfMethodException", ctx->request, "Error committing transaction" );
1129 if( !oilsIsDBConnected( writehandle ))
1130 osrfAppSessionPanic( ctx->session );
1133 dbi_result_free( result );
1134 jsonObject* ret = jsonNewObject( trans_id );
1135 osrfAppRespondComplete( ctx, ret );
1136 jsonObjectFree( ret );
1143 @brief Implement the transaction.rollback method.
1144 @param ctx Pointer to the method context.
1145 @return Zero if successful, or -1 if not.
1147 Issue a ROLLBACK to the database server.
1150 - authkey (PCRUD only)
1152 Return to client: Transaction ID
1154 int rollbackTransaction( osrfMethodContext* ctx ) {
1155 if( osrfMethodVerifyContext( ctx )) {
1156 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1160 if( enforce_pcrud ) {
1161 timeout_needs_resetting = 1;
1162 const jsonObject* user = verifyUserPCRUD( ctx );
1167 // Verify that a transaction is pending
1168 const char* trans_id = getXactId( ctx );
1169 if( NULL == trans_id ) {
1170 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1171 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1175 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1178 int errnum = dbi_conn_error( writehandle, &msg );
1179 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1180 modulename, errnum, msg ? msg : "(No description available)" );
1181 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1182 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1183 if( !oilsIsDBConnected( writehandle ))
1184 osrfAppSessionPanic( ctx->session );
1187 dbi_result_free( result );
1188 jsonObject* ret = jsonNewObject( trans_id );
1189 osrfAppRespondComplete( ctx, ret );
1190 jsonObjectFree( ret );
1197 @brief Implement the "search" method.
1198 @param ctx Pointer to the method context.
1199 @return Zero if successful, or -1 if not.
1202 - authkey (PCRUD only)
1203 - WHERE clause, as jsonObject
1204 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1206 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1207 Optionally flesh linked fields.
1209 int doSearch( osrfMethodContext* ctx ) {
1210 if( osrfMethodVerifyContext( ctx )) {
1211 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1216 timeout_needs_resetting = 1;
1218 jsonObject* where_clause;
1219 jsonObject* rest_of_query;
1221 if( enforce_pcrud ) {
1222 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1223 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1225 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1226 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1229 if( !where_clause ) {
1230 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1234 // Get the class metadata
1235 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1236 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1240 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1242 osrfAppRespondComplete( ctx, NULL );
1246 // doFieldmapperSearch() now takes care of our responding for us
1247 // // Return each row to the client
1248 // jsonObject* cur = 0;
1249 // unsigned long res_idx = 0;
1251 // while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1252 // // We used to discard based on perms here, but now that's
1253 // // inside doFieldmapperSearch()
1254 // osrfAppRespond( ctx, cur );
1257 jsonObjectFree( obj );
1259 osrfAppRespondComplete( ctx, NULL );
1264 @brief Implement the "id_list" method.
1265 @param ctx Pointer to the method context.
1266 @param err Pointer through which to return an error code.
1267 @return Zero if successful, or -1 if not.
1270 - authkey (PCRUD only)
1271 - WHERE clause, as jsonObject
1272 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1274 Return to client: The primary key values for all rows of the relevant class that
1275 satisfy a specified WHERE clause.
1277 This method relies on the assumption that every class has a primary key consisting of
1280 int doIdList( osrfMethodContext* ctx ) {
1281 if( osrfMethodVerifyContext( ctx )) {
1282 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1287 timeout_needs_resetting = 1;
1289 jsonObject* where_clause;
1290 jsonObject* rest_of_query;
1292 // We use the where clause without change. But we need to massage the rest of the
1293 // query, so we work with a copy of it instead of modifying the original.
1295 if( enforce_pcrud ) {
1296 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1297 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1299 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1300 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1303 if( !where_clause ) {
1304 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1308 // Eliminate certain SQL clauses, if present.
1309 if( rest_of_query ) {
1310 jsonObjectRemoveKey( rest_of_query, "select" );
1311 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1312 jsonObjectRemoveKey( rest_of_query, "flesh" );
1313 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1315 rest_of_query = jsonNewObjectType( JSON_HASH );
1318 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1320 // Get the class metadata
1321 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1322 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1324 // Build a SELECT list containing just the primary key,
1325 // i.e. like { "classname":["keyname"] }
1326 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1328 // Load array with name of primary key
1329 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1330 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1331 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1333 jsonObjectSetKey( rest_of_query, "select", select_clause );
1338 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1340 jsonObjectFree( rest_of_query );
1342 osrfAppRespondComplete( ctx, NULL );
1346 // Return each primary key value to the client
1348 unsigned long res_idx = 0;
1349 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1350 // We used to discard based on perms here, but now that's
1351 // inside doFieldmapperSearch()
1352 osrfAppRespond( ctx,
1353 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1356 jsonObjectFree( obj );
1357 osrfAppRespondComplete( ctx, NULL );
1362 @brief Verify that we have a valid class reference.
1363 @param ctx Pointer to the method context.
1364 @param param Pointer to the method parameters.
1365 @return 1 if the class reference is valid, or zero if it isn't.
1367 The class of the method params must match the class to which the method id devoted.
1368 For PCRUD there are additional restrictions.
1370 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1372 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1373 osrfHash* class = osrfHashGet( method_meta, "class" );
1375 // Compare the method's class to the parameters' class
1376 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1378 // Oops -- they don't match. Complain.
1379 growing_buffer* msg = buffer_init( 128 );
1382 "%s: %s method for type %s was passed a %s",
1384 osrfHashGet( method_meta, "methodtype" ),
1385 osrfHashGet( class, "classname" ),
1386 param->classname ? param->classname : "(null)"
1389 char* m = buffer_release( msg );
1390 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1398 return verifyObjectPCRUD( ctx, class, param, 1 );
1404 @brief (PCRUD only) Verify that the user is properly logged in.
1405 @param ctx Pointer to the method context.
1406 @return If the user is logged in, a pointer to the user object from the authentication
1407 server; otherwise NULL.
1409 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1410 return verifyUserPCRUDfull( ctx, 0 );
1413 static const jsonObject* verifyUserPCRUDfull( osrfMethodContext* ctx, int anon_ok ) {
1415 // Get the authkey (the first method parameter)
1416 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1418 jsonObject* user = NULL;
1420 // If we are /not/ in anonymous mode
1421 if( strcmp( "ANONYMOUS", auth ) ) {
1422 // See if we have the same authkey, and a user object,
1423 // locally cached from a previous call
1424 const char* cached_authkey = getAuthkey( ctx );
1425 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1426 const jsonObject* cached_user = getUserLogin( ctx );
1431 // We have no matching authentication data in the cache. Authenticate from scratch.
1432 jsonObject* auth_object = jsonNewObject( auth );
1434 // Fetch the user object from the authentication server
1435 user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve", auth_object );
1436 jsonObjectFree( auth_object );
1438 if( !user->classname || strcmp(user->classname, "au" )) {
1440 growing_buffer* msg = buffer_init( 128 );
1443 "%s: permacrud received a bad auth token: %s",
1448 char* m = buffer_release( msg );
1449 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1453 jsonObjectFree( user );
1455 } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
1456 // Failed to set audit information - But note that write_audit_info already set error information.
1457 jsonObjectFree( user );
1462 } else if ( anon_ok ) { // we /are/ (attempting to be) anonymous
1463 user = jsonNewObjectType(JSON_ARRAY);
1464 jsonObjectSetClass( user, "aou" );
1465 oilsFMSetString(user, "id", "-1");
1468 setUserLogin( ctx, user );
1469 setAuthkey( ctx, auth );
1471 // Allow ourselves up to a second before we have to reset the login timeout.
1472 // It would be nice to use some fraction of the timeout interval enforced by the
1473 // authentication server, but that value is not readily available at this point.
1474 // Instead, we use a conservative default interval.
1475 time_next_reset = time( NULL ) + 1;
1481 @brief For PCRUD: Determine whether the current user may access the current row.
1482 @param ctx Pointer to the method context.
1483 @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1484 @param obj Pointer to the row being potentially accessed.
1485 @return 1 if access is permitted, or 0 if it isn't.
1487 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1489 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1491 dbhandle = writehandle;
1493 // Figure out what class and method are involved
1494 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1495 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1498 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1499 if (rs_size_from_hash) {
1500 rs_size = *rs_size_from_hash;
1501 osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1505 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1506 // contexts we will do another lookup of the current row, even if we already have a
1507 // previously fetched row image, because the row image in hand may not include the
1508 // foreign key(s) that we need.
1510 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1511 // but they aren't implemented yet.
1514 if( *method_type == 's' || *method_type == 'i' ) {
1515 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1517 } else if( *method_type == 'u' || *method_type == 'd' ) {
1518 fetch = 1; // MUST go to the db for the object for update and delete
1521 // In retrieve or search ONLY we allow anon. Later perm checks will fail as they should,
1522 // in the face of a fake user but required permissions.
1524 if( *method_type == 'r' )
1527 // Get the appropriate permacrud entry from the IDL, depending on method type
1528 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1530 // No permacrud for this method type on this class
1532 growing_buffer* msg = buffer_init( 128 );
1535 "%s: %s on class %s has no permacrud IDL entry",
1537 osrfHashGet( method_metadata, "methodtype" ),
1538 osrfHashGet( class, "classname" )
1541 char* m = buffer_release( msg );
1542 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1543 "osrfMethodException", ctx->request, m );
1550 // Get the user id, and make sure the user is logged in
1551 const jsonObject* user = verifyUserPCRUDfull( ctx, anon_ok );
1553 return 0; // Not logged in or anon? No access.
1555 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1557 // Get a list of permissions from the permacrud entry.
1558 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1559 if( permission->size == 0 ) {
1562 "No permissions required for this action (class %s), passing through",
1563 osrfHashGet(class, "classname")
1568 // But, if there are perms and the user is anonymous ... FAIL
1572 // Build a list of org units that own the row. This is fairly convoluted because there
1573 // are several different ways that an org unit may own the row, as defined by the
1576 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1577 // identifying an owning org_unit..
1578 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1580 // Foreign context adds a layer of indirection. The row points to some other row that
1581 // an org unit may own. The "jump" attribute, if present, adds another layer of
1583 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1585 // The following string array stores the list of org units. (We don't have a thingie
1586 // for storing lists of integers, so we fake it with a list of strings.)
1587 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1589 const char* context_org = NULL;
1590 const char* pkey = NULL;
1591 jsonObject *param = NULL;
1592 const char* perm = NULL;
1596 const char* pkey_value = NULL;
1597 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1598 // If the global_required attribute is present and true, then the only owning
1599 // org unit is the root org unit, i.e. the one with no parent.
1600 osrfLogDebug( OSRF_LOG_MARK,
1601 "global-level permissions required, fetching top of the org tree" );
1603 // no need to check perms for org tree root retrieval
1604 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1605 // check for perm at top of org tree
1606 const char* org_tree_root_id = org_tree_root( ctx );
1607 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1609 if( org_tree_root_id ) {
1610 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1611 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1613 osrfStringArrayFree( context_org_array );
1618 // If the global_required attribute is absent or false, then we look for
1619 // local and/or foreign context. In order to find the relevant foreign
1620 // keys, we must either read the relevant row from the database, or look at
1621 // the image of the row that we already have in memory.
1623 // Even if we have an image of the row in memory, that image may not include the
1624 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1625 // of the row to make sure that we have what we need.
1627 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1628 "fetching context org ids" );
1630 pkey = osrfHashGet( class, "primarykey" );
1633 // There is no primary key, so we can't do a fresh lookup. Use the row
1634 // image that we already have. If it doesn't have everything we need, too bad.
1636 param = jsonObjectClone( obj );
1637 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1638 } else if( obj->classname ) {
1639 pkey_value = oilsFMGetStringConst( obj, pkey );
1641 param = jsonObjectClone( obj );
1642 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1645 pkey_value = jsonObjectGetString( obj );
1647 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1648 "of %s and retrieving from the database", pkey_value );
1652 // Fetch the row so that we can look at the foreign key(s)
1653 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1654 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1655 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1656 jsonObjectFree( _tmp_params );
1657 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1659 param = jsonObjectExtractIndex( _list, 0 );
1660 jsonObjectFree( _list );
1666 // The row doesn't exist. Complain, and deny access.
1667 osrfLogDebug( OSRF_LOG_MARK,
1668 "Object not found in the database with primary key %s of %s",
1671 growing_buffer* msg = buffer_init( 128 );
1674 "%s: no object found with primary key %s of %s",
1680 char* m = buffer_release( msg );
1681 osrfAppSessionStatus(
1683 OSRF_STATUS_INTERNALSERVERERROR,
1684 "osrfMethodException",
1693 if( local_context && local_context->size > 0 ) {
1694 // The IDL provides a list of column names for the foreign keys denoting
1695 // local context, i.e. columns identifying owing org units directly. Look up
1696 // the value of each one, and if it isn't null, add it to the list of org units.
1697 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1698 local_context->size );
1700 const char* lcontext = NULL;
1701 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1702 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1703 if( fkey_value ) { // if not null
1704 osrfStringArrayAdd( context_org_array, fkey_value );
1707 "adding class-local field %s (value: %s) to the context org list",
1709 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1715 if( foreign_context ) {
1716 unsigned long class_count = osrfHashGetCount( foreign_context );
1717 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1719 if( class_count > 0 ) {
1721 // The IDL provides a list of foreign key columns pointing to rows that
1722 // an org unit may own. Follow each link, identify the owning org unit,
1723 // and add it to the list.
1724 osrfHash* fcontext = NULL;
1725 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1726 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1727 // For each class to which a foreign key points:
1728 const char* class_name = osrfHashIteratorKey( class_itr );
1729 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1733 "%d foreign context fields(s) specified for class %s",
1734 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1738 // Get the name of the key field in the foreign table
1739 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1741 // Get the value of the foreign key pointing to the foreign table
1742 char* foreign_pkey_value =
1743 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1744 if( !foreign_pkey_value )
1745 continue; // Foreign key value is null; skip it
1747 // Look up the row to which the foreign key points
1748 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1750 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1751 jsonObject* _list = doFieldmapperSearch(
1752 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1753 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1755 jsonObject* _fparam = NULL;
1756 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1757 _fparam = jsonObjectExtractIndex( _list, 0 );
1759 jsonObjectFree( _tmp_params );
1760 jsonObjectFree( _list );
1762 // At this point _fparam either points to the row identified by the
1763 // foreign key, or it's NULL (no such row found).
1765 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1767 const char* bad_class = NULL; // For noting failed lookups
1769 bad_class = class_name; // Referenced row not found
1770 else if( jump_list ) {
1771 // Follow a chain of rows, linked by foreign keys, to find an owner
1772 const char* flink = NULL;
1774 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1775 // For each entry in the jump list. Each entry (i.e. flink) is
1776 // the name of a foreign key column in the current row.
1778 // From the IDL, get the linkage information for the next jump
1779 osrfHash* foreign_link_hash =
1780 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1782 // Get the class metadata for the class
1783 // to which the foreign key points
1784 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1785 osrfHashGet( foreign_link_hash, "class" ));
1787 // Get the name of the referenced key of that class
1788 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1790 // Get the value of the foreign key pointing to that class
1791 free( foreign_pkey_value );
1792 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1793 if( !foreign_pkey_value )
1794 break; // Foreign key is null; quit looking
1796 // Build a WHERE clause for the lookup
1797 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1800 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1801 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1802 _tmp_params, NULL, &err );
1803 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1805 // Get the resulting row
1806 jsonObjectFree( _fparam );
1807 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1808 _fparam = jsonObjectExtractIndex( _list, 0 );
1810 // Referenced row not found
1812 bad_class = osrfHashGet( foreign_link_hash, "class" );
1815 jsonObjectFree( _tmp_params );
1816 jsonObjectFree( _list );
1822 // We had a foreign key pointing to such-and-such a row, but then
1823 // we couldn't fetch that row. The data in the database are in an
1824 // inconsistent state; the database itself may even be corrupted.
1825 growing_buffer* msg = buffer_init( 128 );
1828 "%s: no object of class %s found with primary key %s of %s",
1832 foreign_pkey_value ? foreign_pkey_value : "(null)"
1835 char* m = buffer_release( msg );
1836 osrfAppSessionStatus(
1838 OSRF_STATUS_INTERNALSERVERERROR,
1839 "osrfMethodException",
1845 osrfHashIteratorFree( class_itr );
1846 free( foreign_pkey_value );
1847 jsonObjectFree( param );
1852 free( foreign_pkey_value );
1855 // Examine each context column of the foreign row,
1856 // and add its value to the list of org units.
1858 const char* foreign_field = NULL;
1859 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1860 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1861 osrfStringArrayAdd( context_org_array,
1862 oilsFMGetStringConst( _fparam, foreign_field ));
1863 osrfLogDebug( OSRF_LOG_MARK,
1864 "adding foreign class %s field %s (value: %s) "
1865 "to the context org list",
1868 osrfStringArrayGetString(
1869 context_org_array, context_org_array->size - 1 )
1873 jsonObjectFree( _fparam );
1877 osrfHashIteratorFree( class_itr );
1882 // If there is an owning_user attached to the action, we allow that user and users with
1883 // object perms on the object. CREATE can't use this. We only do this when there is no
1884 // context org for this action, and when we're not ignoring object perms.
1886 *method_type != 'c' &&
1887 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
1888 context_org_array->size == 0
1890 char* owning_user_field = osrfHashGet( pcrud, "owning_user" );
1891 if (owning_user_field) {
1893 if (!param) { // We didn't get it during the context lookup
1894 pkey = osrfHashGet( class, "primarykey" );
1897 // There is no primary key, so we can't do a fresh lookup. Use the row
1898 // image that we already have. If it doesn't have everything we need, too bad.
1900 param = jsonObjectClone( obj );
1901 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1902 } else if( obj->classname ) {
1903 pkey_value = oilsFMGetStringConst( obj, pkey );
1905 param = jsonObjectClone( obj );
1906 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1909 pkey_value = jsonObjectGetString( obj );
1911 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1912 "of %s and retrieving from the database", pkey_value );
1916 // Fetch the row so that we can look at the foreign key(s)
1917 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1918 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1919 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1920 jsonObjectFree( _tmp_params );
1921 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1923 param = jsonObjectExtractIndex( _list, 0 );
1924 jsonObjectFree( _list );
1929 // The row doesn't exist. Complain, and deny access.
1930 osrfLogDebug( OSRF_LOG_MARK,
1931 "Object not found in the database with primary key %s of %s",
1934 growing_buffer* msg = buffer_init( 128 );
1937 "%s: no object found with primary key %s of %s",
1943 char* m = buffer_release( msg );
1944 osrfAppSessionStatus(
1946 OSRF_STATUS_INTERNALSERVERERROR,
1947 "osrfMethodException",
1956 int ownerid = atoi( oilsFMGetStringConst( param, owning_user_field ) );
1958 // Allow the owner to do whatever
1959 if (ownerid == userid)
1963 while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
1968 "Checking object permission [%s] for user %d "
1969 "on object %s (class %s)",
1973 osrfHashGet( class, "classname" )
1976 result = dbi_conn_queryf(
1978 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s') AS has_perm;",
1981 osrfHashGet( class, "classname" ),
1988 "Received a result for object permission [%s] "
1989 "for user %d on object %s (class %s)",
1993 osrfHashGet( class, "classname" )
1996 if( dbi_result_first_row( result )) {
1997 jsonObject* return_val = oilsMakeJSONFromResult( result );
1998 const char* has_perm = jsonObjectGetString(
1999 jsonObjectGetKeyConst( return_val, "has_perm" ));
2003 "Status of object permission [%s] for user %d "
2004 "on object %s (class %s) is %s",
2008 osrfHashGet(class, "classname"),
2012 if( *has_perm == 't' )
2014 jsonObjectFree( return_val );
2017 dbi_result_free( result );
2022 int errnum = dbi_conn_error( writehandle, &msg );
2023 osrfLogWarning( OSRF_LOG_MARK,
2024 "Unable to call check object permissions: %d, %s",
2025 errnum, msg ? msg : "(No description available)" );
2026 if( !oilsIsDBConnected( writehandle ))
2027 osrfAppSessionPanic( ctx->session );
2034 // For every combination of permission and context org unit: call a stored procedure
2035 // to determine if the user has this permission in the context of this org unit.
2036 // If the answer is yes at any point, then we're done, and the user has permission.
2037 // In other words permissions are additive.
2039 while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
2042 osrfStringArray* pcache = NULL;
2043 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
2044 pcache = getPermLocationCache(ctx, perm);
2047 pcache = osrfNewStringArray(0);
2049 result = dbi_conn_queryf(
2051 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
2059 "Received a result for permission [%s] for user %d",
2064 if( dbi_result_first_row( result )) {
2066 jsonObject* return_val = oilsMakeJSONFromResult( result );
2067 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
2068 jsonObjectFree( return_val );
2069 } while( dbi_result_next_row( result ));
2071 setPermLocationCache(ctx, perm, pcache);
2074 dbi_result_free( result );
2080 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
2082 if (rs_size > perm_at_threshold) {
2083 if (osrfStringArrayContains( pcache, context_org )) {
2091 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
2093 !str_is_true( osrfHashGet(pcrud, "global_required") ) ||
2094 osrfHashGet(pcrud, "owning_user")
2099 "Checking object permission [%s] for user %d "
2100 "on object %s (class %s) at org %d",
2104 osrfHashGet( class, "classname" ),
2108 result = dbi_conn_queryf(
2110 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
2113 osrfHashGet( class, "classname" ),
2121 "Received a result for object permission [%s] "
2122 "for user %d on object %s (class %s) at org %d",
2126 osrfHashGet( class, "classname" ),
2130 if( dbi_result_first_row( result )) {
2131 jsonObject* return_val = oilsMakeJSONFromResult( result );
2132 const char* has_perm = jsonObjectGetString(
2133 jsonObjectGetKeyConst( return_val, "has_perm" ));
2137 "Status of object permission [%s] for user %d "
2138 "on object %s (class %s) at org %d is %s",
2142 osrfHashGet(class, "classname"),
2147 if( *has_perm == 't' )
2149 jsonObjectFree( return_val );
2152 dbi_result_free( result );
2157 int errnum = dbi_conn_error( writehandle, &msg );
2158 osrfLogWarning( OSRF_LOG_MARK,
2159 "Unable to call check object permissions: %d, %s",
2160 errnum, msg ? msg : "(No description available)" );
2161 if( !oilsIsDBConnected( writehandle ))
2162 osrfAppSessionPanic( ctx->session );
2166 if (rs_size > perm_at_threshold) break;
2168 osrfLogDebug( OSRF_LOG_MARK,
2169 "Checking non-object permission [%s] for user %d at org %d",
2170 perm, userid, atoi(context_org) );
2171 result = dbi_conn_queryf(
2173 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
2180 osrfLogDebug( OSRF_LOG_MARK,
2181 "Received a result for permission [%s] for user %d at org %d",
2182 perm, userid, atoi( context_org ));
2183 if( dbi_result_first_row( result )) {
2184 jsonObject* return_val = oilsMakeJSONFromResult( result );
2185 const char* has_perm = jsonObjectGetString(
2186 jsonObjectGetKeyConst( return_val, "has_perm" ));
2187 osrfLogDebug( OSRF_LOG_MARK,
2188 "Status of permission [%s] for user %d at org %d is [%s]",
2189 perm, userid, atoi( context_org ), has_perm );
2190 if( *has_perm == 't' )
2192 jsonObjectFree( return_val );
2195 dbi_result_free( result );
2200 int errnum = dbi_conn_error( writehandle, &msg );
2201 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
2202 errnum, msg ? msg : "(No description available)" );
2203 if( !oilsIsDBConnected( writehandle ))
2204 osrfAppSessionPanic( ctx->session );
2213 osrfStringArrayFree( context_org_array );
2219 @brief Look up the root of the org_unit tree.
2220 @param ctx Pointer to the method context.
2221 @return The id of the root org unit, as a character string.
2223 Query actor.org_unit where parent_ou is null, and return the id as a string.
2225 This function assumes that there is only one root org unit, i.e. that we
2226 have a single tree, not a forest.
2228 The calling code is responsible for freeing the returned string.
2230 static const char* org_tree_root( osrfMethodContext* ctx ) {
2232 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
2233 static time_t last_lookup_time = 0;
2234 time_t current_time = time( NULL );
2236 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2237 // We successfully looked this up less than an hour ago.
2238 // It's not likely to have changed since then.
2239 return strdup( cached_root_id );
2241 last_lookup_time = current_time;
2244 jsonObject* where_clause = single_hash( "parent_ou", NULL );
2245 jsonObject* result = doFieldmapperSearch(
2246 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2247 jsonObjectFree( where_clause );
2249 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2252 jsonObjectFree( result );
2254 growing_buffer* msg = buffer_init( 128 );
2255 OSRF_BUFFER_ADD( msg, modulename );
2256 OSRF_BUFFER_ADD( msg,
2257 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2259 char* m = buffer_release( msg );
2260 osrfAppSessionStatus( ctx->session,
2261 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2264 cached_root_id[ 0 ] = '\0';
2268 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2269 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2271 strcpy( cached_root_id, root_org_unit_id );
2272 jsonObjectFree( result );
2273 return cached_root_id;
2277 @brief Create a JSON_HASH with a single key/value pair.
2278 @param key The key of the key/value pair.
2279 @param value the value of the key/value pair.
2280 @return Pointer to a newly created jsonObject of type JSON_HASH.
2282 The value of the key/value is either a string or (if @a value is NULL) a null.
2284 static jsonObject* single_hash( const char* key, const char* value ) {
2286 if( ! key ) key = "";
2288 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2289 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2294 int doCreate( osrfMethodContext* ctx ) {
2295 if(osrfMethodVerifyContext( ctx )) {
2296 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2301 timeout_needs_resetting = 1;
2303 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2304 jsonObject* target = NULL;
2305 jsonObject* options = NULL;
2307 if( enforce_pcrud ) {
2308 target = jsonObjectGetIndex( ctx->params, 1 );
2309 options = jsonObjectGetIndex( ctx->params, 2 );
2311 target = jsonObjectGetIndex( ctx->params, 0 );
2312 options = jsonObjectGetIndex( ctx->params, 1 );
2315 if( !verifyObjectClass( ctx, target )) {
2316 osrfAppRespondComplete( ctx, NULL );
2320 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2322 const char* trans_id = getXactId( ctx );
2324 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2326 osrfAppSessionStatus(
2328 OSRF_STATUS_BADREQUEST,
2329 "osrfMethodException",
2331 "No active transaction -- required for CREATE"
2333 osrfAppRespondComplete( ctx, NULL );
2337 // The following test is harmless but redundant. If a class is
2338 // readonly, we don't register a create method for it.
2339 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2340 osrfAppSessionStatus(
2342 OSRF_STATUS_BADREQUEST,
2343 "osrfMethodException",
2345 "Cannot INSERT readonly class"
2347 osrfAppRespondComplete( ctx, NULL );
2351 // Set the last_xact_id
2352 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2354 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2355 trans_id, target->classname, index);
2356 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2359 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2361 dbhandle = writehandle;
2363 osrfHash* fields = osrfHashGet( meta, "fields" );
2364 char* pkey = osrfHashGet( meta, "primarykey" );
2365 char* seq = osrfHashGet( meta, "sequence" );
2367 growing_buffer* table_buf = buffer_init( 128 );
2368 growing_buffer* col_buf = buffer_init( 128 );
2369 growing_buffer* val_buf = buffer_init( 128 );
2371 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2372 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2373 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2374 buffer_add( val_buf,"VALUES (" );
2378 osrfHash* field = NULL;
2379 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2380 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2382 const char* field_name = osrfHashIteratorKey( field_itr );
2384 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2387 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2390 if( field_object && field_object->classname ) {
2391 value = oilsFMGetString(
2393 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2395 } else if( field_object && JSON_BOOL == field_object->type ) {
2396 if( jsonBoolIsTrue( field_object ) )
2397 value = strdup( "t" );
2399 value = strdup( "f" );
2401 value = jsonObjectToSimpleString( field_object );
2407 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2408 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2411 buffer_add( col_buf, field_name );
2413 if( !field_object || field_object->type == JSON_NULL ) {
2414 buffer_add( val_buf, "DEFAULT" );
2416 } else if( !strcmp( get_primitive( field ), "number" )) {
2417 const char* numtype = get_datatype( field );
2418 if( !strcmp( numtype, "INT8" )) {
2419 buffer_fadd( val_buf, "%lld", atoll( value ));
2421 } else if( !strcmp( numtype, "INT" )) {
2422 buffer_fadd( val_buf, "%d", atoi( value ));
2424 } else if( !strcmp( numtype, "NUMERIC" )) {
2425 buffer_fadd( val_buf, "%f", atof( value ));
2428 if( dbi_conn_quote_string( writehandle, &value )) {
2429 OSRF_BUFFER_ADD( val_buf, value );
2432 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2433 osrfAppSessionStatus(
2435 OSRF_STATUS_INTERNALSERVERERROR,
2436 "osrfMethodException",
2438 "Error quoting string -- please see the error log for more details"
2441 buffer_free( table_buf );
2442 buffer_free( col_buf );
2443 buffer_free( val_buf );
2444 osrfAppRespondComplete( ctx, NULL );
2452 osrfHashIteratorFree( field_itr );
2454 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2455 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2457 char* table_str = buffer_release( table_buf );
2458 char* col_str = buffer_release( col_buf );
2459 char* val_str = buffer_release( val_buf );
2460 growing_buffer* sql = buffer_init( 128 );
2461 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2466 char* query = buffer_release( sql );
2468 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2470 jsonObject* obj = NULL;
2473 dbi_result result = dbi_conn_query( writehandle, query );
2475 obj = jsonNewObject( NULL );
2477 int errnum = dbi_conn_error( writehandle, &msg );
2480 "%s ERROR inserting %s object using query [%s]: %d %s",
2482 osrfHashGet(meta, "fieldmapper"),
2485 msg ? msg : "(No description available)"
2487 osrfAppSessionStatus(
2489 OSRF_STATUS_INTERNALSERVERERROR,
2490 "osrfMethodException",
2492 "INSERT error -- please see the error log for more details"
2494 if( !oilsIsDBConnected( writehandle ))
2495 osrfAppSessionPanic( ctx->session );
2498 dbi_result_free( result );
2500 char* id = oilsFMGetString( target, pkey );
2502 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2503 growing_buffer* _id = buffer_init( 10 );
2504 buffer_fadd( _id, "%lld", new_id );
2505 id = buffer_release( _id );
2508 // Find quietness specification, if present
2509 const char* quiet_str = NULL;
2511 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2513 quiet_str = jsonObjectGetString( quiet_obj );
2516 if( str_is_true( quiet_str )) { // if quietness is specified
2517 obj = jsonNewObject( id );
2521 // Fetch the row that we just inserted, so that we can return it to the client
2522 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2523 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2526 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2530 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2532 jsonObjectFree( list );
2533 jsonObjectFree( where_clause );
2540 osrfAppRespondComplete( ctx, obj );
2541 jsonObjectFree( obj );
2546 @brief Implement the retrieve method.
2547 @param ctx Pointer to the method context.
2548 @param err Pointer through which to return an error code.
2549 @return If successful, a pointer to the result to be returned to the client;
2552 From the method's class, fetch a row with a specified value in the primary key. This
2553 method relies on the database design convention that a primary key consists of a single
2557 - authkey (PCRUD only)
2558 - value of the primary key for the desired row, for building the WHERE clause
2559 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2561 Return to client: One row from the query.
2563 int doRetrieve( osrfMethodContext* ctx ) {
2564 if(osrfMethodVerifyContext( ctx )) {
2565 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2570 timeout_needs_resetting = 1;
2575 if( enforce_pcrud ) {
2580 // Get the class metadata
2581 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2583 // Get the value of the primary key, from a method parameter
2584 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2588 "%s retrieving %s object with primary key value of %s",
2590 osrfHashGet( class_def, "fieldmapper" ),
2591 jsonObjectGetString( id_obj )
2594 // Build a WHERE clause based on the key value
2595 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2598 osrfHashGet( class_def, "primarykey" ), // name of key column
2599 jsonObjectClone( id_obj ) // value of key column
2602 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2606 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2608 jsonObjectFree( where_clause );
2610 osrfAppRespondComplete( ctx, NULL );
2614 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2615 jsonObjectFree( list );
2617 if( enforce_pcrud ) {
2618 // no result, skip this entirely
2619 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2620 jsonObjectFree( obj );
2622 growing_buffer* msg = buffer_init( 128 );
2623 OSRF_BUFFER_ADD( msg, modulename );
2624 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2626 char* m = buffer_release( msg );
2627 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2631 osrfAppRespondComplete( ctx, NULL );
2636 // doFieldmapperSearch() now does the responding for us
2637 //osrfAppRespondComplete( ctx, obj );
2638 osrfAppRespondComplete( ctx, NULL );
2640 jsonObjectFree( obj );
2645 @brief Translate a numeric value to a string representation for the database.
2646 @param field Pointer to the IDL field definition.
2647 @param value Pointer to a jsonObject holding the value of a field.
2648 @return Pointer to a newly allocated string.
2650 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2651 its contents are numeric. A non-numeric string is likely to result in invalid SQL.
2653 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2655 The calling code is responsible for freeing the resulting string by calling free().
2657 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2658 growing_buffer* val_buf = buffer_init( 32 );
2660 // If the value is a number and the DB field is numeric, no quotes needed
2661 if( value->type == JSON_NUMBER && !strcmp( get_primitive( field ), "number") ) {
2662 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2664 // Presumably this was really intended to be a string, so quote it
2665 char* str = jsonObjectToSimpleString( value );
2666 if( dbi_conn_quote_string( dbhandle, &str )) {
2667 OSRF_BUFFER_ADD( val_buf, str );
2670 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2672 buffer_free( val_buf );
2677 return buffer_release( val_buf );
2680 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2681 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2682 growing_buffer* sql_buf = buffer_init( 32 );
2688 osrfHashGet( field, "name" )
2692 buffer_add( sql_buf, "IN (" );
2693 } else if( !strcasecmp( op,"not in" )) {
2694 buffer_add( sql_buf, "NOT IN (" );
2696 buffer_add( sql_buf, "IN (" );
2699 if( node->type == JSON_HASH ) {
2700 // subquery predicate
2701 char* subpred = buildQuery( ctx, node, SUBSELECT );
2703 buffer_free( sql_buf );
2707 buffer_add( sql_buf, subpred );
2710 } else if( node->type == JSON_ARRAY ) {
2711 // literal value list
2712 int in_item_index = 0;
2713 int in_item_first = 1;
2714 const jsonObject* in_item;
2715 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2720 buffer_add( sql_buf, ", " );
2723 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2724 osrfLogError( OSRF_LOG_MARK,
2725 "%s: Expected string or number within IN list; found %s",
2726 modulename, json_type( in_item->type ) );
2727 buffer_free( sql_buf );
2731 // Append the literal value -- quoted if not a number
2732 if( JSON_NUMBER == in_item->type ) {
2733 char* val = jsonNumberToDBString( field, in_item );
2734 OSRF_BUFFER_ADD( sql_buf, val );
2737 } else if( !strcmp( get_primitive( field ), "number" )) {
2738 char* val = jsonNumberToDBString( field, in_item );
2739 OSRF_BUFFER_ADD( sql_buf, val );
2743 char* key_string = jsonObjectToSimpleString( in_item );
2744 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2745 OSRF_BUFFER_ADD( sql_buf, key_string );
2748 osrfLogError( OSRF_LOG_MARK,
2749 "%s: Error quoting key string [%s]", modulename, key_string );
2751 buffer_free( sql_buf );
2757 if( in_item_first ) {
2758 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2759 buffer_free( sql_buf );
2763 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2764 modulename, json_type( node->type ));
2765 buffer_free( sql_buf );
2769 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2771 return buffer_release( sql_buf );
2774 // Receive a JSON_ARRAY representing a function call. The first
2775 // entry in the array is the function name. The rest are parameters.
2776 static char* searchValueTransform( const jsonObject* array ) {
2778 if( array->size < 1 ) {
2779 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2783 // Get the function name
2784 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2785 if( func_item->type != JSON_STRING ) {
2786 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2787 modulename, json_type( func_item->type ));
2791 growing_buffer* sql_buf = buffer_init( 32 );
2793 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2794 OSRF_BUFFER_ADD( sql_buf, "( " );
2796 // Get the parameters
2797 int func_item_index = 1; // We already grabbed the zeroth entry
2798 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2800 // Add a separator comma, if we need one
2801 if( func_item_index > 2 )
2802 buffer_add( sql_buf, ", " );
2804 // Add the current parameter
2805 if( func_item->type == JSON_NULL ) {
2806 buffer_add( sql_buf, "NULL" );
2808 if( func_item->type == JSON_BOOL ) {
2809 if( jsonBoolIsTrue(func_item) ) {
2810 buffer_add( sql_buf, "TRUE" );
2812 buffer_add( sql_buf, "FALSE" );
2815 char* val = jsonObjectToSimpleString( func_item );
2816 if( dbi_conn_quote_string( dbhandle, &val )) {
2817 OSRF_BUFFER_ADD( sql_buf, val );
2820 osrfLogError( OSRF_LOG_MARK,
2821 "%s: Error quoting key string [%s]", modulename, val );
2822 buffer_free( sql_buf );
2830 buffer_add( sql_buf, " )" );
2832 return buffer_release( sql_buf );
2835 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2836 const jsonObject* node, const char* op ) {
2838 if( ! is_good_operator( op ) ) {
2839 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2843 char* val = searchValueTransform( node );
2847 const char* right_percent = "";
2848 const char* real_op = op;
2850 if( !strcasecmp( op, "startwith") ) {
2852 right_percent = "|| '%'";
2855 growing_buffer* sql_buf = buffer_init( 32 );
2858 "\"%s\".%s %s %s%s",
2860 osrfHashGet( field, "name" ),
2868 return buffer_release( sql_buf );
2871 // class_alias is a class name or other table alias
2872 // field is a field definition as stored in the IDL
2873 // node comes from the method parameter, and may represent an entry in the SELECT list
2874 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2875 const jsonObject* node ) {
2876 growing_buffer* sql_buf = buffer_init( 32 );
2878 const char* field_transform = jsonObjectGetString(
2879 jsonObjectGetKeyConst( node, "transform" ) );
2880 const char* transform_subcolumn = jsonObjectGetString(
2881 jsonObjectGetKeyConst( node, "result_field" ) );
2883 if( transform_subcolumn ) {
2884 if( ! is_identifier( transform_subcolumn ) ) {
2885 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2886 modulename, transform_subcolumn );
2887 buffer_free( sql_buf );
2890 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2893 if( field_transform ) {
2895 if( ! is_identifier( field_transform ) ) {
2896 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2897 modulename, field_transform );
2898 buffer_free( sql_buf );
2902 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2903 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2904 field_transform, class_alias, osrfHashGet( field, "name" ));
2906 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2907 field_transform, class_alias, osrfHashGet( field, "name" ));
2910 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2913 if( array->type != JSON_ARRAY ) {
2914 osrfLogError( OSRF_LOG_MARK,
2915 "%s: Expected JSON_ARRAY for function params; found %s",
2916 modulename, json_type( array->type ) );
2917 buffer_free( sql_buf );
2920 int func_item_index = 0;
2921 jsonObject* func_item;
2922 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2924 char* val = jsonObjectToSimpleString( func_item );
2927 buffer_add( sql_buf, ",NULL" );
2928 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2929 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2930 OSRF_BUFFER_ADD( sql_buf, val );
2932 osrfLogError( OSRF_LOG_MARK,
2933 "%s: Error quoting key string [%s]", modulename, val );
2935 buffer_free( sql_buf );
2942 buffer_add( sql_buf, " )" );
2945 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2948 if( transform_subcolumn )
2949 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2951 return buffer_release( sql_buf );
2954 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2955 const jsonObject* node, const char* op ) {
2957 if( ! is_good_operator( op ) ) {
2958 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2962 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2963 if( ! field_transform )
2966 int extra_parens = 0; // boolean
2968 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2970 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2972 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2974 free( field_transform );
2978 } else if( value_obj->type == JSON_ARRAY ) {
2979 value = searchValueTransform( value_obj );
2981 osrfLogError( OSRF_LOG_MARK,
2982 "%s: Error building value transform for field transform", modulename );
2983 free( field_transform );
2986 } else if( value_obj->type == JSON_HASH ) {
2987 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2989 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2991 free( field_transform );
2995 } else if( value_obj->type == JSON_NUMBER ) {
2996 value = jsonNumberToDBString( field, value_obj );
2997 } else if( value_obj->type == JSON_NULL ) {
2998 osrfLogError( OSRF_LOG_MARK,
2999 "%s: Error building predicate for field transform: null value", modulename );
3000 free( field_transform );
3002 } else if( value_obj->type == JSON_BOOL ) {
3003 osrfLogError( OSRF_LOG_MARK,
3004 "%s: Error building predicate for field transform: boolean value", modulename );
3005 free( field_transform );
3008 if( !strcmp( get_primitive( field ), "number") ) {
3009 value = jsonNumberToDBString( field, value_obj );
3011 value = jsonObjectToSimpleString( value_obj );
3012 if( !dbi_conn_quote_string( dbhandle, &value )) {
3013 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3014 modulename, value );
3016 free( field_transform );
3022 const char* left_parens = "";
3023 const char* right_parens = "";
3025 if( extra_parens ) {
3030 const char* right_percent = "";
3031 const char* real_op = op;
3033 if( !strcasecmp( op, "startwith") ) {
3035 right_percent = "|| '%'";
3038 growing_buffer* sql_buf = buffer_init( 32 );
3042 "%s%s %s %s %s%s %s%s",
3054 free( field_transform );
3056 return buffer_release( sql_buf );
3059 static char* searchSimplePredicate( const char* op, const char* class_alias,
3060 osrfHash* field, const jsonObject* node ) {
3062 if( ! is_good_operator( op ) ) {
3063 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
3069 // Get the value to which we are comparing the specified column
3070 if( node->type != JSON_NULL ) {
3071 if( node->type == JSON_NUMBER ) {
3072 val = jsonNumberToDBString( field, node );
3073 } else if( !strcmp( get_primitive( field ), "number" ) ) {
3074 val = jsonNumberToDBString( field, node );
3076 val = jsonObjectToSimpleString( node );
3081 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
3082 // Value is not numeric; enclose it in quotes
3083 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
3084 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3091 // Compare to a null value
3092 val = strdup( "NULL" );
3093 if( strcmp( op, "=" ))
3099 const char* right_percent = "";
3100 const char* real_op = op;
3102 if( !strcasecmp( op, "startwith") ) {
3104 right_percent = "|| '%'";
3107 growing_buffer* sql_buf = buffer_init( 32 );
3108 buffer_fadd( sql_buf, "\"%s\".%s %s %s%s", class_alias, osrfHashGet(field, "name"), real_op, val, right_percent );
3109 char* pred = buffer_release( sql_buf );
3116 static char* searchBETWEENPredicate( const char* class_alias,
3117 osrfHash* field, const jsonObject* node ) {
3119 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
3120 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
3122 if( NULL == y_node ) {
3123 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
3126 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
3127 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
3134 if( !strcmp( get_primitive( field ), "number") ) {
3135 x_string = jsonNumberToDBString( field, x_node );
3136 y_string = jsonNumberToDBString( field, y_node );
3139 x_string = jsonObjectToSimpleString( x_node );
3140 y_string = jsonObjectToSimpleString( y_node );
3141 if( !(dbi_conn_quote_string( dbhandle, &x_string )
3142 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
3143 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
3144 modulename, x_string, y_string );
3151 growing_buffer* sql_buf = buffer_init( 32 );
3152 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
3153 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
3157 return buffer_release( sql_buf );
3160 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
3161 jsonObject* node, osrfMethodContext* ctx ) {
3164 if( node->type == JSON_ARRAY ) { // equality IN search
3165 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
3166 } else if( node->type == JSON_HASH ) { // other search
3167 jsonIterator* pred_itr = jsonNewIterator( node );
3168 if( !jsonIteratorHasNext( pred_itr ) ) {
3169 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
3170 modulename, osrfHashGet(field, "name" ));
3172 jsonObject* pred_node = jsonIteratorNext( pred_itr );
3174 // Verify that there are no additional predicates
3175 if( jsonIteratorHasNext( pred_itr ) ) {
3176 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
3177 modulename, osrfHashGet(field, "name" ));
3178 } else if( !(strcasecmp( pred_itr->key,"between" )) )
3179 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
3180 else if( !(strcasecmp( pred_itr->key,"in" ))
3181 || !(strcasecmp( pred_itr->key,"not in" )) )
3182 pred = searchINPredicate(
3183 class_info->alias, field, pred_node, pred_itr->key, ctx );
3184 else if( pred_node->type == JSON_ARRAY )
3185 pred = searchFunctionPredicate(
3186 class_info->alias, field, pred_node, pred_itr->key );
3187 else if( pred_node->type == JSON_HASH )
3188 pred = searchFieldTransformPredicate(
3189 class_info, field, pred_node, pred_itr->key );
3191 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
3193 jsonIteratorFree( pred_itr );
3195 } else if( node->type == JSON_NULL ) { // IS NULL search
3196 growing_buffer* _p = buffer_init( 64 );
3199 "\"%s\".%s IS NULL",
3201 osrfHashGet( field, "name" )
3203 pred = buffer_release( _p );
3204 } else { // equality search
3205 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3224 field : call_number,
3240 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3242 const jsonObject* working_hash;
3243 jsonObject* freeable_hash = NULL;
3245 if( join_hash->type == JSON_HASH ) {
3246 working_hash = join_hash;
3247 } else if( join_hash->type == JSON_STRING ) {
3248 // turn it into a JSON_HASH by creating a wrapper
3249 // around a copy of the original
3250 const char* _tmp = jsonObjectGetString( join_hash );
3251 freeable_hash = jsonNewObjectType( JSON_HASH );
3252 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3253 working_hash = freeable_hash;
3257 "%s: JOIN failed; expected JSON object type not found",
3263 growing_buffer* join_buf = buffer_init( 128 );
3264 const char* leftclass = left_info->class_name;
3266 jsonObject* snode = NULL;
3267 jsonIterator* search_itr = jsonNewIterator( working_hash );
3269 while ( (snode = jsonIteratorNext( search_itr )) ) {
3270 const char* right_alias = search_itr->key;
3272 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3274 class = right_alias;
3276 const ClassInfo* right_info = add_joined_class( right_alias, class );
3280 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3284 jsonIteratorFree( search_itr );
3285 buffer_free( join_buf );
3287 jsonObjectFree( freeable_hash );
3290 osrfHash* links = right_info->links;
3291 const char* table = right_info->source_def;
3293 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3294 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3296 if( field && !fkey ) {
3297 // Look up the corresponding join column in the IDL.
3298 // The link must be defined in the child table,
3299 // and point to the right parent table.
3300 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3301 const char* reltype = NULL;
3302 const char* other_class = NULL;
3303 reltype = osrfHashGet( idl_link, "reltype" );
3304 if( reltype && strcmp( reltype, "has_many" ) )
3305 other_class = osrfHashGet( idl_link, "class" );
3306 if( other_class && !strcmp( other_class, leftclass ) )
3307 fkey = osrfHashGet( idl_link, "key" );
3311 "%s: JOIN failed. No link defined from %s.%s to %s",
3317 buffer_free( join_buf );
3319 jsonObjectFree( freeable_hash );
3320 jsonIteratorFree( search_itr );
3324 } else if( !field && fkey ) {
3325 // Look up the corresponding join column in the IDL.
3326 // The link must be defined in the child table,
3327 // and point to the right parent table.
3328 osrfHash* left_links = left_info->links;
3329 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3330 const char* reltype = NULL;
3331 const char* other_class = NULL;
3332 reltype = osrfHashGet( idl_link, "reltype" );
3333 if( reltype && strcmp( reltype, "has_many" ) )
3334 other_class = osrfHashGet( idl_link, "class" );
3335 if( other_class && !strcmp( other_class, class ) )
3336 field = osrfHashGet( idl_link, "key" );
3340 "%s: JOIN failed. No link defined from %s.%s to %s",
3346 buffer_free( join_buf );
3348 jsonObjectFree( freeable_hash );
3349 jsonIteratorFree( search_itr );
3353 } else if( !field && !fkey ) {
3354 osrfHash* left_links = left_info->links;
3356 // For each link defined for the left class:
3357 // see if the link references the joined class
3358 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3359 osrfHash* curr_link = NULL;
3360 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3361 const char* other_class = osrfHashGet( curr_link, "class" );
3362 if( other_class && !strcmp( other_class, class ) ) {
3364 // In the IDL, the parent class doesn't always know then names of the child
3365 // columns that are pointing to it, so don't use that end of the link
3366 const char* reltype = osrfHashGet( curr_link, "reltype" );
3367 if( reltype && strcmp( reltype, "has_many" ) ) {
3368 // Found a link between the classes
3369 fkey = osrfHashIteratorKey( itr );
3370 field = osrfHashGet( curr_link, "key" );
3375 osrfHashIteratorFree( itr );
3377 if( !field || !fkey ) {
3378 // Do another such search, with the classes reversed
3380 // For each link defined for the joined class:
3381 // see if the link references the left class
3382 osrfHashIterator* itr = osrfNewHashIterator( links );
3383 osrfHash* curr_link = NULL;
3384 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3385 const char* other_class = osrfHashGet( curr_link, "class" );
3386 if( other_class && !strcmp( other_class, leftclass ) ) {
3388 // In the IDL, the parent class doesn't know then names of the child
3389 // columns that are pointing to it, so don't use that end of the link
3390 const char* reltype = osrfHashGet( curr_link, "reltype" );
3391 if( reltype && strcmp( reltype, "has_many" ) ) {
3392 // Found a link between the classes
3393 field = osrfHashIteratorKey( itr );
3394 fkey = osrfHashGet( curr_link, "key" );
3399 osrfHashIteratorFree( itr );
3402 if( !field || !fkey ) {
3405 "%s: JOIN failed. No link defined between %s and %s",
3410 buffer_free( join_buf );
3412 jsonObjectFree( freeable_hash );
3413 jsonIteratorFree( search_itr );
3418 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3420 if( !strcasecmp( type,"left" )) {
3421 buffer_add( join_buf, " LEFT JOIN" );
3422 } else if( !strcasecmp( type,"right" )) {
3423 buffer_add( join_buf, " RIGHT JOIN" );
3424 } else if( !strcasecmp( type,"full" )) {
3425 buffer_add( join_buf, " FULL JOIN" );
3427 buffer_add( join_buf, " INNER JOIN" );
3430 buffer_add( join_buf, " INNER JOIN" );
3433 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3434 table, right_alias, right_alias, field, left_info->alias, fkey );
3436 // Add any other join conditions as specified by "filter"
3437 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3439 const char* filter_op = jsonObjectGetString(
3440 jsonObjectGetKeyConst( snode, "filter_op" ) );
3441 if( filter_op && !strcasecmp( "or",filter_op )) {
3442 buffer_add( join_buf, " OR " );
3444 buffer_add( join_buf, " AND " );
3447 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3449 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3450 OSRF_BUFFER_ADD( join_buf, jpred );
3455 "%s: JOIN failed. Invalid conditional expression.",
3458 jsonIteratorFree( search_itr );
3459 buffer_free( join_buf );
3461 jsonObjectFree( freeable_hash );
3466 buffer_add( join_buf, " ) " );
3468 // Recursively add a nested join, if one is present
3469 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3471 char* jpred = searchJOIN( join_filter, right_info );
3473 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3474 OSRF_BUFFER_ADD( join_buf, jpred );
3477 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3478 jsonIteratorFree( search_itr );
3479 buffer_free( join_buf );
3481 jsonObjectFree( freeable_hash );
3488 jsonObjectFree( freeable_hash );
3489 jsonIteratorFree( search_itr );
3491 return buffer_release( join_buf );
3496 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3497 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3498 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3500 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3502 search_hash is the JSON expression of the conditions.
3503 meta is the class definition from the IDL, for the relevant table.
3504 opjoin_type indicates whether multiple conditions, if present, should be
3505 connected by AND or OR.
3506 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3507 to pass it to other functions -- and all they do with it is to use the session
3508 and request members to send error messages back to the client.
3512 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3513 int opjoin_type, osrfMethodContext* ctx ) {
3517 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3518 "opjoin_type = %d, ctx addr = %p",
3521 class_info->class_def,
3526 growing_buffer* sql_buf = buffer_init( 128 );
3528 jsonObject* node = NULL;
3531 if( search_hash->type == JSON_ARRAY ) {
3532 if( 0 == search_hash->size ) {
3535 "%s: Invalid predicate structure: empty JSON array",
3538 buffer_free( sql_buf );
3542 unsigned long i = 0;
3543 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3547 if( opjoin_type == OR_OP_JOIN )
3548 buffer_add( sql_buf, " OR " );
3550 buffer_add( sql_buf, " AND " );
3553 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3555 buffer_free( sql_buf );
3559 buffer_fadd( sql_buf, "( %s )", subpred );
3563 } else if( search_hash->type == JSON_HASH ) {
3564 osrfLogDebug( OSRF_LOG_MARK,
3565 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3566 jsonIterator* search_itr = jsonNewIterator( search_hash );
3567 if( !jsonIteratorHasNext( search_itr ) ) {
3570 "%s: Invalid predicate structure: empty JSON object",
3573 jsonIteratorFree( search_itr );
3574 buffer_free( sql_buf );
3578 while( (node = jsonIteratorNext( search_itr )) ) {
3583 if( opjoin_type == OR_OP_JOIN )
3584 buffer_add( sql_buf, " OR " );
3586 buffer_add( sql_buf, " AND " );
3589 if( '+' == search_itr->key[ 0 ] ) {
3591 // This plus sign prefixes a class name or other table alias;
3592 // make sure the table alias is in scope
3593 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3594 if( ! alias_info ) {
3597 "%s: Invalid table alias \"%s\" in WHERE clause",
3601 jsonIteratorFree( search_itr );
3602 buffer_free( sql_buf );
3606 if( node->type == JSON_STRING ) {
3607 // It's the name of a column; make sure it belongs to the class
3608 const char* fieldname = jsonObjectGetString( node );
3609 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3612 "%s: Invalid column name \"%s\" in WHERE clause "
3613 "for table alias \"%s\"",
3618 jsonIteratorFree( search_itr );
3619 buffer_free( sql_buf );
3623 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3625 // It's something more complicated
3626 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3628 jsonIteratorFree( search_itr );
3629 buffer_free( sql_buf );
3633 buffer_fadd( sql_buf, "( %s )", subpred );
3636 } else if( '-' == search_itr->key[ 0 ] ) {
3637 if( !strcasecmp( "-or", search_itr->key )) {
3638 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3640 jsonIteratorFree( search_itr );
3641 buffer_free( sql_buf );
3645 buffer_fadd( sql_buf, "( %s )", subpred );
3647 } else if( !strcasecmp( "-and", search_itr->key )) {
3648 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3650 jsonIteratorFree( search_itr );
3651 buffer_free( sql_buf );
3655 buffer_fadd( sql_buf, "( %s )", subpred );
3657 } else if( !strcasecmp("-not",search_itr->key) ) {
3658 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3660 jsonIteratorFree( search_itr );
3661 buffer_free( sql_buf );
3665 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3667 } else if( !strcasecmp( "-exists", search_itr->key )) {
3668 char* subpred = buildQuery( ctx, node, SUBSELECT );
3670 jsonIteratorFree( search_itr );
3671 buffer_free( sql_buf );
3675 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3677 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3678 char* subpred = buildQuery( ctx, node, SUBSELECT );
3680 jsonIteratorFree( search_itr );
3681 buffer_free( sql_buf );
3685 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3687 } else { // Invalid "minus" operator
3690 "%s: Invalid operator \"%s\" in WHERE clause",
3694 jsonIteratorFree( search_itr );
3695 buffer_free( sql_buf );
3701 const char* class = class_info->class_name;
3702 osrfHash* fields = class_info->fields;
3703 osrfHash* field = osrfHashGet( fields, search_itr->key );
3706 const char* table = class_info->source_def;
3709 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3712 table ? table : "?",
3715 jsonIteratorFree( search_itr );
3716 buffer_free( sql_buf );
3720 char* subpred = searchPredicate( class_info, field, node, ctx );
3722 buffer_free( sql_buf );
3723 jsonIteratorFree( search_itr );
3727 buffer_add( sql_buf, subpred );
3731 jsonIteratorFree( search_itr );
3734 // ERROR ... only hash and array allowed at this level
3735 char* predicate_string = jsonObjectToJSON( search_hash );
3738 "%s: Invalid predicate structure: %s",
3742 buffer_free( sql_buf );
3743 free( predicate_string );
3747 return buffer_release( sql_buf );
3750 /* Build a JSON_ARRAY of field names for a given table alias
3752 static jsonObject* defaultSelectList( const char* table_alias ) {
3757 ClassInfo* class_info = search_all_alias( table_alias );
3758 if( ! class_info ) {
3761 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3768 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3769 osrfHash* field_def = NULL;
3770 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3771 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3772 const char* field_name = osrfHashIteratorKey( field_itr );
3773 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3774 jsonObjectPush( array, jsonNewObject( field_name ) );
3777 osrfHashIteratorFree( field_itr );
3782 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3783 // The jsonObject must be a JSON_HASH with an single entry for "union",
3784 // "intersect", or "except". The data associated with this key must be an
3785 // array of hashes, each hash being a query.
3786 // Also allowed but currently ignored: entries for "order_by" and "alias".
3787 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3789 if( ! combo || combo->type != JSON_HASH )
3790 return NULL; // should be impossible; validated by caller
3792 const jsonObject* query_array = NULL; // array of subordinate queries
3793 const char* op = NULL; // name of operator, e.g. UNION
3794 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3795 int op_count = 0; // for detecting conflicting operators
3796 int excepting = 0; // boolean
3797 int all = 0; // boolean
3798 jsonObject* order_obj = NULL;
3800 // Identify the elements in the hash
3801 jsonIterator* query_itr = jsonNewIterator( combo );
3802 jsonObject* curr_obj = NULL;
3803 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3804 if( ! strcmp( "union", query_itr->key ) ) {
3807 query_array = curr_obj;
3808 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3811 query_array = curr_obj;
3812 } else if( ! strcmp( "except", query_itr->key ) ) {
3816 query_array = curr_obj;
3817 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3820 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3823 order_obj = curr_obj;
3824 } else if( ! strcmp( "alias", query_itr->key ) ) {
3825 if( curr_obj->type != JSON_STRING ) {
3826 jsonIteratorFree( query_itr );
3829 alias = jsonObjectGetString( curr_obj );
3830 } else if( ! strcmp( "all", query_itr->key ) ) {
3831 if( obj_is_true( curr_obj ) )
3835 osrfAppSessionStatus(
3837 OSRF_STATUS_INTERNALSERVERERROR,
3838 "osrfMethodException",
3840 "Malformed query; unexpected entry in query object"
3844 "%s: Unexpected entry for \"%s\" in%squery",
3849 jsonIteratorFree( query_itr );
3853 jsonIteratorFree( query_itr );
3855 // More sanity checks
3856 if( ! query_array ) {
3858 osrfAppSessionStatus(
3860 OSRF_STATUS_INTERNALSERVERERROR,
3861 "osrfMethodException",
3863 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3867 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3870 return NULL; // should be impossible...
3871 } else if( op_count > 1 ) {
3873 osrfAppSessionStatus(
3875 OSRF_STATUS_INTERNALSERVERERROR,
3876 "osrfMethodException",
3878 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3882 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3886 } if( query_array->type != JSON_ARRAY ) {
3888 osrfAppSessionStatus(
3890 OSRF_STATUS_INTERNALSERVERERROR,
3891 "osrfMethodException",
3893 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3897 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3900 json_type( query_array->type )
3903 } if( query_array->size < 2 ) {
3905 osrfAppSessionStatus(
3907 OSRF_STATUS_INTERNALSERVERERROR,
3908 "osrfMethodException",
3910 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3914 "%s:%srequires multiple queries as operands",
3919 } else if( excepting && query_array->size > 2 ) {
3921 osrfAppSessionStatus(
3923 OSRF_STATUS_INTERNALSERVERERROR,
3924 "osrfMethodException",
3926 "EXCEPT operator has too many queries as operands"
3930 "%s:EXCEPT operator has too many queries as operands",
3934 } else if( order_obj && ! alias ) {
3936 osrfAppSessionStatus(
3938 OSRF_STATUS_INTERNALSERVERERROR,
3939 "osrfMethodException",
3941 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3945 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3951 // So far so good. Now build the SQL.
3952 growing_buffer* sql = buffer_init( 256 );
3954 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3955 // Add a layer of parentheses
3956 if( flags & SUBCOMBO )
3957 OSRF_BUFFER_ADD( sql, "( " );
3959 // Traverse the query array. Each entry should be a hash.
3960 int first = 1; // boolean
3962 jsonObject* query = NULL;
3963 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3964 if( query->type != JSON_HASH ) {
3966 osrfAppSessionStatus(
3968 OSRF_STATUS_INTERNALSERVERERROR,
3969 "osrfMethodException",
3971 "Malformed query under UNION, INTERSECT or EXCEPT"
3975 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3978 json_type( query->type )
3987 OSRF_BUFFER_ADD( sql, op );
3989 OSRF_BUFFER_ADD( sql, "ALL " );
3992 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3996 "%s: Error building query under%s",
4004 OSRF_BUFFER_ADD( sql, query_str );
4007 if( flags & SUBCOMBO )
4008 OSRF_BUFFER_ADD_CHAR( sql, ')' );
4010 if( !(flags & SUBSELECT) )
4011 OSRF_BUFFER_ADD_CHAR( sql, ';' );
4013 return buffer_release( sql );
4016 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
4017 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
4018 // or "except" to indicate the type of query.
4019 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
4023 osrfAppSessionStatus(
4025 OSRF_STATUS_INTERNALSERVERERROR,
4026 "osrfMethodException",
4028 "Malformed query; no query object"
4030 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
4032 } else if( query->type != JSON_HASH ) {
4034 osrfAppSessionStatus(
4036 OSRF_STATUS_INTERNALSERVERERROR,
4037 "osrfMethodException",
4039 "Malformed query object"
4043 "%s: Query object is %s instead of JSON_HASH",
4045 json_type( query->type )
4050 // Determine what kind of query it purports to be, and dispatch accordingly.
4051 if( jsonObjectGetKeyConst( query, "union" ) ||
4052 jsonObjectGetKeyConst( query, "intersect" ) ||
4053 jsonObjectGetKeyConst( query, "except" )) {
4054 return doCombo( ctx, query, flags );
4056 // It is presumably a SELECT query
4058 // Push a node onto the stack for the current query. Every level of
4059 // subquery gets its own QueryFrame on the Stack.
4062 // Build an SQL SELECT statement
4065 jsonObjectGetKey( query, "select" ),
4066 jsonObjectGetKeyConst( query, "from" ),
4067 jsonObjectGetKeyConst( query, "where" ),
4068 jsonObjectGetKeyConst( query, "having" ),
4069 jsonObjectGetKeyConst( query, "order_by" ),
4070 jsonObjectGetKeyConst( query, "limit" ),
4071 jsonObjectGetKeyConst( query, "offset" ),
4080 /* method context */ osrfMethodContext* ctx,
4082 /* SELECT */ jsonObject* selhash,
4083 /* FROM */ const jsonObject* join_hash,
4084 /* WHERE */ const jsonObject* search_hash,
4085 /* HAVING */ const jsonObject* having_hash,
4086 /* ORDER BY */ const jsonObject* order_hash,
4087 /* LIMIT */ const jsonObject* limit,
4088 /* OFFSET */ const jsonObject* offset,
4089 /* flags */ int flags
4091 const char* locale = osrf_message_get_last_locale();
4093 // general tmp objects
4094 const jsonObject* tmp_const;
4095 jsonObject* selclass = NULL;
4096 jsonObject* snode = NULL;
4097 jsonObject* onode = NULL;
4099 char* string = NULL;
4100 int from_function = 0;
4105 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
4107 // punt if there's no FROM clause
4108 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
4111 "%s: FROM clause is missing or empty",
4115 osrfAppSessionStatus(
4117 OSRF_STATUS_INTERNALSERVERERROR,
4118 "osrfMethodException",
4120 "FROM clause is missing or empty in JSON query"
4125 // the core search class
4126 const char* core_class = NULL;
4128 // get the core class -- the only key of the top level FROM clause, or a string
4129 if( join_hash->type == JSON_HASH ) {
4130 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
4131 snode = jsonIteratorNext( tmp_itr );
4133 // Populate the current QueryFrame with information
4134 // about the core class
4135 if( add_query_core( NULL, tmp_itr->key ) ) {
4137 osrfAppSessionStatus(
4139 OSRF_STATUS_INTERNALSERVERERROR,
4140 "osrfMethodException",
4142 "Unable to look up core class"
4146 core_class = curr_query->core.class_name;
4149 jsonObject* extra = jsonIteratorNext( tmp_itr );
4151 jsonIteratorFree( tmp_itr );
4154 // There shouldn't be more than one entry in join_hash
4158 "%s: Malformed FROM clause: extra entry in JSON_HASH",
4162 osrfAppSessionStatus(
4164 OSRF_STATUS_INTERNALSERVERERROR,
4165 "osrfMethodException",
4167 "Malformed FROM clause in JSON query"
4169 return NULL; // Malformed join_hash; extra entry
4171 } else if( join_hash->type == JSON_ARRAY ) {
4172 // We're selecting from a function, not from a table
4174 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
4177 } else if( join_hash->type == JSON_STRING ) {
4178 // Populate the current QueryFrame with information
4179 // about the core class
4180 core_class = jsonObjectGetString( join_hash );
4182 if( add_query_core( NULL, core_class ) ) {
4184 osrfAppSessionStatus(
4186 OSRF_STATUS_INTERNALSERVERERROR,
4187 "osrfMethodException",
4189 "Unable to look up core class"
4197 "%s: FROM clause is unexpected JSON type: %s",
4199 json_type( join_hash->type )
4202 osrfAppSessionStatus(
4204 OSRF_STATUS_INTERNALSERVERERROR,
4205 "osrfMethodException",
4207 "Ill-formed FROM clause in JSON query"
4212 // Build the join clause, if any, while filling out the list
4213 // of joined classes in the current QueryFrame.
4214 char* join_clause = NULL;
4215 if( join_hash && ! from_function ) {
4217 join_clause = searchJOIN( join_hash, &curr_query->core );
4218 if( ! join_clause ) {
4220 osrfAppSessionStatus(
4222 OSRF_STATUS_INTERNALSERVERERROR,
4223 "osrfMethodException",
4225 "Unable to construct JOIN clause(s)"
4231 // For in case we don't get a select list
4232 jsonObject* defaultselhash = NULL;
4234 // if there is no select list, build a default select list ...
4235 if( !selhash && !from_function ) {
4236 jsonObject* default_list = defaultSelectList( core_class );
4237 if( ! default_list ) {
4239 osrfAppSessionStatus(
4241 OSRF_STATUS_INTERNALSERVERERROR,
4242 "osrfMethodException",
4244 "Unable to build default SELECT clause in JSON query"
4246 free( join_clause );
4251 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4252 jsonObjectSetKey( selhash, core_class, default_list );
4255 // The SELECT clause can be encoded only by a hash
4256 if( !from_function && selhash->type != JSON_HASH ) {
4259 "%s: Expected JSON_HASH for SELECT clause; found %s",
4261 json_type( selhash->type )
4265 osrfAppSessionStatus(
4267 OSRF_STATUS_INTERNALSERVERERROR,
4268 "osrfMethodException",
4270 "Malformed SELECT clause in JSON query"
4272 free( join_clause );
4276 // If you see a null or wild card specifier for the core class, or an
4277 // empty array, replace it with a default SELECT list
4278 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4280 int default_needed = 0; // boolean
4281 if( JSON_STRING == tmp_const->type
4282 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4284 else if( JSON_NULL == tmp_const->type )
4287 if( default_needed ) {
4288 // Build a default SELECT list
4289 jsonObject* default_list = defaultSelectList( core_class );
4290 if( ! default_list ) {
4292 osrfAppSessionStatus(
4294 OSRF_STATUS_INTERNALSERVERERROR,
4295 "osrfMethodException",
4297 "Can't build default SELECT clause in JSON query"
4299 free( join_clause );
4304 jsonObjectSetKey( selhash, core_class, default_list );
4308 // temp buffers for the SELECT list and GROUP BY clause
4309 growing_buffer* select_buf = buffer_init( 128 );
4310 growing_buffer* group_buf = buffer_init( 128 );
4312 int aggregate_found = 0; // boolean
4314 // Build a select list
4315 if( from_function ) // From a function we select everything
4316 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4319 // Build the SELECT list as SQL
4323 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4324 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4326 const char* cname = selclass_itr->key;
4328 // Make sure the target relation is in the FROM clause.
4330 // At this point join_hash is a step down from the join_hash we
4331 // received as a parameter. If the original was a JSON_STRING,
4332 // then json_hash is now NULL. If the original was a JSON_HASH,
4333 // then json_hash is now the first (and only) entry in it,
4334 // denoting the core class. We've already excluded the
4335 // possibility that the original was a JSON_ARRAY, because in
4336 // that case from_function would be non-NULL, and we wouldn't
4339 // If the current table alias isn't in scope, bail out
4340 ClassInfo* class_info = search_alias( cname );
4341 if( ! class_info ) {
4344 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4349 osrfAppSessionStatus(
4351 OSRF_STATUS_INTERNALSERVERERROR,
4352 "osrfMethodException",
4354 "Selected class not in FROM clause in JSON query"
4356 jsonIteratorFree( selclass_itr );
4357 buffer_free( select_buf );
4358 buffer_free( group_buf );
4359 if( defaultselhash )
4360 jsonObjectFree( defaultselhash );
4361 free( join_clause );
4365 if( selclass->type != JSON_ARRAY ) {
4368 "%s: Malformed SELECT list for class \"%s\"; not an array",
4373 osrfAppSessionStatus(
4375 OSRF_STATUS_INTERNALSERVERERROR,
4376 "osrfMethodException",
4378 "Selected class not in FROM clause in JSON query"
4381 jsonIteratorFree( selclass_itr );
4382 buffer_free( select_buf );
4383 buffer_free( group_buf );
4384 if( defaultselhash )
4385 jsonObjectFree( defaultselhash );
4386 free( join_clause );
4390 // Look up some attributes of the current class
4391 osrfHash* idlClass = class_info->class_def;
4392 osrfHash* class_field_set = class_info->fields;
4393 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4394 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4396 if( 0 == selclass->size ) {
4399 "%s: No columns selected from \"%s\"",
4405 // stitch together the column list for the current table alias...
4406 unsigned long field_idx = 0;
4407 jsonObject* selfield = NULL;
4408 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4410 // If we need a separator comma, add one
4414 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4417 // if the field specification is a string, add it to the list
4418 if( selfield->type == JSON_STRING ) {
4420 // Look up the field in the IDL
4421 const char* col_name = jsonObjectGetString( selfield );
4422 osrfHash* field_def = NULL;
4424 if (!osrfStringArrayContains(
4426 osrfHashGet( class_field_set, col_name ),
4427 "suppress_controller"),
4430 field_def = osrfHashGet( class_field_set, col_name );
4433 // No such field in current class
4436 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4442 osrfAppSessionStatus(
4444 OSRF_STATUS_INTERNALSERVERERROR,
4445 "osrfMethodException",
4447 "Selected column not defined in JSON query"
4449 jsonIteratorFree( selclass_itr );
4450 buffer_free( select_buf );
4451 buffer_free( group_buf );
4452 if( defaultselhash )
4453 jsonObjectFree( defaultselhash );
4454 free( join_clause );
4456 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4457 // Virtual field not allowed
4460 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4466 osrfAppSessionStatus(
4468 OSRF_STATUS_INTERNALSERVERERROR,
4469 "osrfMethodException",
4471 "Selected column may not be virtual in JSON query"
4473 jsonIteratorFree( selclass_itr );
4474 buffer_free( select_buf );
4475 buffer_free( group_buf );
4476 if( defaultselhash )
4477 jsonObjectFree( defaultselhash );
4478 free( join_clause );
4484 if( flags & DISABLE_I18N )
4487 i18n = osrfHashGet( field_def, "i18n" );
4489 if( str_is_true( i18n ) ) {
4490 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4491 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4492 class_tname, cname, col_name, class_pkey,
4493 cname, class_pkey, locale, col_name );
4495 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4496 cname, col_name, col_name );
4499 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4500 cname, col_name, col_name );
4503 // ... but it could be an object, in which case we check for a Field Transform
4504 } else if( selfield->type == JSON_HASH ) {
4506 const char* col_name = jsonObjectGetString(
4507 jsonObjectGetKeyConst( selfield, "column" ) );
4509 // Get the field definition from the IDL
4510 osrfHash* field_def = NULL;
4511 if (!osrfStringArrayContains(
4513 osrfHashGet( class_field_set, col_name ),
4514 "suppress_controller"),
4517 field_def = osrfHashGet( class_field_set, col_name );
4521 // No such field in current class
4524 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4530 osrfAppSessionStatus(
4532 OSRF_STATUS_INTERNALSERVERERROR,
4533 "osrfMethodException",
4535 "Selected column is not defined in JSON query"
4537 jsonIteratorFree( selclass_itr );
4538 buffer_free( select_buf );
4539 buffer_free( group_buf );
4540 if( defaultselhash )
4541 jsonObjectFree( defaultselhash );
4542 free( join_clause );
4544 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4545 // No such field in current class
4548 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4554 osrfAppSessionStatus(
4556 OSRF_STATUS_INTERNALSERVERERROR,
4557 "osrfMethodException",
4559 "Selected column is virtual in JSON query"
4561 jsonIteratorFree( selclass_itr );
4562 buffer_free( select_buf );
4563 buffer_free( group_buf );
4564 if( defaultselhash )
4565 jsonObjectFree( defaultselhash );
4566 free( join_clause );
4570 // Decide what to use as a column alias
4572 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4573 _alias = jsonObjectGetString( tmp_const );
4574 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4575 _alias = jsonObjectGetString( tmp_const );
4576 } else { // Use field name as the alias
4580 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4581 char* transform_str = searchFieldTransform(
4582 class_info->alias, field_def, selfield );
4583 if( transform_str ) {
4584 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4585 free( transform_str );
4588 osrfAppSessionStatus(
4590 OSRF_STATUS_INTERNALSERVERERROR,
4591 "osrfMethodException",
4593 "Unable to generate transform function in JSON query"
4595 jsonIteratorFree( selclass_itr );
4596 buffer_free( select_buf );
4597 buffer_free( group_buf );
4598 if( defaultselhash )
4599 jsonObjectFree( defaultselhash );
4600 free( join_clause );
4607 if( flags & DISABLE_I18N )
4610 i18n = osrfHashGet( field_def, "i18n" );
4612 if( str_is_true( i18n ) ) {
4613 buffer_fadd( select_buf,
4614 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4615 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4616 class_tname, cname, col_name, class_pkey, cname,
4617 class_pkey, locale, _alias );
4619 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4620 cname, col_name, _alias );
4623 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4624 cname, col_name, _alias );
4631 "%s: Selected item is unexpected JSON type: %s",
4633 json_type( selfield->type )
4636 osrfAppSessionStatus(
4638 OSRF_STATUS_INTERNALSERVERERROR,
4639 "osrfMethodException",
4641 "Ill-formed SELECT item in JSON query"
4643 jsonIteratorFree( selclass_itr );
4644 buffer_free( select_buf );
4645 buffer_free( group_buf );
4646 if( defaultselhash )
4647 jsonObjectFree( defaultselhash );
4648 free( join_clause );
4652 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4653 if( obj_is_true( agg_obj ) )
4654 aggregate_found = 1;
4656 // Append a comma (except for the first one)
4657 // and add the column to a GROUP BY clause
4661 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4663 buffer_fadd( group_buf, " %d", sel_pos );
4667 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4669 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( selfield, "aggregate");
4670 if ( ! obj_is_true( aggregate_obj ) ) {
4674 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4677 buffer_fadd(group_buf, " %d", sel_pos);
4680 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4684 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4687 _column = searchFieldTransform(class_info->alias, field, selfield);
4688 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4689 OSRF_BUFFER_ADD(group_buf, _column);
4690 _column = searchFieldTransform(class_info->alias, field, selfield);
4697 } // end while -- iterating across SELECT columns
4699 } // end while -- iterating across classes
4701 jsonIteratorFree( selclass_itr );
4704 char* col_list = buffer_release( select_buf );
4706 // Make sure the SELECT list isn't empty. This can happen, for example,
4707 // if we try to build a default SELECT clause from a non-core table.
4710 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4712 osrfAppSessionStatus(
4714 OSRF_STATUS_INTERNALSERVERERROR,
4715 "osrfMethodException",
4717 "SELECT list is empty"
4720 buffer_free( group_buf );
4721 if( defaultselhash )
4722 jsonObjectFree( defaultselhash );
4723 free( join_clause );
4729 table = searchValueTransform( join_hash );
4731 table = strdup( curr_query->core.source_def );
4735 osrfAppSessionStatus(
4737 OSRF_STATUS_INTERNALSERVERERROR,
4738 "osrfMethodException",
4740 "Unable to identify table for core class"
4743 buffer_free( group_buf );
4744 if( defaultselhash )
4745 jsonObjectFree( defaultselhash );
4746 free( join_clause );
4750 // Put it all together
4751 growing_buffer* sql_buf = buffer_init( 128 );
4752 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4756 // Append the join clause, if any
4758 buffer_add(sql_buf, join_clause );
4759 free( join_clause );
4762 char* order_by_list = NULL;
4763 char* having_buf = NULL;
4765 if( !from_function ) {
4767 // Build a WHERE clause, if there is one
4769 buffer_add( sql_buf, " WHERE " );
4771 // and it's on the WHERE clause
4772 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4775 osrfAppSessionStatus(
4777 OSRF_STATUS_INTERNALSERVERERROR,
4778 "osrfMethodException",
4780 "Severe query error in WHERE predicate -- see error log for more details"
4783 buffer_free( group_buf );
4784 buffer_free( sql_buf );
4785 if( defaultselhash )
4786 jsonObjectFree( defaultselhash );
4790 buffer_add( sql_buf, pred );
4794 // Build a HAVING clause, if there is one
4797 // and it's on the the WHERE clause
4798 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4800 if( ! having_buf ) {
4802 osrfAppSessionStatus(
4804 OSRF_STATUS_INTERNALSERVERERROR,
4805 "osrfMethodException",
4807 "Severe query error in HAVING predicate -- see error log for more details"
4810 buffer_free( group_buf );
4811 buffer_free( sql_buf );
4812 if( defaultselhash )
4813 jsonObjectFree( defaultselhash );
4818 // Build an ORDER BY clause, if there is one
4819 if( NULL == order_hash )
4820 ; // No ORDER BY? do nothing
4821 else if( JSON_ARRAY == order_hash->type ) {
4822 order_by_list = buildOrderByFromArray( ctx, order_hash );
4823 if( !order_by_list ) {
4825 buffer_free( group_buf );
4826 buffer_free( sql_buf );
4827 if( defaultselhash )
4828 jsonObjectFree( defaultselhash );
4831 } else if( JSON_HASH == order_hash->type ) {
4832 // This hash is keyed on class alias. Each class has either
4833 // an array of field names or a hash keyed on field name.
4834 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4835 jsonIterator* class_itr = jsonNewIterator( order_hash );
4836 while( (snode = jsonIteratorNext( class_itr )) ) {
4838 ClassInfo* order_class_info = search_alias( class_itr->key );
4839 if( ! order_class_info ) {
4840 osrfLogError( OSRF_LOG_MARK,
4841 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4842 modulename, class_itr->key );
4844 osrfAppSessionStatus(
4846 OSRF_STATUS_INTERNALSERVERERROR,
4847 "osrfMethodException",
4849 "Invalid class referenced in ORDER BY clause -- "
4850 "see error log for more details"
4852 jsonIteratorFree( class_itr );
4853 buffer_free( order_buf );
4855 buffer_free( group_buf );
4856 buffer_free( sql_buf );
4857 if( defaultselhash )
4858 jsonObjectFree( defaultselhash );
4862 osrfHash* field_list_def = order_class_info->fields;
4864 if( snode->type == JSON_HASH ) {
4866 // Hash is keyed on field names from the current class. For each field
4867 // there is another layer of hash to define the sorting details, if any,
4868 // or a string to indicate direction of sorting.
4869 jsonIterator* order_itr = jsonNewIterator( snode );
4870 while( (onode = jsonIteratorNext( order_itr )) ) {
4872 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4874 osrfLogError( OSRF_LOG_MARK,
4875 "%s: Invalid field \"%s\" in ORDER BY clause",
4876 modulename, order_itr->key );
4878 osrfAppSessionStatus(
4880 OSRF_STATUS_INTERNALSERVERERROR,
4881 "osrfMethodException",
4883 "Invalid field in ORDER BY clause -- "
4884 "see error log for more details"
4886 jsonIteratorFree( order_itr );
4887 jsonIteratorFree( class_itr );
4888 buffer_free( order_buf );
4890 buffer_free( group_buf );
4891 buffer_free( sql_buf );
4892 if( defaultselhash )
4893 jsonObjectFree( defaultselhash );
4895 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4896 osrfLogError( OSRF_LOG_MARK,
4897 "%s: Virtual field \"%s\" in ORDER BY clause",
4898 modulename, order_itr->key );
4900 osrfAppSessionStatus(
4902 OSRF_STATUS_INTERNALSERVERERROR,
4903 "osrfMethodException",
4905 "Virtual field in ORDER BY clause -- "
4906 "see error log for more details"
4908 jsonIteratorFree( order_itr );
4909 jsonIteratorFree( class_itr );
4910 buffer_free( order_buf );
4912 buffer_free( group_buf );
4913 buffer_free( sql_buf );
4914 if( defaultselhash )
4915 jsonObjectFree( defaultselhash );
4919 const char* direction = NULL;
4920 if( onode->type == JSON_HASH ) {
4921 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4922 string = searchFieldTransform(
4924 osrfHashGet( field_list_def, order_itr->key ),
4928 if( ctx ) osrfAppSessionStatus(
4930 OSRF_STATUS_INTERNALSERVERERROR,
4931 "osrfMethodException",
4933 "Severe query error in ORDER BY clause -- "
4934 "see error log for more details"
4936 jsonIteratorFree( order_itr );
4937 jsonIteratorFree( class_itr );
4939 buffer_free( group_buf );
4940 buffer_free( order_buf);
4941 buffer_free( sql_buf );
4942 if( defaultselhash )
4943 jsonObjectFree( defaultselhash );
4947 growing_buffer* field_buf = buffer_init( 16 );
4948 buffer_fadd( field_buf, "\"%s\".%s",
4949 class_itr->key, order_itr->key );
4950 string = buffer_release( field_buf );
4953 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4954 const char* dir = jsonObjectGetString( tmp_const );
4955 if(!strncasecmp( dir, "d", 1 )) {
4956 direction = " DESC";
4962 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4963 osrfLogError( OSRF_LOG_MARK,
4964 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4965 modulename, json_type( onode->type ) );
4967 osrfAppSessionStatus(
4969 OSRF_STATUS_INTERNALSERVERERROR,
4970 "osrfMethodException",
4972 "Malformed ORDER BY clause -- see error log for more details"
4974 jsonIteratorFree( order_itr );
4975 jsonIteratorFree( class_itr );
4977 buffer_free( group_buf );
4978 buffer_free( order_buf );
4979 buffer_free( sql_buf );
4980 if( defaultselhash )
4981 jsonObjectFree( defaultselhash );
4985 string = strdup( order_itr->key );
4986 const char* dir = jsonObjectGetString( onode );
4987 if( !strncasecmp( dir, "d", 1 )) {
4988 direction = " DESC";
4995 OSRF_BUFFER_ADD( order_buf, ", " );
4997 order_buf = buffer_init( 128 );
4999 OSRF_BUFFER_ADD( order_buf, string );
5003 OSRF_BUFFER_ADD( order_buf, direction );
5007 jsonIteratorFree( order_itr );
5009 } else if( snode->type == JSON_ARRAY ) {
5011 // Array is a list of fields from the current class
5012 unsigned long order_idx = 0;
5013 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
5015 const char* _f = jsonObjectGetString( onode );
5017 osrfHash* field_def = osrfHashGet( field_list_def, _f );
5019 osrfLogError( OSRF_LOG_MARK,
5020 "%s: Invalid field \"%s\" in ORDER BY clause",
5023 osrfAppSessionStatus(
5025 OSRF_STATUS_INTERNALSERVERERROR,
5026 "osrfMethodException",
5028 "Invalid field in ORDER BY clause -- "
5029 "see error log for more details"
5031 jsonIteratorFree( class_itr );
5032 buffer_free( order_buf );
5034 buffer_free( group_buf );
5035 buffer_free( sql_buf );
5036 if( defaultselhash )
5037 jsonObjectFree( defaultselhash );
5039 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5040 osrfLogError( OSRF_LOG_MARK,
5041 "%s: Virtual field \"%s\" in ORDER BY clause",
5044 osrfAppSessionStatus(
5046 OSRF_STATUS_INTERNALSERVERERROR,
5047 "osrfMethodException",
5049 "Virtual field in ORDER BY clause -- "
5050 "see error log for more details"
5052 jsonIteratorFree( class_itr );
5053 buffer_free( order_buf );
5055 buffer_free( group_buf );
5056 buffer_free( sql_buf );
5057 if( defaultselhash )
5058 jsonObjectFree( defaultselhash );
5063 OSRF_BUFFER_ADD( order_buf, ", " );
5065 order_buf = buffer_init( 128 );
5067 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
5071 // IT'S THE OOOOOOOOOOOLD STYLE!
5073 osrfLogError( OSRF_LOG_MARK,
5074 "%s: Possible SQL injection attempt; direct order by is not allowed",
5077 osrfAppSessionStatus(
5079 OSRF_STATUS_INTERNALSERVERERROR,
5080 "osrfMethodException",
5082 "Severe query error -- see error log for more details"
5087 buffer_free( group_buf );
5088 buffer_free( order_buf );
5089 buffer_free( sql_buf );
5090 if( defaultselhash )
5091 jsonObjectFree( defaultselhash );
5092 jsonIteratorFree( class_itr );
5096 jsonIteratorFree( class_itr );
5098 order_by_list = buffer_release( order_buf );
5100 osrfLogError( OSRF_LOG_MARK,
5101 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
5102 modulename, json_type( order_hash->type ) );
5104 osrfAppSessionStatus(
5106 OSRF_STATUS_INTERNALSERVERERROR,
5107 "osrfMethodException",
5109 "Malformed ORDER BY clause -- see error log for more details"
5112 buffer_free( group_buf );
5113 buffer_free( sql_buf );
5114 if( defaultselhash )
5115 jsonObjectFree( defaultselhash );
5120 string = buffer_release( group_buf );
5122 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
5123 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
5124 OSRF_BUFFER_ADD( sql_buf, string );
5129 if( having_buf && *having_buf ) {
5130 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
5131 OSRF_BUFFER_ADD( sql_buf, having_buf );
5135 if( order_by_list ) {
5137 if( *order_by_list ) {
5138 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5139 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5142 free( order_by_list );
5146 const char* str = jsonObjectGetString( limit );
5147 if (str) { // limit could be JSON_NULL, etc.
5148 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
5153 const char* str = jsonObjectGetString( offset );
5155 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
5159 if( !(flags & SUBSELECT) )
5160 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5162 if( defaultselhash )
5163 jsonObjectFree( defaultselhash );
5165 return buffer_release( sql_buf );
5167 } // end of SELECT()
5170 @brief Build a list of ORDER BY expressions.
5171 @param ctx Pointer to the method context.
5172 @param order_array Pointer to a JSON_ARRAY of field specifications.
5173 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
5174 Each expression may be either a column reference or a function call whose first parameter
5175 is a column reference.
5177 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
5178 It may optionally include entries for "direction" and/or "transform".
5180 The calling code is responsible for freeing the returned string.
5182 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
5183 if( ! order_array ) {
5184 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
5187 osrfAppSessionStatus(
5189 OSRF_STATUS_INTERNALSERVERERROR,
5190 "osrfMethodException",
5192 "Logic error: ORDER BY clause expected, not found; "
5193 "see error log for more details"
5196 } else if( order_array->type != JSON_ARRAY ) {
5197 osrfLogError( OSRF_LOG_MARK,
5198 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
5200 osrfAppSessionStatus(
5202 OSRF_STATUS_INTERNALSERVERERROR,
5203 "osrfMethodException",
5205 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
5209 growing_buffer* order_buf = buffer_init( 128 );
5210 int first = 1; // boolean
5212 jsonObject* order_spec;
5213 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
5215 if( JSON_HASH != order_spec->type ) {
5216 osrfLogError( OSRF_LOG_MARK,
5217 "%s: Malformed field specification in ORDER BY clause; "
5218 "expected JSON_HASH, found %s",
5219 modulename, json_type( order_spec->type ) );
5221 osrfAppSessionStatus(
5223 OSRF_STATUS_INTERNALSERVERERROR,
5224 "osrfMethodException",
5226 "Malformed ORDER BY clause -- see error log for more details"
5228 buffer_free( order_buf );
5232 const char* class_alias =
5233 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5235 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5237 jsonObject* compare_to = jsonObjectGetKey( order_spec, "compare" );
5239 if( !field || !class_alias ) {
5240 osrfLogError( OSRF_LOG_MARK,
5241 "%s: Missing class or field name in field specification of ORDER BY clause",
5244 osrfAppSessionStatus(
5246 OSRF_STATUS_INTERNALSERVERERROR,
5247 "osrfMethodException",
5249 "Malformed ORDER BY clause -- see error log for more details"
5251 buffer_free( order_buf );
5255 const ClassInfo* order_class_info = search_alias( class_alias );
5256 if( ! order_class_info ) {
5257 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5258 "not in FROM clause, skipping it", modulename, class_alias );
5262 // Add a separating comma, except at the beginning
5266 OSRF_BUFFER_ADD( order_buf, ", " );
5268 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5270 osrfLogError( OSRF_LOG_MARK,
5271 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5272 modulename, class_alias, field );
5274 osrfAppSessionStatus(
5276 OSRF_STATUS_INTERNALSERVERERROR,
5277 "osrfMethodException",
5279 "Invalid field referenced in ORDER BY clause -- "
5280 "see error log for more details"
5284 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5285 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5286 modulename, field );
5288 osrfAppSessionStatus(
5290 OSRF_STATUS_INTERNALSERVERERROR,
5291 "osrfMethodException",
5293 "Virtual field in ORDER BY clause -- see error log for more details"
5295 buffer_free( order_buf );
5299 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5300 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5301 if( ! transform_str ) {
5303 osrfAppSessionStatus(
5305 OSRF_STATUS_INTERNALSERVERERROR,
5306 "osrfMethodException",
5308 "Severe query error in ORDER BY clause -- "
5309 "see error log for more details"
5311 buffer_free( order_buf );
5315 OSRF_BUFFER_ADD( order_buf, transform_str );
5316 free( transform_str );
5317 } else if( compare_to ) {
5318 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5319 if( ! compare_str ) {
5321 osrfAppSessionStatus(
5323 OSRF_STATUS_INTERNALSERVERERROR,
5324 "osrfMethodException",
5326 "Severe query error in ORDER BY clause -- "
5327 "see error log for more details"
5329 buffer_free( order_buf );
5333 buffer_fadd( order_buf, "(%s)", compare_str );
5334 free( compare_str );
5337 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5339 const char* direction =
5340 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5342 if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5343 OSRF_BUFFER_ADD( order_buf, " DESC" );
5345 OSRF_BUFFER_ADD( order_buf, " ASC" );
5349 return buffer_release( order_buf );
5353 @brief Build a SELECT statement.
5354 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5355 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5356 @param meta Pointer to the class metadata for the core class.
5357 @param ctx Pointer to the method context.
5358 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5360 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5361 "order_by", "limit", and "offset".
5363 The SELECT statements built here are distinct from those built for the json_query method.
5365 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5366 osrfHash* meta, osrfMethodContext* ctx ) {
5368 const char* locale = osrf_message_get_last_locale();
5370 osrfHash* fields = osrfHashGet( meta, "fields" );
5371 const char* core_class = osrfHashGet( meta, "classname" );
5373 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5375 jsonObject* selhash = NULL;
5376 jsonObject* defaultselhash = NULL;
5378 growing_buffer* sql_buf = buffer_init( 128 );
5379 growing_buffer* select_buf = buffer_init( 128 );
5381 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5382 defaultselhash = jsonNewObjectType( JSON_HASH );
5383 selhash = defaultselhash;
5386 // If there's no SELECT list for the core class, build one
5387 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5388 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5390 // Add every non-virtual field to the field list
5391 osrfHash* field_def = NULL;
5392 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5393 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5394 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5395 const char* field = osrfHashIteratorKey( field_itr );
5396 jsonObjectPush( field_list, jsonNewObject( field ) );
5399 osrfHashIteratorFree( field_itr );
5400 jsonObjectSetKey( selhash, core_class, field_list );
5403 // Build a list of columns for the SELECT clause
5405 const jsonObject* snode = NULL;
5406 jsonIterator* class_itr = jsonNewIterator( selhash );
5407 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5409 // If the class isn't in the IDL, ignore it
5410 const char* cname = class_itr->key;
5411 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5415 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5416 if( strcmp( core_class, class_itr->key )) {
5420 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5421 if( !found->size ) {
5422 jsonObjectFree( found );
5426 jsonObjectFree( found );
5429 const jsonObject* node = NULL;
5430 jsonIterator* select_itr = jsonNewIterator( snode );
5431 while( (node = jsonIteratorNext( select_itr )) ) {
5432 const char* item_str = jsonObjectGetString( node );
5433 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5434 char* fname = osrfHashGet( field, "name" );
5439 if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5445 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5450 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5451 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5454 i18n = osrfHashGet( field, "i18n" );
5456 if( str_is_true( i18n ) ) {
5457 char* pkey = osrfHashGet( idlClass, "primarykey" );
5458 char* tname = osrfHashGet( idlClass, "tablename" );
5460 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5461 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5462 tname, cname, fname, pkey, cname, pkey, locale, fname );
5464 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5467 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5471 jsonIteratorFree( select_itr );
5474 jsonIteratorFree( class_itr );
5476 char* col_list = buffer_release( select_buf );
5477 char* table = oilsGetRelation( meta );
5479 table = strdup( "(null)" );
5481 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5485 // Clear the query stack (as a fail-safe precaution against possible
5486 // leftover garbage); then push the first query frame onto the stack.
5487 clear_query_stack();
5489 if( add_query_core( NULL, core_class ) ) {
5491 osrfAppSessionStatus(
5493 OSRF_STATUS_INTERNALSERVERERROR,
5494 "osrfMethodException",
5496 "Unable to build query frame for core class"
5498 buffer_free( sql_buf );
5499 if( defaultselhash )
5500 jsonObjectFree( defaultselhash );
5504 // Add the JOIN clauses, if any
5506 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5507 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5508 OSRF_BUFFER_ADD( sql_buf, join_clause );
5509 free( join_clause );
5512 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5513 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5515 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5517 // Add the conditions in the WHERE clause
5518 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5520 osrfAppSessionStatus(
5522 OSRF_STATUS_INTERNALSERVERERROR,
5523 "osrfMethodException",
5525 "Severe query error -- see error log for more details"
5527 buffer_free( sql_buf );
5528 if( defaultselhash )
5529 jsonObjectFree( defaultselhash );
5530 clear_query_stack();
5533 buffer_add( sql_buf, pred );
5537 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5538 if( rest_of_query ) {
5539 const jsonObject* order_by = NULL;
5540 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5542 char* order_by_list = NULL;
5544 if( JSON_ARRAY == order_by->type ) {
5545 order_by_list = buildOrderByFromArray( ctx, order_by );
5546 if( !order_by_list ) {
5547 buffer_free( sql_buf );
5548 if( defaultselhash )
5549 jsonObjectFree( defaultselhash );
5550 clear_query_stack();
5553 } else if( JSON_HASH == order_by->type ) {
5554 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5555 // and build a list of ORDER BY expressions.
5556 growing_buffer* order_buf = buffer_init( 128 );
5558 jsonIterator* class_itr = jsonNewIterator( order_by );
5559 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5561 ClassInfo* order_class_info = search_alias( class_itr->key );
5562 if( ! order_class_info )
5563 continue; // class not referenced by FROM clause? Ignore it.
5565 if( JSON_HASH == snode->type ) {
5567 // If the data for the current class is a JSON_HASH, then it is
5568 // keyed on field name.
5570 const jsonObject* onode = NULL;
5571 jsonIterator* order_itr = jsonNewIterator( snode );
5572 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5574 osrfHash* field_def = osrfHashGet(
5575 order_class_info->fields, order_itr->key );
5577 continue; // Field not defined in IDL? Ignore it.
5578 if( str_is_true( osrfHashGet( field_def, "virtual")))
5579 continue; // Field is virtual? Ignore it.
5581 char* field_str = NULL;
5582 char* direction = NULL;
5583 if( onode->type == JSON_HASH ) {
5584 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5585 field_str = searchFieldTransform(
5586 class_itr->key, field_def, onode );
5588 osrfAppSessionStatus(
5590 OSRF_STATUS_INTERNALSERVERERROR,
5591 "osrfMethodException",
5593 "Severe query error in ORDER BY clause -- "
5594 "see error log for more details"
5596 jsonIteratorFree( order_itr );
5597 jsonIteratorFree( class_itr );
5598 buffer_free( order_buf );
5599 buffer_free( sql_buf );
5600 if( defaultselhash )
5601 jsonObjectFree( defaultselhash );
5602 clear_query_stack();
5606 growing_buffer* field_buf = buffer_init( 16 );
5607 buffer_fadd( field_buf, "\"%s\".%s",
5608 class_itr->key, order_itr->key );
5609 field_str = buffer_release( field_buf );
5612 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5613 const char* dir = jsonObjectGetString( order_by );
5614 if(!strncasecmp( dir, "d", 1 )) {
5615 direction = " DESC";
5619 field_str = strdup( order_itr->key );
5620 const char* dir = jsonObjectGetString( onode );
5621 if( !strncasecmp( dir, "d", 1 )) {
5622 direction = " DESC";
5631 buffer_add( order_buf, ", " );
5634 buffer_add( order_buf, field_str );
5638 buffer_add( order_buf, direction );
5640 } // end while; looping over ORDER BY expressions
5642 jsonIteratorFree( order_itr );
5644 } else if( JSON_STRING == snode->type ) {
5645 // We expect a comma-separated list of sort fields.
5646 const char* str = jsonObjectGetString( snode );
5647 if( strchr( str, ';' )) {
5648 // No semicolons allowed. It is theoretically possible for a
5649 // legitimate semicolon to occur within quotes, but it's not likely
5650 // to occur in practice in the context of an ORDER BY list.
5651 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5652 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5654 osrfAppSessionStatus(
5656 OSRF_STATUS_INTERNALSERVERERROR,
5657 "osrfMethodException",
5659 "Possible attempt at SOL injection -- "
5660 "semicolon found in ORDER BY list"
5663 jsonIteratorFree( class_itr );
5664 buffer_free( order_buf );
5665 buffer_free( sql_buf );
5666 if( defaultselhash )
5667 jsonObjectFree( defaultselhash );
5668 clear_query_stack();
5671 buffer_add( order_buf, str );
5675 } // end while; looping over order_by classes
5677 jsonIteratorFree( class_itr );
5678 order_by_list = buffer_release( order_buf );
5681 osrfLogWarning( OSRF_LOG_MARK,
5682 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5683 "no ORDER BY generated" );
5686 if( order_by_list && *order_by_list ) {
5687 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5688 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5691 free( order_by_list );
5694 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5696 const char* str = jsonObjectGetString( limit );
5706 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5708 const char* str = jsonObjectGetString( offset );
5719 if( defaultselhash )
5720 jsonObjectFree( defaultselhash );
5721 clear_query_stack();
5723 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5724 return buffer_release( sql_buf );
5727 int doJSONSearch ( osrfMethodContext* ctx ) {
5728 if(osrfMethodVerifyContext( ctx )) {
5729 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5733 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5737 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5741 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5742 flags |= SELECT_DISTINCT;
5744 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5745 flags |= DISABLE_I18N;
5747 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5748 clear_query_stack(); // a possibly needless precaution
5749 char* sql = buildQuery( ctx, hash, flags );
5750 clear_query_stack();
5757 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5760 dbhandle = writehandle;
5762 dbi_result result = dbi_conn_query( dbhandle, sql );
5765 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5767 if( dbi_result_first_row( result )) {
5768 /* JSONify the result */
5769 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5772 jsonObject* return_val = oilsMakeJSONFromResult( result );
5773 osrfAppRespond( ctx, return_val );
5774 jsonObjectFree( return_val );
5775 } while( dbi_result_next_row( result ));
5778 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5781 osrfAppRespondComplete( ctx, NULL );
5783 /* clean up the query */
5784 dbi_result_free( result );
5789 int errnum = dbi_conn_error( dbhandle, &msg );
5790 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5791 modulename, sql, errnum, msg ? msg : "(No description available)" );
5792 osrfAppSessionStatus(
5794 OSRF_STATUS_INTERNALSERVERERROR,
5795 "osrfMethodException",
5797 "Severe query error -- see error log for more details"
5799 if( !oilsIsDBConnected( dbhandle ))
5800 osrfAppSessionPanic( ctx->session );
5807 // The last parameter, err, is used to report an error condition by updating an int owned by
5808 // the calling code.
5810 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5811 // It is the responsibility of the calling code to initialize *err before the
5812 // call, so that it will be able to make sense of the result.
5814 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5815 // redundant anyway.
5816 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5817 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5820 dbhandle = writehandle;
5822 char* core_class = osrfHashGet( class_meta, "classname" );
5823 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5825 char* pkey = osrfHashGet( class_meta, "primarykey" );
5827 if (!ctx->session->userData)
5828 (void) initSessionCache( ctx );
5830 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5831 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5832 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5834 int i_respond_directly = 0;
5835 int flesh_depth = 0;
5837 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5839 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5844 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5846 dbi_result result = dbi_conn_query( dbhandle, sql );
5847 if( NULL == result ) {
5849 int errnum = dbi_conn_error( dbhandle, &msg );
5850 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5851 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5852 msg ? msg : "(No description available)" );
5853 if( !oilsIsDBConnected( dbhandle ))
5854 osrfAppSessionPanic( ctx->session );
5855 osrfAppSessionStatus(
5857 OSRF_STATUS_INTERNALSERVERERROR,
5858 "osrfMethodException",
5860 "Severe query error -- see error log for more details"
5867 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5870 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5871 jsonObject* row_obj = NULL;
5873 // The following two steps are for verifyObjectPCRUD()'s benefit.
5874 // 1. get the flesh depth
5875 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5877 flesh_depth = (int) jsonObjectGetNumber( _tmp );
5878 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5879 flesh_depth = max_flesh_depth;
5882 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
5883 // over the whole life of this request. This means if we've already set
5884 // up a rs_size_req_%d, do nothing.
5885 // a. Incidentally, we can also use this opportunity to set i_respond_directly
5886 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
5887 if( !rs_size ) { // pointer null, so value not set in hash
5888 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
5889 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
5891 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
5892 unsigned long long result_count = dbi_result_get_numrows( result );
5893 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
5894 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
5897 if( dbi_result_first_row( result )) {
5899 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5900 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5901 // eliminate the duplicates.
5902 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5903 osrfHash* dedup = osrfNewHash();
5905 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5906 char* pkey_val = oilsFMGetString( row_obj, pkey );
5907 if( osrfHashGet( dedup, pkey_val ) ) {
5908 jsonObjectFree( row_obj );
5911 if( !enforce_pcrud || !need_to_verify ||
5912 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
5913 osrfHashSet( dedup, pkey_val, pkey_val );
5914 jsonObjectPush( res_list, row_obj );
5917 } while( dbi_result_next_row( result ));
5918 osrfHashFree( dedup );
5921 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5925 /* clean up the query */
5926 dbi_result_free( result );
5929 // If we're asked to flesh, and there's anything to flesh, then flesh it
5930 // (formerly we would skip fleshing if in pcrud mode, but now we support
5931 // fleshing even in PCRUD).
5932 if( res_list->size ) {
5933 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
5934 jsonObject* flesh_fields;
5935 jsonObject* flesh_blob = NULL;
5936 osrfStringArray* link_fields = NULL;
5937 osrfHash* links = NULL;
5941 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
5942 if( temp_blob && flesh_depth > 0 ) {
5944 flesh_blob = jsonObjectClone( temp_blob );
5945 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
5947 links = osrfHashGet( class_meta, "links" );
5949 // Make an osrfStringArray of the names of fields to be fleshed
5950 if( flesh_fields ) {
5951 if( flesh_fields->size == 1 ) {
5952 const char* _t = jsonObjectGetString(
5953 jsonObjectGetIndex( flesh_fields, 0 ) );
5954 if( !strcmp( _t, "*" ))
5955 link_fields = osrfHashKeys( links );
5958 if( !link_fields ) {
5960 link_fields = osrfNewStringArray( 1 );
5961 jsonIterator* _i = jsonNewIterator( flesh_fields );
5962 while ((_f = jsonIteratorNext( _i ))) {
5963 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5965 jsonIteratorFree( _i );
5968 want_flesh = link_fields ? 1 : 0;
5972 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5974 // Iterate over the JSON_ARRAY of rows
5976 unsigned long res_idx = 0;
5977 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5980 const char* link_field;
5982 // Iterate over the list of fleshable fields
5984 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5986 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5988 osrfHash* kid_link = osrfHashGet( links, link_field );
5990 continue; // Not a link field; skip it
5992 osrfHash* field = osrfHashGet( fields, link_field );
5994 continue; // Not a field at all; skip it (IDL is ill-formed)
5996 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5997 osrfHashGet( kid_link, "class" ));
5999 continue; // The class it links to doesn't exist; skip it
6001 const char* reltype = osrfHashGet( kid_link, "reltype" );
6003 continue; // No reltype; skip it (IDL is ill-formed)
6005 osrfHash* value_field = field;
6007 if( !strcmp( reltype, "has_many" )
6008 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
6009 value_field = osrfHashGet(
6010 fields, osrfHashGet( class_meta, "primarykey" ) );
6013 int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
6014 // fleshing pcrud case: we require the controller in need_to_verify mode
6015 if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
6016 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
6020 (unsigned long) atoi( osrfHashGet(field, "array_position") ),
6022 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
6028 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
6030 if( link_map->size > 0 ) {
6031 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
6034 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
6039 osrfHashGet( kid_link, "class" ),
6046 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
6047 osrfHashGet( kid_link, "field" ),
6048 osrfHashGet( kid_link, "class" ),
6049 osrfHashGet( kid_link, "key" ),
6050 osrfHashGet( kid_link, "reltype" )
6053 const char* search_key = jsonObjectGetString(
6054 jsonObjectGetIndex( cur,
6055 atoi( osrfHashGet( value_field, "array_position" ) )
6060 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
6064 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
6066 // construct WHERE clause
6067 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
6070 osrfHashGet( kid_link, "key" ),
6071 jsonNewObject( search_key )
6074 // construct the rest of the query, mostly
6075 // by copying pieces of the previous level of query
6076 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
6077 jsonObjectSetKey( rest_of_query, "flesh",
6078 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
6082 jsonObjectSetKey( rest_of_query, "flesh_fields",
6083 jsonObjectClone( flesh_blob ));
6085 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
6086 jsonObjectSetKey( rest_of_query, "order_by",
6087 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
6091 if( jsonObjectGetKeyConst( query_hash, "select" )) {
6092 jsonObjectSetKey( rest_of_query, "select",
6093 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
6097 // do the query, recursively, to expand the fleshable field
6098 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
6099 where_clause, rest_of_query, err );
6101 jsonObjectFree( where_clause );
6102 jsonObjectFree( rest_of_query );
6105 osrfStringArrayFree( link_fields );
6106 jsonObjectFree( res_list );
6107 jsonObjectFree( flesh_blob );
6111 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
6112 osrfHashGet( kid_link, "class" ), kids->size );
6114 // Traverse the result set
6115 jsonObject* X = NULL;
6116 if( link_map->size > 0 && kids->size > 0 ) {
6118 kids = jsonNewObjectType( JSON_ARRAY );
6120 jsonObject* _k_node;
6121 unsigned long res_idx = 0;
6122 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
6128 (unsigned long) atoi(
6134 osrfHashGet( kid_link, "class" )
6138 osrfStringArrayGetString( link_map, 0 )
6146 } // end while loop traversing X
6149 if (kids->size > 0) {
6151 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
6152 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
6154 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
6155 osrfHashGet( kid_link, "field" ));
6158 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6159 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
6164 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
6166 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
6167 osrfHashGet( kid_link, "field" ) );
6170 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6171 jsonObjectClone( kids )
6176 jsonObjectFree( kids );
6180 jsonObjectFree( kids );
6182 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
6183 osrfHashGet( kid_link, "field" ) );
6184 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
6186 } // end while loop traversing list of fleshable fields
6189 if( i_respond_directly ) {
6190 if ( *methodtype == 'i' ) {
6191 osrfAppRespond( ctx,
6192 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
6194 osrfAppRespond( ctx, cur );
6197 } // end while loop traversing res_list
6198 jsonObjectFree( flesh_blob );
6199 osrfStringArrayFree( link_fields );
6202 if( i_respond_directly ) {
6203 jsonObjectFree( res_list );
6204 return jsonNewObjectType( JSON_ARRAY );
6211 int doUpdate( osrfMethodContext* ctx ) {
6212 if( osrfMethodVerifyContext( ctx )) {
6213 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6218 timeout_needs_resetting = 1;
6220 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6222 jsonObject* target = NULL;
6224 target = jsonObjectGetIndex( ctx->params, 1 );
6226 target = jsonObjectGetIndex( ctx->params, 0 );
6228 if(!verifyObjectClass( ctx, target )) {
6229 osrfAppRespondComplete( ctx, NULL );
6233 if( getXactId( ctx ) == NULL ) {
6234 osrfAppSessionStatus(
6236 OSRF_STATUS_BADREQUEST,
6237 "osrfMethodException",
6239 "No active transaction -- required for UPDATE"
6241 osrfAppRespondComplete( ctx, NULL );
6245 // The following test is harmless but redundant. If a class is
6246 // readonly, we don't register an update method for it.
6247 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6248 osrfAppSessionStatus(
6250 OSRF_STATUS_BADREQUEST,
6251 "osrfMethodException",
6253 "Cannot UPDATE readonly class"
6255 osrfAppRespondComplete( ctx, NULL );
6259 const char* trans_id = getXactId( ctx );
6261 // Set the last_xact_id
6262 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6264 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6265 trans_id, target->classname, index );
6266 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6269 char* pkey = osrfHashGet( meta, "primarykey" );
6270 osrfHash* fields = osrfHashGet( meta, "fields" );
6272 char* id = oilsFMGetString( target, pkey );
6276 "%s updating %s object with %s = %s",
6278 osrfHashGet( meta, "fieldmapper" ),
6283 dbhandle = writehandle;
6284 growing_buffer* sql = buffer_init( 128 );
6285 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6288 osrfHash* field_def = NULL;
6289 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6290 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6292 // Skip virtual fields, and the primary key
6293 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6296 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6300 const char* field_name = osrfHashIteratorKey( field_itr );
6301 if( ! strcmp( field_name, pkey ) )
6304 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6306 int value_is_numeric = 0; // boolean
6308 if( field_object && field_object->classname ) {
6309 value = oilsFMGetString(
6311 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6313 } else if( field_object && JSON_BOOL == field_object->type ) {
6314 if( jsonBoolIsTrue( field_object ) )
6315 value = strdup( "t" );
6317 value = strdup( "f" );
6319 value = jsonObjectToSimpleString( field_object );
6320 if( field_object && JSON_NUMBER == field_object->type )
6321 value_is_numeric = 1;
6324 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6325 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6327 if( !field_object || field_object->type == JSON_NULL ) {
6328 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6329 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6333 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6334 buffer_fadd( sql, " %s = NULL", field_name );
6337 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6341 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6343 const char* numtype = get_datatype( field_def );
6344 if( !strncmp( numtype, "INT", 3 ) ) {
6345 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6346 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6347 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6349 // Must really be intended as a string, so quote it
6350 if( dbi_conn_quote_string( dbhandle, &value )) {
6351 buffer_fadd( sql, " %s = %s", field_name, value );
6353 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6354 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 );
6371 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6374 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6378 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6379 buffer_fadd( sql, " %s = %s", field_name, value );
6381 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6382 osrfAppSessionStatus(
6384 OSRF_STATUS_INTERNALSERVERERROR,
6385 "osrfMethodException",
6387 "Error quoting string -- please see the error log for more details"
6391 osrfHashIteratorFree( field_itr );
6393 osrfAppRespondComplete( ctx, NULL );
6402 osrfHashIteratorFree( field_itr );
6404 jsonObject* obj = jsonNewObject( id );
6406 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6407 dbi_conn_quote_string( dbhandle, &id );
6409 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6411 char* query = buffer_release( sql );
6412 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6414 dbi_result result = dbi_conn_query( dbhandle, query );
6419 jsonObjectFree( obj );
6420 obj = jsonNewObject( NULL );
6422 int errnum = dbi_conn_error( dbhandle, &msg );
6425 "%s ERROR updating %s object with %s = %s: %d %s",
6427 osrfHashGet( meta, "fieldmapper" ),
6431 msg ? msg : "(No description available)"
6433 osrfAppSessionStatus(
6435 OSRF_STATUS_INTERNALSERVERERROR,
6436 "osrfMethodException",
6438 "Error in updating a row -- please see the error log for more details"
6440 if( !oilsIsDBConnected( dbhandle ))
6441 osrfAppSessionPanic( ctx->session );
6444 dbi_result_free( result );
6447 osrfAppRespondComplete( ctx, obj );
6448 jsonObjectFree( obj );
6452 int doDelete( osrfMethodContext* ctx ) {
6453 if( osrfMethodVerifyContext( ctx )) {
6454 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6459 timeout_needs_resetting = 1;
6461 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6463 if( getXactId( ctx ) == NULL ) {
6464 osrfAppSessionStatus(
6466 OSRF_STATUS_BADREQUEST,
6467 "osrfMethodException",
6469 "No active transaction -- required for DELETE"
6471 osrfAppRespondComplete( ctx, NULL );
6475 // The following test is harmless but redundant. If a class is
6476 // readonly, we don't register a delete method for it.
6477 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6478 osrfAppSessionStatus(
6480 OSRF_STATUS_BADREQUEST,
6481 "osrfMethodException",
6483 "Cannot DELETE readonly class"
6485 osrfAppRespondComplete( ctx, NULL );
6489 dbhandle = writehandle;
6491 char* pkey = osrfHashGet( meta, "primarykey" );
6498 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6499 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6500 osrfAppRespondComplete( ctx, NULL );
6504 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6506 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6507 osrfAppRespondComplete( ctx, NULL );
6510 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6515 "%s deleting %s object with %s = %s",
6517 osrfHashGet( meta, "fieldmapper" ),
6522 jsonObject* obj = jsonNewObject( id );
6524 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6525 dbi_conn_quote_string( writehandle, &id );
6527 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6528 osrfHashGet( meta, "tablename" ), pkey, id );
6533 jsonObjectFree( obj );
6534 obj = jsonNewObject( NULL );
6536 int errnum = dbi_conn_error( writehandle, &msg );
6539 "%s ERROR deleting %s object with %s = %s: %d %s",
6541 osrfHashGet( meta, "fieldmapper" ),
6545 msg ? msg : "(No description available)"
6547 osrfAppSessionStatus(
6549 OSRF_STATUS_INTERNALSERVERERROR,
6550 "osrfMethodException",
6552 "Error in deleting a row -- please see the error log for more details"
6554 if( !oilsIsDBConnected( writehandle ))
6555 osrfAppSessionPanic( ctx->session );
6557 dbi_result_free( result );
6561 osrfAppRespondComplete( ctx, obj );
6562 jsonObjectFree( obj );
6567 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6568 @param result An iterator for a result set; we only look at the current row.
6569 @param @meta Pointer to the class metadata for the core class.
6570 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6572 If a column is not defined in the IDL, or if it has no array_position defined for it in
6573 the IDL, or if it is defined as virtual, ignore it.
6575 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6576 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6577 array_position in the IDL.
6579 A field defined in the IDL but not represented in the returned row will leave a hole
6580 in the JSON_ARRAY. In effect it will be treated as a null value.
6582 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6583 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6584 classname corresponding to the @a meta argument.
6586 The calling code is responsible for freeing the the resulting jsonObject by calling
6589 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6590 if( !( result && meta )) return NULL;
6592 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6593 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6594 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6596 osrfHash* fields = osrfHashGet( meta, "fields" );
6598 int columnIndex = 1;
6599 const char* columnName;
6601 /* cycle through the columns in the row returned from the database */
6602 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6604 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6606 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6608 /* determine the field type and storage attributes */
6609 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6610 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6612 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6613 // or if it has no sequence number there, or if it's virtual, skip it.
6614 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6617 if( str_is_true( osrfHashGet( _f, "virtual" )))
6618 continue; // skip this column: IDL says it's virtual
6620 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6621 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6622 continue; // since we assign sequence numbers dynamically as we load the IDL.
6624 fmIndex = atoi( pos );
6625 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6627 continue; // This field is not defined in the IDL
6630 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6631 // sequence number from the IDL (which is likely to be different from the sequence
6632 // of columns in the SELECT clause).
6633 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6634 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6639 case DBI_TYPE_INTEGER :
6641 if( attr & DBI_INTEGER_SIZE8 )
6642 jsonObjectSetIndex( object, fmIndex,
6643 jsonNewNumberObject(
6644 dbi_result_get_longlong_idx( result, columnIndex )));
6646 jsonObjectSetIndex( object, fmIndex,
6647 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6651 case DBI_TYPE_DECIMAL :
6652 jsonObjectSetIndex( object, fmIndex,
6653 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6656 case DBI_TYPE_STRING :
6661 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6666 case DBI_TYPE_DATETIME : {
6668 char dt_string[ 256 ] = "";
6671 // Fetch the date column as a time_t
6672 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6674 // Translate the time_t to a human-readable string
6675 if( !( attr & DBI_DATETIME_DATE )) {
6676 gmtime_r( &_tmp_dt, &gmdt );
6677 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6678 } else if( !( attr & DBI_DATETIME_TIME )) {
6679 localtime_r( &_tmp_dt, &gmdt );
6680 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6682 localtime_r( &_tmp_dt, &gmdt );
6683 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6686 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6690 case DBI_TYPE_BINARY :
6691 osrfLogError( OSRF_LOG_MARK,
6692 "Can't do binary at column %s : index %d", columnName, columnIndex );
6701 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6702 if( !result ) return NULL;
6704 jsonObject* object = jsonNewObject( NULL );
6707 char dt_string[ 256 ];
6711 int columnIndex = 1;
6713 unsigned short type;
6714 const char* columnName;
6716 /* cycle through the column list */
6717 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6719 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6721 fmIndex = -1; // reset the position
6723 /* determine the field type and storage attributes */
6724 type = dbi_result_get_field_type_idx( result, columnIndex );
6725 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6727 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6728 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6733 case DBI_TYPE_INTEGER :
6735 if( attr & DBI_INTEGER_SIZE8 )
6736 jsonObjectSetKey( object, columnName,
6737 jsonNewNumberObject( dbi_result_get_longlong_idx(
6738 result, columnIndex )) );
6740 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6741 dbi_result_get_int_idx( result, columnIndex )) );
6744 case DBI_TYPE_DECIMAL :
6745 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6746 dbi_result_get_double_idx( result, columnIndex )) );
6749 case DBI_TYPE_STRING :
6750 jsonObjectSetKey( object, columnName,
6751 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6754 case DBI_TYPE_DATETIME :
6756 memset( dt_string, '\0', sizeof( dt_string ));
6757 memset( &gmdt, '\0', sizeof( gmdt ));
6759 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6761 if( !( attr & DBI_DATETIME_DATE )) {
6762 gmtime_r( &_tmp_dt, &gmdt );
6763 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6764 } else if( !( attr & DBI_DATETIME_TIME )) {
6765 localtime_r( &_tmp_dt, &gmdt );
6766 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6768 localtime_r( &_tmp_dt, &gmdt );
6769 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6772 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6775 case DBI_TYPE_BINARY :
6776 osrfLogError( OSRF_LOG_MARK,
6777 "Can't do binary at column %s : index %d", columnName, columnIndex );
6781 } // end while loop traversing result
6786 // Interpret a string as true or false
6787 int str_is_true( const char* str ) {
6788 if( NULL == str || strcasecmp( str, "true" ) )
6794 // Interpret a jsonObject as true or false
6795 static int obj_is_true( const jsonObject* obj ) {
6798 else switch( obj->type )
6806 if( strcasecmp( obj->value.s, "true" ) )
6810 case JSON_NUMBER : // Support 1/0 for perl's sake
6811 if( jsonObjectGetNumber( obj ) == 1.0 )
6820 // Translate a numeric code into a text string identifying a type of
6821 // jsonObject. To be used for building error messages.
6822 static const char* json_type( int code ) {
6828 return "JSON_ARRAY";
6830 return "JSON_STRING";
6832 return "JSON_NUMBER";
6838 return "(unrecognized)";
6842 // Extract the "primitive" attribute from an IDL field definition.
6843 // If we haven't initialized the app, then we must be running in
6844 // some kind of testbed. In that case, default to "string".
6845 static const char* get_primitive( osrfHash* field ) {
6846 const char* s = osrfHashGet( field, "primitive" );
6848 if( child_initialized )
6851 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6853 osrfHashGet( field, "name" )
6861 // Extract the "datatype" attribute from an IDL field definition.
6862 // If we haven't initialized the app, then we must be running in
6863 // some kind of testbed. In that case, default to to NUMERIC,
6864 // since we look at the datatype only for numbers.
6865 static const char* get_datatype( osrfHash* field ) {
6866 const char* s = osrfHashGet( field, "datatype" );
6868 if( child_initialized )
6871 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6873 osrfHashGet( field, "name" )
6882 @brief Determine whether a string is potentially a valid SQL identifier.
6883 @param s The identifier to be tested.
6884 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6886 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6887 need to follow all the rules exactly, such as requiring that the first character not
6890 We allow leading and trailing white space. In between, we do not allow punctuation
6891 (except for underscores and dollar signs), control characters, or embedded white space.
6893 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6894 for the foreseeable future such quoted identifiers are not likely to be an issue.
6896 int is_identifier( const char* s) {
6900 // Skip leading white space
6901 while( isspace( (unsigned char) *s ) )
6905 return 0; // Nothing but white space? Not okay.
6907 // Check each character until we reach white space or
6908 // end-of-string. Letters, digits, underscores, and
6909 // dollar signs are okay. With the exception of periods
6910 // (as in schema.identifier), control characters and other
6911 // punctuation characters are not okay. Anything else
6912 // is okay -- it could for example be part of a multibyte
6913 // UTF8 character such as a letter with diacritical marks,
6914 // and those are allowed.
6916 if( isalnum( (unsigned char) *s )
6920 ; // Fine; keep going
6921 else if( ispunct( (unsigned char) *s )
6922 || iscntrl( (unsigned char) *s ) )
6925 } while( *s && ! isspace( (unsigned char) *s ) );
6927 // If we found any white space in the above loop,
6928 // the rest had better be all white space.
6930 while( isspace( (unsigned char) *s ) )
6934 return 0; // White space was embedded within non-white space
6940 @brief Determine whether to accept a character string as a comparison operator.
6941 @param op The candidate comparison operator.
6942 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6944 We don't validate the operator for real. We just make sure that it doesn't contain
6945 any semicolons or white space (with special exceptions for a few specific operators).
6946 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6947 space but it's still not a valid operator, then the database will complain.
6949 Another approach would be to compare the string against a short list of approved operators.
6950 We don't do that because we want to allow custom operators like ">100*", which at this
6951 writing would be difficult or impossible to express otherwise in a JSON query.
6953 int is_good_operator( const char* op ) {
6954 if( !op ) return 0; // Sanity check
6958 if( isspace( (unsigned char) *s ) ) {
6959 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6960 // and IS NOT DISTINCT FROM.
6961 if( !strcasecmp( op, "similar to" ) )
6963 else if( !strcasecmp( op, "is distinct from" ) )
6965 else if( !strcasecmp( op, "is not distinct from" ) )
6970 else if( ';' == *s )
6978 @name Query Frame Management
6980 The following machinery supports a stack of query frames for use by SELECT().
6982 A query frame caches information about one level of a SELECT query. When we enter
6983 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6985 The query frame stores information about the core class, and about any joined classes
6988 The main purpose is to map table aliases to classes and tables, so that a query can
6989 join to the same table more than once. A secondary goal is to reduce the number of
6990 lookups in the IDL by caching the results.
6994 #define STATIC_CLASS_INFO_COUNT 3
6996 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6999 @brief Allocate a ClassInfo as raw memory.
7000 @return Pointer to the newly allocated ClassInfo.
7002 Except for the in_use flag, which is used only by the allocation and deallocation
7003 logic, we don't initialize the ClassInfo here.
7005 static ClassInfo* allocate_class_info( void ) {
7006 // In order to reduce the number of mallocs and frees, we return a static
7007 // instance of ClassInfo, if we can find one that we're not already using.
7008 // We rely on the fact that the compiler will implicitly initialize the
7009 // static instances so that in_use == 0.
7012 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7013 if( ! static_class_info[ i ].in_use ) {
7014 static_class_info[ i ].in_use = 1;
7015 return static_class_info + i;
7019 // The static ones are all in use. Malloc one.
7021 return safe_malloc( sizeof( ClassInfo ) );
7025 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
7026 @param info Pointer to the ClassInfo to be cleared.
7028 static void clear_class_info( ClassInfo* info ) {
7033 // Free any malloc'd strings
7035 if( info->alias != info->alias_store )
7036 free( info->alias );
7038 if( info->class_name != info->class_name_store )
7039 free( info->class_name );
7041 free( info->source_def );
7043 info->alias = info->class_name = info->source_def = NULL;
7048 @brief Free a ClassInfo and everything it owns.
7049 @param info Pointer to the ClassInfo to be freed.
7051 static void free_class_info( ClassInfo* info ) {
7056 clear_class_info( info );
7058 // If it's one of the static instances, just mark it as not in use
7061 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7062 if( info == static_class_info + i ) {
7063 static_class_info[ i ].in_use = 0;
7068 // Otherwise it must have been malloc'd, so free it
7074 @brief Populate an already-allocated ClassInfo.
7075 @param info Pointer to the ClassInfo to be populated.
7076 @param alias Alias for the class. If it is NULL, or an empty string, use the class
7078 @param class Name of the class.
7079 @return Zero if successful, or 1 if not.
7081 Populate the ClassInfo with copies of the alias and class name, and with pointers to
7082 the relevant portions of the IDL for the specified class.
7084 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
7087 osrfLogError( OSRF_LOG_MARK,
7088 "%s ERROR: No ClassInfo available to populate", modulename );
7089 info->alias = info->class_name = info->source_def = NULL;
7090 info->class_def = info->fields = info->links = NULL;
7095 osrfLogError( OSRF_LOG_MARK,
7096 "%s ERROR: No class name provided for lookup", modulename );
7097 info->alias = info->class_name = info->source_def = NULL;
7098 info->class_def = info->fields = info->links = NULL;
7102 // Alias defaults to class name if not supplied
7103 if( ! alias || ! alias[ 0 ] )
7106 // Look up class info in the IDL
7107 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
7109 osrfLogError( OSRF_LOG_MARK,
7110 "%s ERROR: Class %s not defined in IDL", modulename, class );
7111 info->alias = info->class_name = info->source_def = NULL;
7112 info->class_def = info->fields = info->links = NULL;
7114 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
7115 osrfLogError( OSRF_LOG_MARK,
7116 "%s ERROR: Class %s is defined as virtual", modulename, class );
7117 info->alias = info->class_name = info->source_def = NULL;
7118 info->class_def = info->fields = info->links = NULL;
7122 osrfHash* links = osrfHashGet( class_def, "links" );
7124 osrfLogError( OSRF_LOG_MARK,
7125 "%s ERROR: No links defined in IDL for class %s", modulename, class );
7126 info->alias = info->class_name = info->source_def = NULL;
7127 info->class_def = info->fields = info->links = NULL;
7131 osrfHash* fields = osrfHashGet( class_def, "fields" );
7133 osrfLogError( OSRF_LOG_MARK,
7134 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
7135 info->alias = info->class_name = info->source_def = NULL;
7136 info->class_def = info->fields = info->links = NULL;
7140 char* source_def = oilsGetRelation( class_def );
7144 // We got everything we need, so populate the ClassInfo
7145 if( strlen( alias ) > ALIAS_STORE_SIZE )
7146 info->alias = strdup( alias );
7148 strcpy( info->alias_store, alias );
7149 info->alias = info->alias_store;
7152 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
7153 info->class_name = strdup( class );
7155 strcpy( info->class_name_store, class );
7156 info->class_name = info->class_name_store;
7159 info->source_def = source_def;
7161 info->class_def = class_def;
7162 info->links = links;
7163 info->fields = fields;
7168 #define STATIC_FRAME_COUNT 3
7170 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
7173 @brief Allocate a QueryFrame as raw memory.
7174 @return Pointer to the newly allocated QueryFrame.
7176 Except for the in_use flag, which is used only by the allocation and deallocation
7177 logic, we don't initialize the QueryFrame here.
7179 static QueryFrame* allocate_frame( void ) {
7180 // In order to reduce the number of mallocs and frees, we return a static
7181 // instance of QueryFrame, if we can find one that we're not already using.
7182 // We rely on the fact that the compiler will implicitly initialize the
7183 // static instances so that in_use == 0.
7186 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7187 if( ! static_frame[ i ].in_use ) {
7188 static_frame[ i ].in_use = 1;
7189 return static_frame + i;
7193 // The static ones are all in use. Malloc one.
7195 return safe_malloc( sizeof( QueryFrame ) );
7199 @brief Free a QueryFrame, and all the memory it owns.
7200 @param frame Pointer to the QueryFrame to be freed.
7202 static void free_query_frame( QueryFrame* frame ) {
7207 clear_class_info( &frame->core );
7209 // Free the join list
7211 ClassInfo* info = frame->join_list;
7214 free_class_info( info );
7218 frame->join_list = NULL;
7221 // If the frame is a static instance, just mark it as unused
7223 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7224 if( frame == static_frame + i ) {
7225 static_frame[ i ].in_use = 0;
7230 // Otherwise it must have been malloc'd, so free it
7236 @brief Search a given QueryFrame for a specified alias.
7237 @param frame Pointer to the QueryFrame to be searched.
7238 @param target The alias for which to search.
7239 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7241 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7242 if( ! frame || ! target ) {
7246 ClassInfo* found_class = NULL;
7248 if( !strcmp( target, frame->core.alias ) )
7249 return &(frame->core);
7251 ClassInfo* curr_class = frame->join_list;
7252 while( curr_class ) {
7253 if( strcmp( target, curr_class->alias ) )
7254 curr_class = curr_class->next;
7256 found_class = curr_class;
7266 @brief Push a new (blank) QueryFrame onto the stack.
7268 static void push_query_frame( void ) {
7269 QueryFrame* frame = allocate_frame();
7270 frame->join_list = NULL;
7271 frame->next = curr_query;
7273 // Initialize the ClassInfo for the core class
7274 ClassInfo* core = &frame->core;
7275 core->alias = core->class_name = core->source_def = NULL;
7276 core->class_def = core->fields = core->links = NULL;
7282 @brief Pop a QueryFrame off the stack and destroy it.
7284 static void pop_query_frame( void ) {
7289 QueryFrame* popped = curr_query;
7290 curr_query = popped->next;
7292 free_query_frame( popped );
7296 @brief Populate the ClassInfo for the core class.
7297 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7298 class name as an alias.
7299 @param class_name Name of the core class.
7300 @return Zero if successful, or 1 if not.
7302 Populate the ClassInfo of the core class with copies of the alias and class name, and
7303 with pointers to the relevant portions of the IDL for the core class.
7305 static int add_query_core( const char* alias, const char* class_name ) {
7308 if( ! curr_query ) {
7309 osrfLogError( OSRF_LOG_MARK,
7310 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7312 } else if( curr_query->core.alias ) {
7313 osrfLogError( OSRF_LOG_MARK,
7314 "%s ERROR: Core class %s already populated as %s",
7315 modulename, curr_query->core.class_name, curr_query->core.alias );
7319 build_class_info( &curr_query->core, alias, class_name );
7320 if( curr_query->core.alias )
7323 osrfLogError( OSRF_LOG_MARK,
7324 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7330 @brief Search the current QueryFrame for a specified alias.
7331 @param target The alias for which to search.
7332 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7334 static inline ClassInfo* search_alias( const char* target ) {
7335 return search_alias_in_frame( curr_query, target );
7339 @brief Search all levels of query for a specified alias, starting with the current query.
7340 @param target The alias for which to search.
7341 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7343 static ClassInfo* search_all_alias( const char* target ) {
7344 ClassInfo* found_class = NULL;
7345 QueryFrame* curr_frame = curr_query;
7347 while( curr_frame ) {
7348 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7351 curr_frame = curr_frame->next;
7358 @brief Add a class to the list of classes joined to the current query.
7359 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7360 the class name as an alias.
7361 @param classname The name of the class to be added.
7362 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7364 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7366 if( ! classname || ! *classname ) { // sanity check
7367 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7374 const ClassInfo* conflict = search_alias( alias );
7376 osrfLogError( OSRF_LOG_MARK,
7377 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7378 modulename, alias, conflict->class_name );
7382 ClassInfo* info = allocate_class_info();
7384 if( build_class_info( info, alias, classname ) ) {
7385 free_class_info( info );
7389 // Add the new ClassInfo to the join list of the current QueryFrame
7390 info->next = curr_query->join_list;
7391 curr_query->join_list = info;
7397 @brief Destroy all nodes on the query stack.
7399 static void clear_query_stack( void ) {
7405 @brief Implement the set_audit_info method.
7406 @param ctx Pointer to the method context.
7407 @return Zero if successful, or -1 if not.
7409 Issue a SAVEPOINT to the database server.
7414 - workstation id (int)
7416 If user id is not provided the authkey will be used.
7417 For PCRUD the authkey is always used, even if a user is provided.
7419 int setAuditInfo( osrfMethodContext* ctx ) {
7420 if(osrfMethodVerifyContext( ctx )) {
7421 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
7425 // Get the user id from the parameters
7426 const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7428 if( enforce_pcrud || !user_id ) {
7429 timeout_needs_resetting = 1;
7430 const jsonObject* user = verifyUserPCRUD( ctx );
7433 osrfAppRespondComplete( ctx, NULL );
7437 // Not PCRUD and have a user_id?
7438 int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7439 osrfAppRespondComplete( ctx, NULL );
7444 @brief Save a audit info
7445 @param ctx Pointer to the method context.
7446 @param user_id User ID to write as a string
7447 @param ws_id Workstation ID to write as a string
7449 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7450 if( ctx && ctx->session ) {
7451 osrfAppSession* session = ctx->session;
7453 osrfHash* cache = session->userData;
7455 // If the session doesn't already have a hash, create one. Make sure
7456 // that the application session frees the hash when it terminates.
7457 if( NULL == cache ) {
7458 session->userData = cache = osrfNewHash();
7459 osrfHashSetCallback( cache, &sessionDataFree );
7460 ctx->session->userDataFree = &userDataFree;
7463 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7465 osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7467 int errnum = dbi_conn_error( writehandle, &msg );
7470 "%s: Error setting auditor information: %d %s",
7473 msg ? msg : "(No description available)"
7475 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7476 "osrfMethodException", ctx->request, "Error setting auditor info" );
7477 if( !oilsIsDBConnected( writehandle ))
7478 osrfAppSessionPanic( ctx->session );
7481 dbi_result_free( result );
7488 @brief Remove all but safe character from savepoint name
7489 @param sp User-supplied savepoint name
7490 @return sanitized savepoint name, or NULL
7492 The caller is expected to free the returned string. Note that
7493 this function exists only because we can't use PQescapeLiteral
7494 without either forking libdbi or abandoning it.
7496 static char* _sanitize_savepoint_name( const char* sp ) {
7498 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_";
7500 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7501 // and the default value of NAMEDATALEN is 64; that should be long enough
7502 // for our purposes, and it's unlikely that anyone is going to recompile
7503 // PostgreSQL to have a smaller value, so cap the identifier name
7504 // accordingly to avoid the remote chance that someone manages to pass in a
7505 // 12GB savepoint name
7506 const int MAX_LITERAL_NAMELEN = 63;
7509 if (len > MAX_LITERAL_NAMELEN) {
7510 len = MAX_LITERAL_NAMELEN;
7513 char* safeSpName = safe_malloc( len + 1 );
7517 for (j = 0; j < len; j++) {
7518 found = strchr(safe_chars, sp[j]);
7520 safeSpName[ i++ ] = found[0];
7523 safeSpName[ i ] = '\0';