3 @brief Utility routines for translating JSON into SQL.
11 #include "opensrf/utils.h"
12 #include "opensrf/log.h"
13 #include "opensrf/osrf_application.h"
14 #include "openils/oils_utils.h"
15 #include "openils/oils_sql.h"
17 // The next four macros are OR'd together as needed to form a set
18 // of bitflags. SUBCOMBO enables an extra pair of parentheses when
19 // nesting one UNION, INTERSECT or EXCEPT inside another.
20 // SUBSELECT tells us we're in a subquery, so don't add the
21 // terminal semicolon yet.
24 #define DISABLE_I18N 2
25 #define SELECT_DISTINCT 1
30 struct ClassInfoStruct;
31 typedef struct ClassInfoStruct ClassInfo;
33 #define ALIAS_STORE_SIZE 16
34 #define CLASS_NAME_STORE_SIZE 16
36 struct ClassInfoStruct {
40 osrfHash* class_def; // Points into IDL
41 osrfHash* fields; // Points into IDL
42 osrfHash* links; // Points into IDL
44 // The remaining members are private and internal. Client code should not
45 // access them directly.
47 ClassInfo* next; // Supports linked list of joined classes
48 int in_use; // boolean
50 // We usually store the alias and class name in the following arrays, and
51 // point the corresponding pointers at them. When the string is too big
52 // for the array (which will probably never happen in practice), we strdup it.
54 char alias_store[ ALIAS_STORE_SIZE + 1 ];
55 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
58 struct QueryFrameStruct;
59 typedef struct QueryFrameStruct QueryFrame;
61 struct QueryFrameStruct {
63 ClassInfo* join_list; // linked list of classes joined to the core class
64 QueryFrame* next; // implements stack as linked list
65 int in_use; // boolean
68 static int timeout_needs_resetting;
69 static time_t time_next_reset;
71 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
73 static void setXactId( osrfMethodContext* ctx );
74 static inline const char* getXactId( osrfMethodContext* ctx );
75 static inline void clearXactId( osrfMethodContext* ctx );
77 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
78 jsonObject* where_hash, jsonObject* query_hash, int* err );
79 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
80 static jsonObject* oilsMakeJSONFromResult( dbi_result );
82 static char* searchSimplePredicate ( const char* op, const char* class_alias,
83 osrfHash* field, const jsonObject* node );
84 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
85 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject* );
86 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*,
88 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
89 static char* searchINPredicate ( const char*, osrfHash*,
90 jsonObject*, const char*, osrfMethodContext* );
91 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
92 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
93 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo*, int, osrfMethodContext* );
94 static char* buildSELECT( const jsonObject*, jsonObject* rest_of_query,
95 osrfHash* meta, osrfMethodContext* ctx );
96 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array );
98 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
100 char* SELECT ( osrfMethodContext*, jsonObject*, const jsonObject*, const jsonObject*,
101 const jsonObject*, const jsonObject*, const jsonObject*, const jsonObject*, int );
103 static osrfStringArray* getPermLocationCache( osrfMethodContext*, const char* );
104 static void setPermLocationCache( osrfMethodContext*, const char*, osrfStringArray* );
106 void userDataFree( void* );
107 static void sessionDataFree( char*, void* );
108 static void pcacheFree( char*, void* );
109 static int obj_is_true( const jsonObject* obj );
110 static const char* json_type( int code );
111 static const char* get_primitive( osrfHash* field );
112 static const char* get_datatype( osrfHash* field );
113 static void pop_query_frame( void );
114 static void push_query_frame( void );
115 static int add_query_core( const char* alias, const char* class_name );
116 static inline ClassInfo* search_alias( const char* target );
117 static ClassInfo* search_all_alias( const char* target );
118 static ClassInfo* add_joined_class( const char* alias, const char* classname );
119 static void clear_query_stack( void );
121 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
122 static const jsonObject* verifyUserPCRUDfull( osrfMethodContext*, int );
123 static int verifyObjectPCRUD( osrfMethodContext*, osrfHash*, const jsonObject*, int );
124 static const char* org_tree_root( osrfMethodContext* ctx );
125 static jsonObject* single_hash( const char* key, const char* value );
127 static int child_initialized = 0; /* boolean */
129 static dbi_conn writehandle; /* our MASTER db connection */
130 static dbi_conn dbhandle; /* our CURRENT db connection */
131 //static osrfHash * readHandles;
133 // The following points to the top of a stack of QueryFrames. It's a little
134 // confusing because the top level of the query is at the bottom of the stack.
135 static QueryFrame* curr_query = NULL;
137 static dbi_conn writehandle; /* our MASTER db connection */
138 static dbi_conn dbhandle; /* our CURRENT db connection */
139 //static osrfHash * readHandles;
141 static int max_flesh_depth = 100;
143 static int perm_at_threshold = 5;
144 static int enforce_pcrud = 0; // Boolean
145 static char* modulename = NULL;
147 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id);
149 static char* _sanitize_savepoint_name( const char* sp );
152 @brief Connect to the database.
153 @return A database connection if successful, or NULL if not.
155 dbi_conn oilsConnectDB( const char* mod_name ) {
157 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
158 if( dbi_initialize( NULL ) == -1 ) {
159 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
162 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
164 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
165 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
166 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
167 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
168 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
169 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
171 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
172 dbi_conn handle = dbi_conn_new( driver );
175 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
178 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
180 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
181 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
183 if( host ) dbi_conn_set_option( handle, "host", host );
184 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
185 if( user ) dbi_conn_set_option( handle, "username", user );
186 if( pw ) dbi_conn_set_option( handle, "password", pw );
187 if( db ) dbi_conn_set_option( handle, "dbname", db );
195 if( dbi_conn_connect( handle ) < 0 ) {
197 if( dbi_conn_connect( handle ) < 0 ) {
199 dbi_conn_error( handle, &msg );
200 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
201 msg ? msg : "(No description available)" );
206 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
212 @brief Select some options.
213 @param module_name: Name of the server.
214 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
216 This source file is used (at this writing) to implement three different servers:
217 - open-ils.reporter-store
221 These servers behave mostly the same, but they implement different combinations of
222 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
224 Here we use the server name in messages to identify which kind of server issued them.
225 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
227 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
229 module_name = "open-ils.cstore"; // bulletproofing with a default
234 modulename = strdup( module_name );
235 enforce_pcrud = do_pcrud;
236 max_flesh_depth = flesh_depth;
240 @brief Install a database connection.
241 @param conn Pointer to a database connection.
243 In some contexts, @a conn may merely provide a driver so that we can process strings
244 properly, without providing an open database connection.
246 void oilsSetDBConnection( dbi_conn conn ) {
247 dbhandle = writehandle = conn;
251 @brief Determine whether a database connection is alive.
252 @param handle Handle for a database connection.
253 @return 1 if the connection is alive, or zero if it isn't.
255 int oilsIsDBConnected( dbi_conn handle ) {
256 // Do an innocuous SELECT. If it succeeds, the database connection is still good.
257 dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
259 dbi_result_free( result );
262 // This is a terrible, horrible, no good, very bad kludge.
263 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
264 // but because (due to a previous error) the database is ignoring all commands,
265 // even innocuous SELECTs, until the current transaction is rolled back. The only
266 // known way to detect this condition via the dbi library is by looking at the error
267 // message. This approach will break if the language or wording of the message ever
269 // Note: the dbi_conn_ping function purports to determine whether the database
270 // connection is live, but at this writing this function is unreliable and useless.
271 static const char* ok_msg = "ERROR: current transaction is aborted, commands "
272 "ignored until end of transaction block\n";
274 dbi_conn_error( handle, &msg );
275 // Newer versions of dbi_conn_error return codes within the error msg.
276 // E.g. 3624914: ERROR: current transaction is aborted, commands ignored until end of transaction block
277 // Substring test should work regardless.
278 const char* substr = strstr(msg, ok_msg);
279 if( substr == NULL ) {
280 osrfLogError( OSRF_LOG_MARK, "Database connection isn't working : %s", msg );
283 return 1; // ignoring SELECT due to previous error; that's okay
288 @brief Get a table name, view name, or subquery for use in a FROM clause.
289 @param class Pointer to the IDL class entry.
290 @return A table name, a view name, or a subquery in parentheses.
292 In some cases the IDL defines a class, not with a table name or a view name, but with
293 a SELECT statement, which may be used as a subquery.
295 char* oilsGetRelation( osrfHash* classdef ) {
297 char* source_def = NULL;
298 const char* tabledef = osrfHashGet( classdef, "tablename" );
301 source_def = strdup( tabledef ); // Return the name of a table or view
303 tabledef = osrfHashGet( classdef, "source_definition" );
305 // Return a subquery, enclosed in parentheses
306 source_def = safe_malloc( strlen( tabledef ) + 3 );
307 source_def[ 0 ] = '(';
308 strcpy( source_def + 1, tabledef );
309 strcat( source_def, ")" );
311 // Not found: return an error
312 const char* classname = osrfHashGet( classdef, "classname" );
317 "%s ERROR No tablename or source_definition for class \"%s\"",
328 @brief Add datatypes from the database to the fields in the IDL.
329 @param handle Handle for a database connection
330 @return Zero if successful, or 1 upon error.
332 For each relevant class in the IDL: ask the database for the datatype of every field.
333 In particular, determine which fields are text fields and which fields are numeric
334 fields, so that we know whether to enclose their values in quotes.
336 int oilsExtendIDL( dbi_conn handle ) {
337 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
338 osrfHash* class = NULL;
339 growing_buffer* query_buf = buffer_init( 64 );
340 int results_found = 0; // boolean
342 // For each class in the IDL...
343 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
344 const char* classname = osrfHashIteratorKey( class_itr );
345 osrfHash* fields = osrfHashGet( class, "fields" );
347 // If the class is virtual, ignore it
348 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
349 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
353 char* tabledef = oilsGetRelation( class );
355 continue; // No such relation -- a query of it would be doomed to failure
357 buffer_reset( query_buf );
358 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
362 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
363 modulename, OSRF_BUFFER_C_STR( query_buf ) );
365 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
370 const char* columnName;
371 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
373 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
376 /* fetch the fieldmapper index */
377 osrfHash* _f = osrfHashGet(fields, columnName);
380 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
382 /* determine the field type and storage attributes */
384 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
386 case DBI_TYPE_INTEGER : {
388 if( !osrfHashGet(_f, "primitive") )
389 osrfHashSet(_f, "number", "primitive");
391 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
392 if( attr & DBI_INTEGER_SIZE8 )
393 osrfHashSet( _f, "INT8", "datatype" );
395 osrfHashSet( _f, "INT", "datatype" );
398 case DBI_TYPE_DECIMAL :
399 if( !osrfHashGet( _f, "primitive" ))
400 osrfHashSet( _f, "number", "primitive" );
402 osrfHashSet( _f, "NUMERIC", "datatype" );
405 case DBI_TYPE_STRING :
406 if( !osrfHashGet( _f, "primitive" ))
407 osrfHashSet( _f, "string", "primitive" );
409 osrfHashSet( _f,"TEXT", "datatype" );
412 case DBI_TYPE_DATETIME :
413 if( !osrfHashGet( _f, "primitive" ))
414 osrfHashSet( _f, "string", "primitive" );
416 osrfHashSet( _f, "TIMESTAMP", "datatype" );
419 case DBI_TYPE_BINARY :
420 if( !osrfHashGet( _f, "primitive" ))
421 osrfHashSet( _f, "string", "primitive" );
423 osrfHashSet( _f, "BYTEA", "datatype" );
428 "Setting [%s] to primitive [%s] and datatype [%s]...",
430 osrfHashGet( _f, "primitive" ),
431 osrfHashGet( _f, "datatype" )
435 } // end while loop for traversing columns of result
436 dbi_result_free( result );
439 int errnum = dbi_conn_error( handle, &msg );
440 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
441 errnum, msg ? msg : "(No description available)" );
442 // We don't check the database connection here. It's routine to get failures at
443 // this point; we routinely try to query tables that don't exist, because they
444 // are defined in the IDL but not in the database.
446 } // end for each class in IDL
448 buffer_free( query_buf );
449 osrfHashIteratorFree( class_itr );
450 child_initialized = 1;
452 if( !results_found ) {
453 osrfLogError( OSRF_LOG_MARK,
454 "No results found for any class -- bad database connection?" );
456 } else if( ! oilsIsDBConnected( handle )) {
457 osrfLogError( OSRF_LOG_MARK,
458 "Unable to extend IDL: database connection isn't working" );
466 @brief Free an osrfHash that stores a transaction ID.
467 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
469 This function is a callback, to be called by the application session when it ends.
470 The application session stores the osrfHash via an opaque pointer.
472 If the osrfHash contains an entry for the key "xact_id", it means that an
473 uncommitted transaction is pending. Roll it back.
475 void userDataFree( void* blob ) {
476 osrfHash* hash = (osrfHash*) blob;
477 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
478 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
480 int errnum = dbi_conn_error( writehandle, &msg );
481 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
482 errnum, msg ? msg : "(No description available)" );
486 if( !dbi_conn_query( writehandle, "SELECT auditor.clear_audit_info();" ) ) {
488 int errnum = dbi_conn_error( writehandle, &msg );
489 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform audit info clearing: %d %s",
490 errnum, msg ? msg : "(No description available)" );
494 osrfHashFree( hash );
498 @name Managing session data
499 @brief Maintain data stored via the userData pointer of the application session.
501 Currently, session-level data is stored in an osrfHash. Other arrangements are
502 possible, and some would be more efficient. The application session calls a
503 callback function to free userData before terminating.
505 Currently, the only data we store at the session level is the transaction id. By this
506 means we can ensure that any pending transactions are rolled back before the application
512 @brief Free an item in the application session's userData.
513 @param key The name of a key for an osrfHash.
514 @param item An opaque pointer to the item associated with the key.
516 We store an osrfHash as userData with the application session, and arrange (by
517 installing userDataFree() as a different callback) for the session to free that
518 osrfHash before terminating.
520 This function is a callback for freeing items in the osrfHash. Currently we store
522 - Transaction id of a pending transaction; a character string. Key: "xact_id".
523 - Authkey; a character string. Key: "authkey".
524 - User object from the authentication server; a jsonObject. Key: "user_login".
526 If we ever store anything else in userData, we will need to revisit this function so
527 that it will free whatever else needs freeing.
529 static void sessionDataFree( char* key, void* item ) {
530 if( !strcmp( key, "xact_id" ) || !strcmp( key, "authkey" ) || !strncmp( key, "rs_size_", 8) )
532 else if( !strcmp( key, "user_login" ) )
533 jsonObjectFree( (jsonObject*) item );
534 else if( !strcmp( key, "pcache" ) )
535 osrfHashFree( (osrfHash*) item );
538 static void pcacheFree( char* key, void* item ) {
539 osrfStringArrayFree( (osrfStringArray*) item );
543 @brief Initialize session cache.
544 @param ctx Pointer to the method context.
546 Create a cache for the session by making the session's userData member point
547 to an osrfHash instance.
549 static osrfHash* initSessionCache( osrfMethodContext* ctx ) {
550 ctx->session->userData = osrfNewHash();
551 osrfHashSetCallback( (osrfHash*) ctx->session->userData, &sessionDataFree );
552 ctx->session->userDataFree = &userDataFree;
553 return ctx->session->userData;
557 @brief Save a transaction id.
558 @param ctx Pointer to the method context.
560 Save the session_id of the current application session as a transaction id.
562 static void setXactId( osrfMethodContext* ctx ) {
563 if( ctx && ctx->session ) {
564 osrfAppSession* session = ctx->session;
566 osrfHash* cache = session->userData;
568 // If the session doesn't already have a hash, create one. Make sure
569 // that the application session frees the hash when it terminates.
571 cache = initSessionCache( ctx );
573 // Save the transaction id in the hash, with the key "xact_id"
574 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
579 @brief Get the transaction ID for the current transaction, if any.
580 @param ctx Pointer to the method context.
581 @return Pointer to the transaction ID.
583 The return value points to an internal buffer, and will become invalid upon issuing
584 a commit or rollback.
586 static inline const char* getXactId( osrfMethodContext* ctx ) {
587 if( ctx && ctx->session && ctx->session->userData )
588 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
594 @brief Clear the current transaction id.
595 @param ctx Pointer to the method context.
597 static inline void clearXactId( osrfMethodContext* ctx ) {
598 if( ctx && ctx->session && ctx->session->userData )
599 osrfHashRemove( ctx->session->userData, "xact_id" );
604 @brief Stash the location for a particular perm in the sessionData cache
605 @param ctx Pointer to the method context.
606 @param perm Name of the permission we're looking at
607 @param array StringArray of perm location ids
609 static void setPermLocationCache( osrfMethodContext* ctx, const char* perm, osrfStringArray* locations ) {
610 if( ctx && ctx->session ) {
611 osrfAppSession* session = ctx->session;
613 osrfHash* cache = session->userData;
615 // If the session doesn't already have a hash, create one. Make sure
616 // that the application session frees the hash when it terminates.
618 cache = initSessionCache( ctx );
620 osrfHash* pcache = osrfHashGet(cache, "pcache");
622 if( NULL == pcache ) {
623 pcache = osrfNewHash();
624 osrfHashSetCallback( pcache, &pcacheFree );
625 osrfHashSet( cache, pcache, "pcache" );
628 if( perm && locations )
629 osrfHashSet( pcache, locations, strdup(perm) );
634 @brief Grab stashed location for a particular perm in the sessionData cache
635 @param ctx Pointer to the method context.
636 @param perm Name of the permission we're looking at
638 static osrfStringArray* getPermLocationCache( osrfMethodContext* ctx, const char* perm ) {
639 if( ctx && ctx->session ) {
640 osrfAppSession* session = ctx->session;
641 osrfHash* cache = session->userData;
643 osrfHash* pcache = osrfHashGet(cache, "pcache");
645 return osrfHashGet( pcache, perm );
654 @brief Save the user's login in the userData for the current application session.
655 @param ctx Pointer to the method context.
656 @param user_login Pointer to the user login object to be cached (we cache the original,
659 If @a user_login is NULL, remove the user login if one is already cached.
661 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
662 if( ctx && ctx->session ) {
663 osrfAppSession* session = ctx->session;
665 osrfHash* cache = session->userData;
667 // If the session doesn't already have a hash, create one. Make sure
668 // that the application session frees the hash when it terminates.
670 cache = initSessionCache( ctx );
673 osrfHashSet( cache, user_login, "user_login" );
675 osrfHashRemove( cache, "user_login" );
680 @brief Get the user login object for the current application session, if any.
681 @param ctx Pointer to the method context.
682 @return Pointer to the user login object if found; otherwise NULL.
684 The user login object was returned from the authentication server, and then cached so
685 we don't have to call the authentication server again for the same user.
687 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
688 if( ctx && ctx->session && ctx->session->userData )
689 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
695 @brief Save a copy of an authkey in the userData of the current application session.
696 @param ctx Pointer to the method context.
697 @param authkey The authkey to be saved.
699 If @a authkey is NULL, remove the authkey if one is already cached.
701 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
702 if( ctx && ctx->session && authkey ) {
703 osrfAppSession* session = ctx->session;
704 osrfHash* cache = session->userData;
706 // If the session doesn't already have a hash, create one. Make sure
707 // that the application session frees the hash when it terminates.
709 cache = initSessionCache( ctx );
711 // Save the transaction id in the hash, with the key "xact_id"
712 if( authkey && *authkey )
713 osrfHashSet( cache, strdup( authkey ), "authkey" );
715 osrfHashRemove( cache, "authkey" );
720 @brief Reset the login timeout.
721 @param authkey The authentication key for the current login session.
722 @param now The current time.
723 @return Zero if successful, or 1 if not.
725 Tell the authentication server to reset the timeout so that the login session won't
726 expire for a while longer.
728 We could dispense with the @a now parameter by calling time(). But we just called
729 time() in order to decide whether to reset the timeout, so we might as well reuse
730 the result instead of calling time() again.
732 static int reset_timeout( const char* authkey, time_t now ) {
733 jsonObject* auth_object = jsonNewObject( authkey );
735 // Ask the authentication server to reset the timeout. It returns an event
736 // indicating success or failure.
737 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
738 "open-ils.auth.session.reset_timeout", auth_object );
739 jsonObjectFree( auth_object );
741 if( !result || result->type != JSON_HASH ) {
742 osrfLogError( OSRF_LOG_MARK,
743 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
744 jsonObjectFree( result );
745 return 1; // Not the right sort of object returned
748 const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
749 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
750 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
751 jsonObjectFree( result );
752 return 1; // Return code from method not available
755 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
756 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
758 desc = "(No reason available)"; // failsafe; shouldn't happen
759 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
760 jsonObjectFree( result );
764 // Revise our local proxy for the timeout deadline
765 // by a smallish fraction of the timeout interval
766 const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
768 timeout = "1"; // failsafe; shouldn't happen
769 time_next_reset = now + atoi( timeout ) / 15;
771 jsonObjectFree( result );
772 return 0; // Successfully reset timeout
776 @brief Get the authkey string for the current application session, if any.
777 @param ctx Pointer to the method context.
778 @return Pointer to the cached authkey if found; otherwise NULL.
780 If present, the authkey string was cached from a previous method call.
782 static const char* getAuthkey( osrfMethodContext* ctx ) {
783 if( ctx && ctx->session && ctx->session->userData ) {
784 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
785 // LFW recent changes mean the userData hash gets set up earlier, but
786 // doesn't necessarily have an authkey yet
790 // Possibly reset the authentication timeout to keep the login alive. We do so
791 // no more than once per method call, and not at all if it has been only a short
792 // time since the last reset.
794 // Here we reset explicitly, if at all. We also implicitly reset the timeout
795 // whenever we call the "open-ils.auth.session.retrieve" method.
796 if( timeout_needs_resetting ) {
797 time_t now = time( NULL );
798 if( now >= time_next_reset && reset_timeout( authkey, now ) )
799 authkey = NULL; // timeout has apparently expired already
802 timeout_needs_resetting = 0;
810 @brief Implement the transaction.begin method.
811 @param ctx Pointer to the method context.
812 @return Zero if successful, or -1 upon error.
814 Start a transaction. Save a transaction ID for future reference.
817 - authkey (PCRUD only)
819 Return to client: Transaction ID
821 int beginTransaction( osrfMethodContext* ctx ) {
822 if(osrfMethodVerifyContext( ctx )) {
823 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
827 if( enforce_pcrud ) {
828 timeout_needs_resetting = 1;
829 const jsonObject* user = verifyUserPCRUD( ctx );
834 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
837 int errnum = dbi_conn_error( writehandle, &msg );
838 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
839 modulename, errnum, msg ? msg : "(No description available)" );
840 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
841 "osrfMethodException", ctx->request, "Error starting transaction" );
842 if( !oilsIsDBConnected( writehandle ))
843 osrfAppSessionPanic( ctx->session );
846 dbi_result_free( result );
848 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
849 osrfAppRespondComplete( ctx, ret );
850 jsonObjectFree( ret );
856 @brief Implement the savepoint.set method.
857 @param ctx Pointer to the method context.
858 @return Zero if successful, or -1 if not.
860 Issue a SAVEPOINT to the database server.
863 - authkey (PCRUD only)
866 Return to client: Savepoint name
868 int setSavepoint( osrfMethodContext* ctx ) {
869 if(osrfMethodVerifyContext( ctx )) {
870 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
875 if( enforce_pcrud ) {
877 timeout_needs_resetting = 1;
878 const jsonObject* user = verifyUserPCRUD( ctx );
883 // Verify that a transaction is pending
884 const char* trans_id = getXactId( ctx );
885 if( NULL == trans_id ) {
886 osrfAppSessionStatus(
888 OSRF_STATUS_INTERNALSERVERERROR,
889 "osrfMethodException",
891 "No active transaction -- required for savepoints"
896 // Get the savepoint name from the method params
897 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
900 osrfLogWarning(OSRF_LOG_MARK, "savepoint.set called with no name");
904 char *safeSpName = _sanitize_savepoint_name( spName );
906 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", safeSpName );
910 int errnum = dbi_conn_error( writehandle, &msg );
913 "%s: Error creating savepoint %s in transaction %s: %d %s",
918 msg ? msg : "(No description available)"
920 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
921 "osrfMethodException", ctx->request, "Error creating savepoint" );
922 if( !oilsIsDBConnected( writehandle ))
923 osrfAppSessionPanic( ctx->session );
926 dbi_result_free( result );
927 jsonObject* ret = jsonNewObject( spName );
928 osrfAppRespondComplete( ctx, ret );
929 jsonObjectFree( ret );
935 @brief Implement the savepoint.release method.
936 @param ctx Pointer to the method context.
937 @return Zero if successful, or -1 if not.
939 Issue a RELEASE SAVEPOINT to the database server.
942 - authkey (PCRUD only)
945 Return to client: Savepoint name
947 int releaseSavepoint( osrfMethodContext* ctx ) {
948 if(osrfMethodVerifyContext( ctx )) {
949 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
954 if( enforce_pcrud ) {
956 timeout_needs_resetting = 1;
957 const jsonObject* user = verifyUserPCRUD( ctx );
962 // Verify that a transaction is pending
963 const char* trans_id = getXactId( ctx );
964 if( NULL == trans_id ) {
965 osrfAppSessionStatus(
967 OSRF_STATUS_INTERNALSERVERERROR,
968 "osrfMethodException",
970 "No active transaction -- required for savepoints"
975 // Get the savepoint name from the method params
976 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
979 osrfLogWarning(OSRF_LOG_MARK, "savepoint.release called with no name");
983 char *safeSpName = _sanitize_savepoint_name( spName );
985 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", safeSpName );
989 int errnum = dbi_conn_error( writehandle, &msg );
992 "%s: Error releasing savepoint %s in transaction %s: %d %s",
997 msg ? msg : "(No description available)"
999 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1000 "osrfMethodException", ctx->request, "Error releasing savepoint" );
1001 if( !oilsIsDBConnected( writehandle ))
1002 osrfAppSessionPanic( ctx->session );
1005 dbi_result_free( result );
1006 jsonObject* ret = jsonNewObject( spName );
1007 osrfAppRespondComplete( ctx, ret );
1008 jsonObjectFree( ret );
1014 @brief Implement the savepoint.rollback method.
1015 @param ctx Pointer to the method context.
1016 @return Zero if successful, or -1 if not.
1018 Issue a ROLLBACK TO SAVEPOINT to the database server.
1021 - authkey (PCRUD only)
1024 Return to client: Savepoint name
1026 int rollbackSavepoint( osrfMethodContext* ctx ) {
1027 if(osrfMethodVerifyContext( ctx )) {
1028 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1033 if( enforce_pcrud ) {
1035 timeout_needs_resetting = 1;
1036 const jsonObject* user = verifyUserPCRUD( ctx );
1041 // Verify that a transaction is pending
1042 const char* trans_id = getXactId( ctx );
1043 if( NULL == trans_id ) {
1044 osrfAppSessionStatus(
1046 OSRF_STATUS_INTERNALSERVERERROR,
1047 "osrfMethodException",
1049 "No active transaction -- required for savepoints"
1054 // Get the savepoint name from the method params
1055 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1058 osrfLogWarning(OSRF_LOG_MARK, "savepoint.rollback called with no name");
1062 char *safeSpName = _sanitize_savepoint_name( spName );
1064 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", safeSpName );
1068 int errnum = dbi_conn_error( writehandle, &msg );
1071 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
1076 msg ? msg : "(No description available)"
1078 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1079 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1080 if( !oilsIsDBConnected( writehandle ))
1081 osrfAppSessionPanic( ctx->session );
1084 dbi_result_free( result );
1085 jsonObject* ret = jsonNewObject( spName );
1086 osrfAppRespondComplete( ctx, ret );
1087 jsonObjectFree( ret );
1093 @brief Implement the transaction.commit method.
1094 @param ctx Pointer to the method context.
1095 @return Zero if successful, or -1 if not.
1097 Issue a COMMIT to the database server.
1100 - authkey (PCRUD only)
1102 Return to client: Transaction ID.
1104 int commitTransaction( osrfMethodContext* ctx ) {
1105 if(osrfMethodVerifyContext( ctx )) {
1106 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1110 if( enforce_pcrud ) {
1111 timeout_needs_resetting = 1;
1112 const jsonObject* user = verifyUserPCRUD( ctx );
1117 // Verify that a transaction is pending
1118 const char* trans_id = getXactId( ctx );
1119 if( NULL == trans_id ) {
1120 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1121 "osrfMethodException", ctx->request, "No active transaction to commit" );
1125 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1128 int errnum = dbi_conn_error( writehandle, &msg );
1129 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1130 modulename, errnum, msg ? msg : "(No description available)" );
1131 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1132 "osrfMethodException", ctx->request, "Error committing transaction" );
1133 if( !oilsIsDBConnected( writehandle ))
1134 osrfAppSessionPanic( ctx->session );
1137 dbi_result_free( result );
1138 jsonObject* ret = jsonNewObject( trans_id );
1139 osrfAppRespondComplete( ctx, ret );
1140 jsonObjectFree( ret );
1147 @brief Implement the transaction.rollback method.
1148 @param ctx Pointer to the method context.
1149 @return Zero if successful, or -1 if not.
1151 Issue a ROLLBACK to the database server.
1154 - authkey (PCRUD only)
1156 Return to client: Transaction ID
1158 int rollbackTransaction( osrfMethodContext* ctx ) {
1159 if( osrfMethodVerifyContext( ctx )) {
1160 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1164 if( enforce_pcrud ) {
1165 timeout_needs_resetting = 1;
1166 const jsonObject* user = verifyUserPCRUD( ctx );
1171 // Verify that a transaction is pending
1172 const char* trans_id = getXactId( ctx );
1173 if( NULL == trans_id ) {
1174 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1175 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1179 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1182 int errnum = dbi_conn_error( writehandle, &msg );
1183 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1184 modulename, errnum, msg ? msg : "(No description available)" );
1185 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1186 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1187 if( !oilsIsDBConnected( writehandle ))
1188 osrfAppSessionPanic( ctx->session );
1191 dbi_result_free( result );
1192 jsonObject* ret = jsonNewObject( trans_id );
1193 osrfAppRespondComplete( ctx, ret );
1194 jsonObjectFree( ret );
1201 @brief Implement the "search" method.
1202 @param ctx Pointer to the method context.
1203 @return Zero if successful, or -1 if not.
1206 - authkey (PCRUD only)
1207 - WHERE clause, as jsonObject
1208 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1210 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1211 Optionally flesh linked fields.
1213 int doSearch( osrfMethodContext* ctx ) {
1214 if( osrfMethodVerifyContext( ctx )) {
1215 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1220 timeout_needs_resetting = 1;
1222 jsonObject* where_clause;
1223 jsonObject* rest_of_query;
1225 if( enforce_pcrud ) {
1226 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1227 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1229 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1230 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1233 if( !where_clause ) {
1234 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1238 // Get the class metadata
1239 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1240 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1244 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1246 osrfAppRespondComplete( ctx, NULL );
1250 // doFieldmapperSearch() now takes care of our responding for us
1251 // // Return each row to the client
1252 // jsonObject* cur = 0;
1253 // unsigned long res_idx = 0;
1255 // while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1256 // // We used to discard based on perms here, but now that's
1257 // // inside doFieldmapperSearch()
1258 // osrfAppRespond( ctx, cur );
1261 jsonObjectFree( obj );
1263 osrfAppRespondComplete( ctx, NULL );
1268 @brief Implement the "id_list" method.
1269 @param ctx Pointer to the method context.
1270 @param err Pointer through which to return an error code.
1271 @return Zero if successful, or -1 if not.
1274 - authkey (PCRUD only)
1275 - WHERE clause, as jsonObject
1276 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1278 Return to client: The primary key values for all rows of the relevant class that
1279 satisfy a specified WHERE clause.
1281 This method relies on the assumption that every class has a primary key consisting of
1284 int doIdList( osrfMethodContext* ctx ) {
1285 if( osrfMethodVerifyContext( ctx )) {
1286 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1291 timeout_needs_resetting = 1;
1293 jsonObject* where_clause;
1294 jsonObject* rest_of_query;
1296 // We use the where clause without change. But we need to massage the rest of the
1297 // query, so we work with a copy of it instead of modifying the original.
1299 if( enforce_pcrud ) {
1300 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1301 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1303 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1304 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1307 if( !where_clause ) {
1308 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1312 // Eliminate certain SQL clauses, if present.
1313 if( rest_of_query ) {
1314 jsonObjectRemoveKey( rest_of_query, "select" );
1315 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1316 jsonObjectRemoveKey( rest_of_query, "flesh" );
1317 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1319 rest_of_query = jsonNewObjectType( JSON_HASH );
1322 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1324 // Get the class metadata
1325 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1326 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1328 // Build a SELECT list containing just the primary key,
1329 // i.e. like { "classname":["keyname"] }
1330 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1332 // Load array with name of primary key
1333 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1334 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1335 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1337 jsonObjectSetKey( rest_of_query, "select", select_clause );
1342 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1344 jsonObjectFree( rest_of_query );
1346 osrfAppRespondComplete( ctx, NULL );
1350 // Return each primary key value to the client
1352 unsigned long res_idx = 0;
1353 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1354 // We used to discard based on perms here, but now that's
1355 // inside doFieldmapperSearch()
1356 osrfAppRespond( ctx,
1357 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1360 jsonObjectFree( obj );
1361 osrfAppRespondComplete( ctx, NULL );
1366 @brief Verify that we have a valid class reference.
1367 @param ctx Pointer to the method context.
1368 @param param Pointer to the method parameters.
1369 @return 1 if the class reference is valid, or zero if it isn't.
1371 The class of the method params must match the class to which the method id devoted.
1372 For PCRUD there are additional restrictions.
1374 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1376 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1377 osrfHash* class = osrfHashGet( method_meta, "class" );
1379 // Compare the method's class to the parameters' class
1380 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1382 // Oops -- they don't match. Complain.
1383 growing_buffer* msg = buffer_init( 128 );
1386 "%s: %s method for type %s was passed a %s",
1388 osrfHashGet( method_meta, "methodtype" ),
1389 osrfHashGet( class, "classname" ),
1390 param->classname ? param->classname : "(null)"
1393 char* m = buffer_release( msg );
1394 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1402 return verifyObjectPCRUD( ctx, class, param, 1 );
1408 @brief (PCRUD only) Verify that the user is properly logged in.
1409 @param ctx Pointer to the method context.
1410 @return If the user is logged in, a pointer to the user object from the authentication
1411 server; otherwise NULL.
1413 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1414 return verifyUserPCRUDfull( ctx, 0 );
1417 static const jsonObject* verifyUserPCRUDfull( osrfMethodContext* ctx, int anon_ok ) {
1419 // Get the authkey (the first method parameter)
1420 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1422 jsonObject* user = NULL;
1424 // If we are /not/ in anonymous mode
1425 if( strcmp( "ANONYMOUS", auth ) ) {
1426 // See if we have the same authkey, and a user object,
1427 // locally cached from a previous call
1428 const char* cached_authkey = getAuthkey( ctx );
1429 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1430 const jsonObject* cached_user = getUserLogin( ctx );
1435 // We have no matching authentication data in the cache. Authenticate from scratch.
1436 jsonObject* auth_object = jsonNewObject( auth );
1438 // Fetch the user object from the authentication server
1439 user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve", auth_object );
1440 jsonObjectFree( auth_object );
1442 if( !user->classname || strcmp(user->classname, "au" )) {
1444 growing_buffer* msg = buffer_init( 128 );
1447 "%s: permacrud received a bad auth token: %s",
1452 char* m = buffer_release( msg );
1453 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1457 jsonObjectFree( user );
1459 } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
1460 // Failed to set audit information - But note that write_audit_info already set error information.
1461 jsonObjectFree( user );
1466 } else if ( anon_ok ) { // we /are/ (attempting to be) anonymous
1467 user = jsonNewObjectType(JSON_ARRAY);
1468 jsonObjectSetClass( user, "aou" );
1469 oilsFMSetString(user, "id", "-1");
1472 setUserLogin( ctx, user );
1473 setAuthkey( ctx, auth );
1475 // Allow ourselves up to a second before we have to reset the login timeout.
1476 // It would be nice to use some fraction of the timeout interval enforced by the
1477 // authentication server, but that value is not readily available at this point.
1478 // Instead, we use a conservative default interval.
1479 time_next_reset = time( NULL ) + 1;
1485 @brief For PCRUD: Determine whether the current user may access the current row.
1486 @param ctx Pointer to the method context.
1487 @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1488 @param obj Pointer to the row being potentially accessed.
1489 @return 1 if access is permitted, or 0 if it isn't.
1491 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1493 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1495 dbhandle = writehandle;
1497 // Figure out what class and method are involved
1498 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1499 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1502 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1503 if (rs_size_from_hash) {
1504 rs_size = *rs_size_from_hash;
1505 osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1509 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1510 // contexts we will do another lookup of the current row, even if we already have a
1511 // previously fetched row image, because the row image in hand may not include the
1512 // foreign key(s) that we need.
1514 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1515 // but they aren't implemented yet.
1518 if( *method_type == 's' || *method_type == 'i' ) {
1519 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1521 } else if( *method_type == 'u' || *method_type == 'd' ) {
1522 fetch = 1; // MUST go to the db for the object for update and delete
1525 // In retrieve or search ONLY we allow anon. Later perm checks will fail as they should,
1526 // in the face of a fake user but required permissions.
1528 if( *method_type == 'r' )
1531 // Get the appropriate permacrud entry from the IDL, depending on method type
1532 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1534 // No permacrud for this method type on this class
1536 growing_buffer* msg = buffer_init( 128 );
1539 "%s: %s on class %s has no permacrud IDL entry",
1541 osrfHashGet( method_metadata, "methodtype" ),
1542 osrfHashGet( class, "classname" )
1545 char* m = buffer_release( msg );
1546 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1547 "osrfMethodException", ctx->request, m );
1554 // Get the user id, and make sure the user is logged in
1555 const jsonObject* user = verifyUserPCRUDfull( ctx, anon_ok );
1557 return 0; // Not logged in or anon? No access.
1559 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1561 // Get a list of permissions from the permacrud entry.
1562 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1563 if( permission->size == 0 ) {
1566 "No permissions required for this action (class %s), passing through",
1567 osrfHashGet(class, "classname")
1572 // But, if there are perms and the user is anonymous ... FAIL
1576 // Build a list of org units that own the row. This is fairly convoluted because there
1577 // are several different ways that an org unit may own the row, as defined by the
1580 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1581 // identifying an owning org_unit..
1582 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1584 // Foreign context adds a layer of indirection. The row points to some other row that
1585 // an org unit may own. The "jump" attribute, if present, adds another layer of
1587 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1589 // The following string array stores the list of org units. (We don't have a thingie
1590 // for storing lists of integers, so we fake it with a list of strings.)
1591 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1593 const char* context_org = NULL;
1594 const char* pkey = NULL;
1595 jsonObject *param = NULL;
1596 const char* perm = NULL;
1600 const char* pkey_value = NULL;
1601 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1602 // If the global_required attribute is present and true, then the only owning
1603 // org unit is the root org unit, i.e. the one with no parent.
1604 osrfLogDebug( OSRF_LOG_MARK,
1605 "global-level permissions required, fetching top of the org tree" );
1607 // no need to check perms for org tree root retrieval
1608 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1609 // check for perm at top of org tree
1610 const char* org_tree_root_id = org_tree_root( ctx );
1611 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1613 if( org_tree_root_id ) {
1614 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1615 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1617 osrfStringArrayFree( context_org_array );
1622 // If the global_required attribute is absent or false, then we look for
1623 // local and/or foreign context. In order to find the relevant foreign
1624 // keys, we must either read the relevant row from the database, or look at
1625 // the image of the row that we already have in memory.
1627 // Even if we have an image of the row in memory, that image may not include the
1628 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1629 // of the row to make sure that we have what we need.
1631 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1632 "fetching context org ids" );
1634 pkey = osrfHashGet( class, "primarykey" );
1637 // There is no primary key, so we can't do a fresh lookup. Use the row
1638 // image that we already have. If it doesn't have everything we need, too bad.
1640 param = jsonObjectClone( obj );
1641 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1642 } else if( obj->classname ) {
1643 pkey_value = oilsFMGetStringConst( obj, pkey );
1645 param = jsonObjectClone( obj );
1646 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1649 pkey_value = jsonObjectGetString( obj );
1651 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1652 "of %s and retrieving from the database", pkey_value );
1656 // Fetch the row so that we can look at the foreign key(s)
1657 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1658 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1659 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1660 jsonObjectFree( _tmp_params );
1661 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1663 param = jsonObjectExtractIndex( _list, 0 );
1664 jsonObjectFree( _list );
1670 // The row doesn't exist. Complain, and deny access.
1671 osrfLogDebug( OSRF_LOG_MARK,
1672 "Object not found in the database with primary key %s of %s",
1675 growing_buffer* msg = buffer_init( 128 );
1678 "%s: no object found with primary key %s of %s",
1684 char* m = buffer_release( msg );
1685 osrfAppSessionStatus(
1687 OSRF_STATUS_INTERNALSERVERERROR,
1688 "osrfMethodException",
1697 if( local_context && local_context->size > 0 ) {
1698 // The IDL provides a list of column names for the foreign keys denoting
1699 // local context, i.e. columns identifying owing org units directly. Look up
1700 // the value of each one, and if it isn't null, add it to the list of org units.
1701 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1702 local_context->size );
1704 const char* lcontext = NULL;
1705 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1706 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1707 if( fkey_value ) { // if not null
1708 osrfStringArrayAdd( context_org_array, fkey_value );
1711 "adding class-local field %s (value: %s) to the context org list",
1713 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1719 if( foreign_context ) {
1720 unsigned long class_count = osrfHashGetCount( foreign_context );
1721 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1723 if( class_count > 0 ) {
1725 // The IDL provides a list of foreign key columns pointing to rows that
1726 // an org unit may own. Follow each link, identify the owning org unit,
1727 // and add it to the list.
1728 osrfHash* fcontext = NULL;
1729 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1730 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1731 // For each class to which a foreign key points:
1732 const char* class_name = osrfHashIteratorKey( class_itr );
1733 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1737 "%d foreign context fields(s) specified for class %s",
1738 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1742 // Get the name of the key field in the foreign table
1743 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1745 // Get the value of the foreign key pointing to the foreign table
1746 char* foreign_pkey_value =
1747 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1748 if( !foreign_pkey_value )
1749 continue; // Foreign key value is null; skip it
1751 // Look up the row to which the foreign key points
1752 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1754 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1755 jsonObject* _list = doFieldmapperSearch(
1756 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1757 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1759 jsonObject* _fparam = NULL;
1760 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1761 _fparam = jsonObjectExtractIndex( _list, 0 );
1763 jsonObjectFree( _tmp_params );
1764 jsonObjectFree( _list );
1766 // At this point _fparam either points to the row identified by the
1767 // foreign key, or it's NULL (no such row found).
1769 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1771 const char* bad_class = NULL; // For noting failed lookups
1773 bad_class = class_name; // Referenced row not found
1774 else if( jump_list ) {
1775 // Follow a chain of rows, linked by foreign keys, to find an owner
1776 const char* flink = NULL;
1778 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1779 // For each entry in the jump list. Each entry (i.e. flink) is
1780 // the name of a foreign key column in the current row.
1782 // From the IDL, get the linkage information for the next jump
1783 osrfHash* foreign_link_hash =
1784 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1786 // Get the class metadata for the class
1787 // to which the foreign key points
1788 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1789 osrfHashGet( foreign_link_hash, "class" ));
1791 // Get the name of the referenced key of that class
1792 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1794 // Get the value of the foreign key pointing to that class
1795 free( foreign_pkey_value );
1796 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1797 if( !foreign_pkey_value )
1798 break; // Foreign key is null; quit looking
1800 // Build a WHERE clause for the lookup
1801 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1804 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1805 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1806 _tmp_params, NULL, &err );
1807 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1809 // Get the resulting row
1810 jsonObjectFree( _fparam );
1811 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1812 _fparam = jsonObjectExtractIndex( _list, 0 );
1814 // Referenced row not found
1816 bad_class = osrfHashGet( foreign_link_hash, "class" );
1819 jsonObjectFree( _tmp_params );
1820 jsonObjectFree( _list );
1826 // We had a foreign key pointing to such-and-such a row, but then
1827 // we couldn't fetch that row. The data in the database are in an
1828 // inconsistent state; the database itself may even be corrupted.
1829 growing_buffer* msg = buffer_init( 128 );
1832 "%s: no object of class %s found with primary key %s of %s",
1836 foreign_pkey_value ? foreign_pkey_value : "(null)"
1839 char* m = buffer_release( msg );
1840 osrfAppSessionStatus(
1842 OSRF_STATUS_INTERNALSERVERERROR,
1843 "osrfMethodException",
1849 osrfHashIteratorFree( class_itr );
1850 free( foreign_pkey_value );
1851 jsonObjectFree( param );
1856 free( foreign_pkey_value );
1859 // Examine each context column of the foreign row,
1860 // and add its value to the list of org units.
1862 const char* foreign_field = NULL;
1863 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1864 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1865 osrfStringArrayAdd( context_org_array,
1866 oilsFMGetStringConst( _fparam, foreign_field ));
1867 osrfLogDebug( OSRF_LOG_MARK,
1868 "adding foreign class %s field %s (value: %s) "
1869 "to the context org list",
1872 osrfStringArrayGetString(
1873 context_org_array, context_org_array->size - 1 )
1877 jsonObjectFree( _fparam );
1881 osrfHashIteratorFree( class_itr );
1886 // If there is an owning_user attached to the action, we allow that user and users with
1887 // object perms on the object. CREATE can't use this. We only do this when we're not
1888 // ignoring object perms.
1889 char* owning_user_field = osrfHashGet( pcrud, "owning_user" );
1891 *method_type != 'c' &&
1892 (!str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) || // Always honor
1895 if (owning_user_field) { // see if we can short-cut by comparing the owner to the requestor
1897 if (!param) { // We didn't get it during the context lookup
1898 pkey = osrfHashGet( class, "primarykey" );
1901 // There is no primary key, so we can't do a fresh lookup. Use the row
1902 // image that we already have. If it doesn't have everything we need, too bad.
1904 param = jsonObjectClone( obj );
1905 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1906 } else if( obj->classname ) {
1907 pkey_value = oilsFMGetStringConst( obj, pkey );
1909 param = jsonObjectClone( obj );
1910 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1913 pkey_value = jsonObjectGetString( obj );
1915 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1916 "of %s and retrieving from the database", pkey_value );
1920 // Fetch the row so that we can look at the foreign key(s)
1921 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1922 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1923 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1924 jsonObjectFree( _tmp_params );
1925 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1927 param = jsonObjectExtractIndex( _list, 0 );
1928 jsonObjectFree( _list );
1933 // The row doesn't exist. Complain, and deny access.
1934 osrfLogDebug( OSRF_LOG_MARK,
1935 "Object not found in the database with primary key %s of %s",
1938 growing_buffer* msg = buffer_init( 128 );
1941 "%s: no object found with primary key %s of %s",
1947 char* m = buffer_release( msg );
1948 osrfAppSessionStatus(
1950 OSRF_STATUS_INTERNALSERVERERROR,
1951 "osrfMethodException",
1960 int ownerid = atoi( oilsFMGetStringConst( param, owning_user_field ) );
1962 // Allow the owner to do whatever
1963 if (ownerid == userid)
1970 (perm = osrfStringArrayGetString(permission, i++)) &&
1971 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms"))
1977 "Checking object permission [%s] for user %d "
1978 "on object %s (class %s)",
1982 osrfHashGet( class, "classname" )
1985 result = dbi_conn_queryf(
1987 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s') AS has_perm;",
1990 osrfHashGet( class, "classname" ),
1997 "Received a result for object permission [%s] "
1998 "for user %d on object %s (class %s)",
2002 osrfHashGet( class, "classname" )
2005 if( dbi_result_first_row( result )) {
2006 jsonObject* return_val = oilsMakeJSONFromResult( result );
2007 const char* has_perm = jsonObjectGetString(
2008 jsonObjectGetKeyConst( return_val, "has_perm" ));
2012 "Status of object permission [%s] for user %d "
2013 "on object %s (class %s) is %s",
2017 osrfHashGet(class, "classname"),
2021 if( *has_perm == 't' )
2023 jsonObjectFree( return_val );
2026 dbi_result_free( result );
2031 int errnum = dbi_conn_error( writehandle, &msg );
2032 osrfLogWarning( OSRF_LOG_MARK,
2033 "Unable to call check object permissions: %d, %s",
2034 errnum, msg ? msg : "(No description available)" );
2035 if( !oilsIsDBConnected( writehandle ))
2036 osrfAppSessionPanic( ctx->session );
2041 // For every combination of permission and context org unit: call a stored procedure
2042 // to determine if the user has this permission in the context of this org unit.
2043 // If the answer is yes at any point, then we're done, and the user has permission.
2044 // In other words permissions are additive.
2046 while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
2049 osrfStringArray* pcache = NULL;
2050 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
2051 pcache = getPermLocationCache(ctx, perm);
2054 pcache = osrfNewStringArray(0);
2056 result = dbi_conn_queryf(
2058 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
2066 "Received a result for permission [%s] for user %d",
2071 if( dbi_result_first_row( result )) {
2073 jsonObject* return_val = oilsMakeJSONFromResult( result );
2074 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
2075 jsonObjectFree( return_val );
2076 } while( dbi_result_next_row( result ));
2078 setPermLocationCache(ctx, perm, pcache);
2081 dbi_result_free( result );
2087 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
2089 if (rs_size > perm_at_threshold) {
2090 if (osrfStringArrayContains( pcache, context_org )) {
2098 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
2100 !str_is_true( osrfHashGet(pcrud, "global_required") ) ||
2101 osrfHashGet(pcrud, "owning_user")
2106 "Checking object permission [%s] for user %d "
2107 "on object %s (class %s) at org %d",
2111 osrfHashGet( class, "classname" ),
2115 result = dbi_conn_queryf(
2117 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
2120 osrfHashGet( class, "classname" ),
2128 "Received a result for object permission [%s] "
2129 "for user %d on object %s (class %s) at org %d",
2133 osrfHashGet( class, "classname" ),
2137 if( dbi_result_first_row( result )) {
2138 jsonObject* return_val = oilsMakeJSONFromResult( result );
2139 const char* has_perm = jsonObjectGetString(
2140 jsonObjectGetKeyConst( return_val, "has_perm" ));
2144 "Status of object permission [%s] for user %d "
2145 "on object %s (class %s) at org %d is %s",
2149 osrfHashGet(class, "classname"),
2154 if( *has_perm == 't' )
2156 jsonObjectFree( return_val );
2159 dbi_result_free( result );
2164 int errnum = dbi_conn_error( writehandle, &msg );
2165 osrfLogWarning( OSRF_LOG_MARK,
2166 "Unable to call check object permissions: %d, %s",
2167 errnum, msg ? msg : "(No description available)" );
2168 if( !oilsIsDBConnected( writehandle ))
2169 osrfAppSessionPanic( ctx->session );
2173 if (rs_size > perm_at_threshold) break;
2175 osrfLogDebug( OSRF_LOG_MARK,
2176 "Checking non-object permission [%s] for user %d at org %d",
2177 perm, userid, atoi(context_org) );
2178 result = dbi_conn_queryf(
2180 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
2187 osrfLogDebug( OSRF_LOG_MARK,
2188 "Received a result for permission [%s] for user %d at org %d",
2189 perm, userid, atoi( context_org ));
2190 if( dbi_result_first_row( result )) {
2191 jsonObject* return_val = oilsMakeJSONFromResult( result );
2192 const char* has_perm = jsonObjectGetString(
2193 jsonObjectGetKeyConst( return_val, "has_perm" ));
2194 osrfLogDebug( OSRF_LOG_MARK,
2195 "Status of permission [%s] for user %d at org %d is [%s]",
2196 perm, userid, atoi( context_org ), has_perm );
2197 if( *has_perm == 't' )
2199 jsonObjectFree( return_val );
2202 dbi_result_free( result );
2207 int errnum = dbi_conn_error( writehandle, &msg );
2208 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
2209 errnum, msg ? msg : "(No description available)" );
2210 if( !oilsIsDBConnected( writehandle ))
2211 osrfAppSessionPanic( ctx->session );
2220 osrfStringArrayFree( context_org_array );
2226 @brief Look up the root of the org_unit tree.
2227 @param ctx Pointer to the method context.
2228 @return The id of the root org unit, as a character string.
2230 Query actor.org_unit where parent_ou is null, and return the id as a string.
2232 This function assumes that there is only one root org unit, i.e. that we
2233 have a single tree, not a forest.
2235 The calling code is responsible for freeing the returned string.
2237 static const char* org_tree_root( osrfMethodContext* ctx ) {
2239 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
2240 static time_t last_lookup_time = 0;
2241 time_t current_time = time( NULL );
2243 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2244 // We successfully looked this up less than an hour ago.
2245 // It's not likely to have changed since then.
2246 return strdup( cached_root_id );
2248 last_lookup_time = current_time;
2251 jsonObject* where_clause = single_hash( "parent_ou", NULL );
2252 jsonObject* result = doFieldmapperSearch(
2253 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2254 jsonObjectFree( where_clause );
2256 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2259 jsonObjectFree( result );
2261 growing_buffer* msg = buffer_init( 128 );
2262 OSRF_BUFFER_ADD( msg, modulename );
2263 OSRF_BUFFER_ADD( msg,
2264 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2266 char* m = buffer_release( msg );
2267 osrfAppSessionStatus( ctx->session,
2268 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2271 cached_root_id[ 0 ] = '\0';
2275 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2276 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2278 strcpy( cached_root_id, root_org_unit_id );
2279 jsonObjectFree( result );
2280 return cached_root_id;
2284 @brief Create a JSON_HASH with a single key/value pair.
2285 @param key The key of the key/value pair.
2286 @param value the value of the key/value pair.
2287 @return Pointer to a newly created jsonObject of type JSON_HASH.
2289 The value of the key/value is either a string or (if @a value is NULL) a null.
2291 static jsonObject* single_hash( const char* key, const char* value ) {
2293 if( ! key ) key = "";
2295 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2296 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2301 int doCreate( osrfMethodContext* ctx ) {
2302 if(osrfMethodVerifyContext( ctx )) {
2303 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2308 timeout_needs_resetting = 1;
2310 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2311 jsonObject* target = NULL;
2312 jsonObject* options = NULL;
2314 if( enforce_pcrud ) {
2315 target = jsonObjectGetIndex( ctx->params, 1 );
2316 options = jsonObjectGetIndex( ctx->params, 2 );
2318 target = jsonObjectGetIndex( ctx->params, 0 );
2319 options = jsonObjectGetIndex( ctx->params, 1 );
2322 if( !verifyObjectClass( ctx, target )) {
2323 osrfAppRespondComplete( ctx, NULL );
2327 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2329 const char* trans_id = getXactId( ctx );
2331 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2333 osrfAppSessionStatus(
2335 OSRF_STATUS_BADREQUEST,
2336 "osrfMethodException",
2338 "No active transaction -- required for CREATE"
2340 osrfAppRespondComplete( ctx, NULL );
2344 // The following test is harmless but redundant. If a class is
2345 // readonly, we don't register a create method for it.
2346 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2347 osrfAppSessionStatus(
2349 OSRF_STATUS_BADREQUEST,
2350 "osrfMethodException",
2352 "Cannot INSERT readonly class"
2354 osrfAppRespondComplete( ctx, NULL );
2358 // Set the last_xact_id
2359 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2361 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2362 trans_id, target->classname, index);
2363 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2366 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2368 dbhandle = writehandle;
2370 osrfHash* fields = osrfHashGet( meta, "fields" );
2371 char* pkey = osrfHashGet( meta, "primarykey" );
2372 char* seq = osrfHashGet( meta, "sequence" );
2374 growing_buffer* table_buf = buffer_init( 128 );
2375 growing_buffer* col_buf = buffer_init( 128 );
2376 growing_buffer* val_buf = buffer_init( 128 );
2378 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2379 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2380 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2381 buffer_add( val_buf,"VALUES (" );
2385 osrfHash* field = NULL;
2386 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2387 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2389 const char* field_name = osrfHashIteratorKey( field_itr );
2391 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2394 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2397 if( field_object && field_object->classname ) {
2398 value = oilsFMGetString(
2400 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2402 } else if( field_object && JSON_BOOL == field_object->type ) {
2403 if( jsonBoolIsTrue( field_object ) )
2404 value = strdup( "t" );
2406 value = strdup( "f" );
2408 value = jsonObjectToSimpleString( field_object );
2414 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2415 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2418 buffer_add( col_buf, field_name );
2420 if( !field_object || field_object->type == JSON_NULL ) {
2421 buffer_add( val_buf, "DEFAULT" );
2423 } else if( !strcmp( get_primitive( field ), "number" )) {
2424 const char* numtype = get_datatype( field );
2425 if( !strcmp( numtype, "INT8" )) {
2426 buffer_fadd( val_buf, "%lld", atoll( value ));
2428 } else if( !strcmp( numtype, "INT" )) {
2429 buffer_fadd( val_buf, "%d", atoi( value ));
2431 } else if( !strcmp( numtype, "NUMERIC" )) {
2432 buffer_fadd( val_buf, "%f", atof( value ));
2435 if( dbi_conn_quote_string( writehandle, &value )) {
2436 OSRF_BUFFER_ADD( val_buf, value );
2439 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2440 osrfAppSessionStatus(
2442 OSRF_STATUS_INTERNALSERVERERROR,
2443 "osrfMethodException",
2445 "Error quoting string -- please see the error log for more details"
2448 buffer_free( table_buf );
2449 buffer_free( col_buf );
2450 buffer_free( val_buf );
2451 osrfAppRespondComplete( ctx, NULL );
2459 osrfHashIteratorFree( field_itr );
2461 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2462 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2464 char* table_str = buffer_release( table_buf );
2465 char* col_str = buffer_release( col_buf );
2466 char* val_str = buffer_release( val_buf );
2467 growing_buffer* sql = buffer_init( 128 );
2468 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2473 char* query = buffer_release( sql );
2475 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2477 jsonObject* obj = NULL;
2480 dbi_result result = dbi_conn_query( writehandle, query );
2482 obj = jsonNewObject( NULL );
2484 int errnum = dbi_conn_error( writehandle, &msg );
2487 "%s ERROR inserting %s object using query [%s]: %d %s",
2489 osrfHashGet(meta, "fieldmapper"),
2492 msg ? msg : "(No description available)"
2494 osrfAppSessionStatus(
2496 OSRF_STATUS_INTERNALSERVERERROR,
2497 "osrfMethodException",
2499 "INSERT error -- please see the error log for more details"
2501 if( !oilsIsDBConnected( writehandle ))
2502 osrfAppSessionPanic( ctx->session );
2505 dbi_result_free( result );
2507 char* id = oilsFMGetString( target, pkey );
2509 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2510 growing_buffer* _id = buffer_init( 10 );
2511 buffer_fadd( _id, "%lld", new_id );
2512 id = buffer_release( _id );
2515 // Find quietness specification, if present
2516 const char* quiet_str = NULL;
2518 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2520 quiet_str = jsonObjectGetString( quiet_obj );
2523 if( str_is_true( quiet_str )) { // if quietness is specified
2524 obj = jsonNewObject( id );
2528 // Fetch the row that we just inserted, so that we can return it to the client
2529 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2530 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2533 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2537 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2539 jsonObjectFree( list );
2540 jsonObjectFree( where_clause );
2547 osrfAppRespondComplete( ctx, obj );
2548 jsonObjectFree( obj );
2553 @brief Implement the retrieve method.
2554 @param ctx Pointer to the method context.
2555 @param err Pointer through which to return an error code.
2556 @return If successful, a pointer to the result to be returned to the client;
2559 From the method's class, fetch a row with a specified value in the primary key. This
2560 method relies on the database design convention that a primary key consists of a single
2564 - authkey (PCRUD only)
2565 - value of the primary key for the desired row, for building the WHERE clause
2566 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2568 Return to client: One row from the query.
2570 int doRetrieve( osrfMethodContext* ctx ) {
2571 if(osrfMethodVerifyContext( ctx )) {
2572 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2577 timeout_needs_resetting = 1;
2582 if( enforce_pcrud ) {
2587 // Get the class metadata
2588 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2590 // Get the value of the primary key, from a method parameter
2591 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2595 "%s retrieving %s object with primary key value of %s",
2597 osrfHashGet( class_def, "fieldmapper" ),
2598 jsonObjectGetString( id_obj )
2601 // Build a WHERE clause based on the key value
2602 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2605 osrfHashGet( class_def, "primarykey" ), // name of key column
2606 jsonObjectClone( id_obj ) // value of key column
2609 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2613 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2615 jsonObjectFree( where_clause );
2617 osrfAppRespondComplete( ctx, NULL );
2621 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2622 jsonObjectFree( list );
2624 if( enforce_pcrud ) {
2625 // no result, skip this entirely
2626 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2627 jsonObjectFree( obj );
2629 growing_buffer* msg = buffer_init( 128 );
2630 OSRF_BUFFER_ADD( msg, modulename );
2631 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2633 char* m = buffer_release( msg );
2634 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2638 osrfAppRespondComplete( ctx, NULL );
2643 // doFieldmapperSearch() now does the responding for us
2644 //osrfAppRespondComplete( ctx, obj );
2645 osrfAppRespondComplete( ctx, NULL );
2647 jsonObjectFree( obj );
2652 @brief Translate a numeric value to a string representation for the database.
2653 @param field Pointer to the IDL field definition.
2654 @param value Pointer to a jsonObject holding the value of a field.
2655 @return Pointer to a newly allocated string.
2657 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2658 its contents are numeric. A non-numeric string is likely to result in invalid SQL.
2660 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2662 The calling code is responsible for freeing the resulting string by calling free().
2664 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2665 growing_buffer* val_buf = buffer_init( 32 );
2667 // If the value is a number and the DB field is numeric, no quotes needed
2668 if( value->type == JSON_NUMBER && !strcmp( get_primitive( field ), "number") ) {
2669 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2671 // Presumably this was really intended to be a string, so quote it
2672 char* str = jsonObjectToSimpleString( value );
2673 if( dbi_conn_quote_string( dbhandle, &str )) {
2674 OSRF_BUFFER_ADD( val_buf, str );
2677 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2679 buffer_free( val_buf );
2684 return buffer_release( val_buf );
2687 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2688 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2689 growing_buffer* sql_buf = buffer_init( 32 );
2695 osrfHashGet( field, "name" )
2699 buffer_add( sql_buf, "IN (" );
2700 } else if( !strcasecmp( op,"not in" )) {
2701 buffer_add( sql_buf, "NOT IN (" );
2703 buffer_add( sql_buf, "IN (" );
2706 if( node->type == JSON_HASH ) {
2707 // subquery predicate
2708 char* subpred = buildQuery( ctx, node, SUBSELECT );
2710 buffer_free( sql_buf );
2714 buffer_add( sql_buf, subpred );
2717 } else if( node->type == JSON_ARRAY ) {
2718 // literal value list
2719 int in_item_index = 0;
2720 int in_item_first = 1;
2721 const jsonObject* in_item;
2722 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2727 buffer_add( sql_buf, ", " );
2730 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2731 osrfLogError( OSRF_LOG_MARK,
2732 "%s: Expected string or number within IN list; found %s",
2733 modulename, json_type( in_item->type ) );
2734 buffer_free( sql_buf );
2738 // Append the literal value -- quoted if not a number
2739 if( JSON_NUMBER == in_item->type ) {
2740 char* val = jsonNumberToDBString( field, in_item );
2741 OSRF_BUFFER_ADD( sql_buf, val );
2744 } else if( !strcmp( get_primitive( field ), "number" )) {
2745 char* val = jsonNumberToDBString( field, in_item );
2746 OSRF_BUFFER_ADD( sql_buf, val );
2750 char* key_string = jsonObjectToSimpleString( in_item );
2751 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2752 OSRF_BUFFER_ADD( sql_buf, key_string );
2755 osrfLogError( OSRF_LOG_MARK,
2756 "%s: Error quoting key string [%s]", modulename, key_string );
2758 buffer_free( sql_buf );
2764 if( in_item_first ) {
2765 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2766 buffer_free( sql_buf );
2770 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2771 modulename, json_type( node->type ));
2772 buffer_free( sql_buf );
2776 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2778 return buffer_release( sql_buf );
2781 // Receive a JSON_ARRAY representing a function call. The first
2782 // entry in the array is the function name. The rest are parameters.
2783 static char* searchValueTransform( const jsonObject* array ) {
2785 if( array->size < 1 ) {
2786 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2790 // Get the function name
2791 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2792 if( func_item->type != JSON_STRING ) {
2793 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2794 modulename, json_type( func_item->type ));
2798 growing_buffer* sql_buf = buffer_init( 32 );
2800 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2801 OSRF_BUFFER_ADD( sql_buf, "( " );
2803 // Get the parameters
2804 int func_item_index = 1; // We already grabbed the zeroth entry
2805 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2807 // Add a separator comma, if we need one
2808 if( func_item_index > 2 )
2809 buffer_add( sql_buf, ", " );
2811 // Add the current parameter
2812 if( func_item->type == JSON_NULL ) {
2813 buffer_add( sql_buf, "NULL" );
2815 if( func_item->type == JSON_BOOL ) {
2816 if( jsonBoolIsTrue(func_item) ) {
2817 buffer_add( sql_buf, "TRUE" );
2819 buffer_add( sql_buf, "FALSE" );
2822 char* val = jsonObjectToSimpleString( func_item );
2823 if( dbi_conn_quote_string( dbhandle, &val )) {
2824 OSRF_BUFFER_ADD( sql_buf, val );
2827 osrfLogError( OSRF_LOG_MARK,
2828 "%s: Error quoting key string [%s]", modulename, val );
2829 buffer_free( sql_buf );
2837 buffer_add( sql_buf, " )" );
2839 return buffer_release( sql_buf );
2842 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2843 const jsonObject* node, const char* op ) {
2845 if( ! is_good_operator( op ) ) {
2846 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2850 char* val = searchValueTransform( node );
2854 const char* right_percent = "";
2855 const char* real_op = op;
2857 if( !strcasecmp( op, "startwith") ) {
2859 right_percent = "|| '%'";
2862 growing_buffer* sql_buf = buffer_init( 32 );
2865 "\"%s\".%s %s %s%s",
2867 osrfHashGet( field, "name" ),
2875 return buffer_release( sql_buf );
2878 // class_alias is a class name or other table alias
2879 // field is a field definition as stored in the IDL
2880 // node comes from the method parameter, and may represent an entry in the SELECT list
2881 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2882 const jsonObject* node ) {
2883 growing_buffer* sql_buf = buffer_init( 32 );
2885 const char* field_transform = jsonObjectGetString(
2886 jsonObjectGetKeyConst( node, "transform" ) );
2887 const char* transform_subcolumn = jsonObjectGetString(
2888 jsonObjectGetKeyConst( node, "result_field" ) );
2890 if( transform_subcolumn ) {
2891 if( ! is_identifier( transform_subcolumn ) ) {
2892 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2893 modulename, transform_subcolumn );
2894 buffer_free( sql_buf );
2897 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2900 if( field_transform ) {
2902 if( ! is_identifier( field_transform ) ) {
2903 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2904 modulename, field_transform );
2905 buffer_free( sql_buf );
2909 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2910 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2911 field_transform, class_alias, osrfHashGet( field, "name" ));
2913 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2914 field_transform, class_alias, osrfHashGet( field, "name" ));
2917 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2920 if( array->type != JSON_ARRAY ) {
2921 osrfLogError( OSRF_LOG_MARK,
2922 "%s: Expected JSON_ARRAY for function params; found %s",
2923 modulename, json_type( array->type ) );
2924 buffer_free( sql_buf );
2927 int func_item_index = 0;
2928 jsonObject* func_item;
2929 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2931 char* val = jsonObjectToSimpleString( func_item );
2934 buffer_add( sql_buf, ",NULL" );
2935 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2936 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2937 OSRF_BUFFER_ADD( sql_buf, val );
2939 osrfLogError( OSRF_LOG_MARK,
2940 "%s: Error quoting key string [%s]", modulename, val );
2942 buffer_free( sql_buf );
2949 buffer_add( sql_buf, " )" );
2952 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2955 if( transform_subcolumn )
2956 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2958 return buffer_release( sql_buf );
2961 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2962 const jsonObject* node, const char* op ) {
2964 if( ! is_good_operator( op ) ) {
2965 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2969 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2970 if( ! field_transform )
2973 int extra_parens = 0; // boolean
2975 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2977 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2979 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2981 free( field_transform );
2985 } else if( value_obj->type == JSON_ARRAY ) {
2986 value = searchValueTransform( value_obj );
2988 osrfLogError( OSRF_LOG_MARK,
2989 "%s: Error building value transform for field transform", modulename );
2990 free( field_transform );
2993 } else if( value_obj->type == JSON_HASH ) {
2994 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2996 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2998 free( field_transform );
3002 } else if( value_obj->type == JSON_NUMBER ) {
3003 value = jsonNumberToDBString( field, value_obj );
3004 } else if( value_obj->type == JSON_NULL ) {
3005 osrfLogError( OSRF_LOG_MARK,
3006 "%s: Error building predicate for field transform: null value", modulename );
3007 free( field_transform );
3009 } else if( value_obj->type == JSON_BOOL ) {
3010 osrfLogError( OSRF_LOG_MARK,
3011 "%s: Error building predicate for field transform: boolean value", modulename );
3012 free( field_transform );
3015 if( !strcmp( get_primitive( field ), "number") ) {
3016 value = jsonNumberToDBString( field, value_obj );
3018 value = jsonObjectToSimpleString( value_obj );
3019 if( !dbi_conn_quote_string( dbhandle, &value )) {
3020 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3021 modulename, value );
3023 free( field_transform );
3029 const char* left_parens = "";
3030 const char* right_parens = "";
3032 if( extra_parens ) {
3037 const char* right_percent = "";
3038 const char* real_op = op;
3040 if( !strcasecmp( op, "startwith") ) {
3042 right_percent = "|| '%'";
3045 growing_buffer* sql_buf = buffer_init( 32 );
3049 "%s%s %s %s %s%s %s%s",
3061 free( field_transform );
3063 return buffer_release( sql_buf );
3066 static char* searchSimplePredicate( const char* op, const char* class_alias,
3067 osrfHash* field, const jsonObject* node ) {
3069 if( ! is_good_operator( op ) ) {
3070 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
3076 // Get the value to which we are comparing the specified column
3077 if( node->type != JSON_NULL ) {
3078 if( node->type == JSON_NUMBER ) {
3079 val = jsonNumberToDBString( field, node );
3080 } else if( !strcmp( get_primitive( field ), "number" ) ) {
3081 val = jsonNumberToDBString( field, node );
3083 val = jsonObjectToSimpleString( node );
3088 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
3089 // Value is not numeric; enclose it in quotes
3090 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
3091 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3098 // Compare to a null value
3099 val = strdup( "NULL" );
3100 if( strcmp( op, "=" ))
3106 const char* right_percent = "";
3107 const char* real_op = op;
3109 if( !strcasecmp( op, "startwith") ) {
3111 right_percent = "|| '%'";
3114 growing_buffer* sql_buf = buffer_init( 32 );
3115 buffer_fadd( sql_buf, "\"%s\".%s %s %s%s", class_alias, osrfHashGet(field, "name"), real_op, val, right_percent );
3116 char* pred = buffer_release( sql_buf );
3123 static char* searchBETWEENPredicate( const char* class_alias,
3124 osrfHash* field, const jsonObject* node ) {
3126 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
3127 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
3129 if( NULL == y_node ) {
3130 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
3133 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
3134 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
3141 if( !strcmp( get_primitive( field ), "number") ) {
3142 x_string = jsonNumberToDBString( field, x_node );
3143 y_string = jsonNumberToDBString( field, y_node );
3146 x_string = jsonObjectToSimpleString( x_node );
3147 y_string = jsonObjectToSimpleString( y_node );
3148 if( !(dbi_conn_quote_string( dbhandle, &x_string )
3149 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
3150 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
3151 modulename, x_string, y_string );
3158 growing_buffer* sql_buf = buffer_init( 32 );
3159 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
3160 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
3164 return buffer_release( sql_buf );
3167 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
3168 jsonObject* node, osrfMethodContext* ctx ) {
3171 if( node->type == JSON_ARRAY ) { // equality IN search
3172 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
3173 } else if( node->type == JSON_HASH ) { // other search
3174 jsonIterator* pred_itr = jsonNewIterator( node );
3175 if( !jsonIteratorHasNext( pred_itr ) ) {
3176 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
3177 modulename, osrfHashGet(field, "name" ));
3179 jsonObject* pred_node = jsonIteratorNext( pred_itr );
3181 // Verify that there are no additional predicates
3182 if( jsonIteratorHasNext( pred_itr ) ) {
3183 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
3184 modulename, osrfHashGet(field, "name" ));
3185 } else if( !(strcasecmp( pred_itr->key,"between" )) )
3186 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
3187 else if( !(strcasecmp( pred_itr->key,"in" ))
3188 || !(strcasecmp( pred_itr->key,"not in" )) )
3189 pred = searchINPredicate(
3190 class_info->alias, field, pred_node, pred_itr->key, ctx );
3191 else if( pred_node->type == JSON_ARRAY )
3192 pred = searchFunctionPredicate(
3193 class_info->alias, field, pred_node, pred_itr->key );
3194 else if( pred_node->type == JSON_HASH )
3195 pred = searchFieldTransformPredicate(
3196 class_info, field, pred_node, pred_itr->key );
3198 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
3200 jsonIteratorFree( pred_itr );
3202 } else if( node->type == JSON_NULL ) { // IS NULL search
3203 growing_buffer* _p = buffer_init( 64 );
3206 "\"%s\".%s IS NULL",
3208 osrfHashGet( field, "name" )
3210 pred = buffer_release( _p );
3211 } else { // equality search
3212 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3231 field : call_number,
3247 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3249 const jsonObject* working_hash;
3250 jsonObject* freeable_hash = NULL;
3252 if( join_hash->type == JSON_HASH ) {
3253 working_hash = join_hash;
3254 } else if( join_hash->type == JSON_STRING ) {
3255 // turn it into a JSON_HASH by creating a wrapper
3256 // around a copy of the original
3257 const char* _tmp = jsonObjectGetString( join_hash );
3258 freeable_hash = jsonNewObjectType( JSON_HASH );
3259 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3260 working_hash = freeable_hash;
3264 "%s: JOIN failed; expected JSON object type not found",
3270 growing_buffer* join_buf = buffer_init( 128 );
3271 const char* leftclass = left_info->class_name;
3273 jsonObject* snode = NULL;
3274 jsonIterator* search_itr = jsonNewIterator( working_hash );
3276 while ( (snode = jsonIteratorNext( search_itr )) ) {
3277 const char* right_alias = search_itr->key;
3279 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3281 class = right_alias;
3283 const ClassInfo* right_info = add_joined_class( right_alias, class );
3287 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3291 jsonIteratorFree( search_itr );
3292 buffer_free( join_buf );
3294 jsonObjectFree( freeable_hash );
3297 osrfHash* links = right_info->links;
3298 const char* table = right_info->source_def;
3300 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3301 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3303 if( field && !fkey ) {
3304 // Look up the corresponding join column in the IDL.
3305 // The link must be defined in the child table,
3306 // and point to the right parent table.
3307 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3308 const char* reltype = NULL;
3309 const char* other_class = NULL;
3310 reltype = osrfHashGet( idl_link, "reltype" );
3311 if( reltype && strcmp( reltype, "has_many" ) )
3312 other_class = osrfHashGet( idl_link, "class" );
3313 if( other_class && !strcmp( other_class, leftclass ) )
3314 fkey = osrfHashGet( idl_link, "key" );
3318 "%s: JOIN failed. No link defined from %s.%s to %s",
3324 buffer_free( join_buf );
3326 jsonObjectFree( freeable_hash );
3327 jsonIteratorFree( search_itr );
3331 } else if( !field && fkey ) {
3332 // Look up the corresponding join column in the IDL.
3333 // The link must be defined in the child table,
3334 // and point to the right parent table.
3335 osrfHash* left_links = left_info->links;
3336 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3337 const char* reltype = NULL;
3338 const char* other_class = NULL;
3339 reltype = osrfHashGet( idl_link, "reltype" );
3340 if( reltype && strcmp( reltype, "has_many" ) )
3341 other_class = osrfHashGet( idl_link, "class" );
3342 if( other_class && !strcmp( other_class, class ) )
3343 field = osrfHashGet( idl_link, "key" );
3347 "%s: JOIN failed. No link defined from %s.%s to %s",
3353 buffer_free( join_buf );
3355 jsonObjectFree( freeable_hash );
3356 jsonIteratorFree( search_itr );
3360 } else if( !field && !fkey ) {
3361 osrfHash* left_links = left_info->links;
3363 // For each link defined for the left class:
3364 // see if the link references the joined class
3365 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3366 osrfHash* curr_link = NULL;
3367 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3368 const char* other_class = osrfHashGet( curr_link, "class" );
3369 if( other_class && !strcmp( other_class, class ) ) {
3371 // In the IDL, the parent class doesn't always know then names of the child
3372 // columns that are pointing to it, so don't use that end of the link
3373 const char* reltype = osrfHashGet( curr_link, "reltype" );
3374 if( reltype && strcmp( reltype, "has_many" ) ) {
3375 // Found a link between the classes
3376 fkey = osrfHashIteratorKey( itr );
3377 field = osrfHashGet( curr_link, "key" );
3382 osrfHashIteratorFree( itr );
3384 if( !field || !fkey ) {
3385 // Do another such search, with the classes reversed
3387 // For each link defined for the joined class:
3388 // see if the link references the left class
3389 osrfHashIterator* itr = osrfNewHashIterator( links );
3390 osrfHash* curr_link = NULL;
3391 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3392 const char* other_class = osrfHashGet( curr_link, "class" );
3393 if( other_class && !strcmp( other_class, leftclass ) ) {
3395 // In the IDL, the parent class doesn't know then names of the child
3396 // columns that are pointing to it, so don't use that end of the link
3397 const char* reltype = osrfHashGet( curr_link, "reltype" );
3398 if( reltype && strcmp( reltype, "has_many" ) ) {
3399 // Found a link between the classes
3400 field = osrfHashIteratorKey( itr );
3401 fkey = osrfHashGet( curr_link, "key" );
3406 osrfHashIteratorFree( itr );
3409 if( !field || !fkey ) {
3412 "%s: JOIN failed. No link defined between %s and %s",
3417 buffer_free( join_buf );
3419 jsonObjectFree( freeable_hash );
3420 jsonIteratorFree( search_itr );
3425 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3427 if( !strcasecmp( type,"left" )) {
3428 buffer_add( join_buf, " LEFT JOIN" );
3429 } else if( !strcasecmp( type,"right" )) {
3430 buffer_add( join_buf, " RIGHT JOIN" );
3431 } else if( !strcasecmp( type,"full" )) {
3432 buffer_add( join_buf, " FULL JOIN" );
3434 buffer_add( join_buf, " INNER JOIN" );
3437 buffer_add( join_buf, " INNER JOIN" );
3440 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3441 table, right_alias, right_alias, field, left_info->alias, fkey );
3443 // Add any other join conditions as specified by "filter"
3444 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3446 const char* filter_op = jsonObjectGetString(
3447 jsonObjectGetKeyConst( snode, "filter_op" ) );
3448 if( filter_op && !strcasecmp( "or",filter_op )) {
3449 buffer_add( join_buf, " OR " );
3451 buffer_add( join_buf, " AND " );
3454 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3456 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3457 OSRF_BUFFER_ADD( join_buf, jpred );
3462 "%s: JOIN failed. Invalid conditional expression.",
3465 jsonIteratorFree( search_itr );
3466 buffer_free( join_buf );
3468 jsonObjectFree( freeable_hash );
3473 buffer_add( join_buf, " ) " );
3475 // Recursively add a nested join, if one is present
3476 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3478 char* jpred = searchJOIN( join_filter, right_info );
3480 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3481 OSRF_BUFFER_ADD( join_buf, jpred );
3484 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3485 jsonIteratorFree( search_itr );
3486 buffer_free( join_buf );
3488 jsonObjectFree( freeable_hash );
3495 jsonObjectFree( freeable_hash );
3496 jsonIteratorFree( search_itr );
3498 return buffer_release( join_buf );
3503 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3504 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3505 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3507 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3509 search_hash is the JSON expression of the conditions.
3510 meta is the class definition from the IDL, for the relevant table.
3511 opjoin_type indicates whether multiple conditions, if present, should be
3512 connected by AND or OR.
3513 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3514 to pass it to other functions -- and all they do with it is to use the session
3515 and request members to send error messages back to the client.
3519 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3520 int opjoin_type, osrfMethodContext* ctx ) {
3524 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3525 "opjoin_type = %d, ctx addr = %p",
3528 class_info->class_def,
3533 growing_buffer* sql_buf = buffer_init( 128 );
3535 jsonObject* node = NULL;
3538 if( search_hash->type == JSON_ARRAY ) {
3539 if( 0 == search_hash->size ) {
3542 "%s: Invalid predicate structure: empty JSON array",
3545 buffer_free( sql_buf );
3549 unsigned long i = 0;
3550 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3554 if( opjoin_type == OR_OP_JOIN )
3555 buffer_add( sql_buf, " OR " );
3557 buffer_add( sql_buf, " AND " );
3560 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3562 buffer_free( sql_buf );
3566 buffer_fadd( sql_buf, "( %s )", subpred );
3570 } else if( search_hash->type == JSON_HASH ) {
3571 osrfLogDebug( OSRF_LOG_MARK,
3572 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3573 jsonIterator* search_itr = jsonNewIterator( search_hash );
3574 if( !jsonIteratorHasNext( search_itr ) ) {
3577 "%s: Invalid predicate structure: empty JSON object",
3580 jsonIteratorFree( search_itr );
3581 buffer_free( sql_buf );
3585 while( (node = jsonIteratorNext( search_itr )) ) {
3590 if( opjoin_type == OR_OP_JOIN )
3591 buffer_add( sql_buf, " OR " );
3593 buffer_add( sql_buf, " AND " );
3596 if( '+' == search_itr->key[ 0 ] ) {
3598 // This plus sign prefixes a class name or other table alias;
3599 // make sure the table alias is in scope
3600 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3601 if( ! alias_info ) {
3604 "%s: Invalid table alias \"%s\" in WHERE clause",
3608 jsonIteratorFree( search_itr );
3609 buffer_free( sql_buf );
3613 if( node->type == JSON_STRING ) {
3614 // It's the name of a column; make sure it belongs to the class
3615 const char* fieldname = jsonObjectGetString( node );
3616 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3619 "%s: Invalid column name \"%s\" in WHERE clause "
3620 "for table alias \"%s\"",
3625 jsonIteratorFree( search_itr );
3626 buffer_free( sql_buf );
3630 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3632 // It's something more complicated
3633 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3635 jsonIteratorFree( search_itr );
3636 buffer_free( sql_buf );
3640 buffer_fadd( sql_buf, "( %s )", subpred );
3643 } else if( '-' == search_itr->key[ 0 ] ) {
3644 if( !strcasecmp( "-or", search_itr->key )) {
3645 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3647 jsonIteratorFree( search_itr );
3648 buffer_free( sql_buf );
3652 buffer_fadd( sql_buf, "( %s )", subpred );
3654 } else if( !strcasecmp( "-and", search_itr->key )) {
3655 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3657 jsonIteratorFree( search_itr );
3658 buffer_free( sql_buf );
3662 buffer_fadd( sql_buf, "( %s )", subpred );
3664 } else if( !strcasecmp("-not",search_itr->key) ) {
3665 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3667 jsonIteratorFree( search_itr );
3668 buffer_free( sql_buf );
3672 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3674 } else if( !strcasecmp( "-exists", search_itr->key )) {
3675 char* subpred = buildQuery( ctx, node, SUBSELECT );
3677 jsonIteratorFree( search_itr );
3678 buffer_free( sql_buf );
3682 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3684 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3685 char* subpred = buildQuery( ctx, node, SUBSELECT );
3687 jsonIteratorFree( search_itr );
3688 buffer_free( sql_buf );
3692 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3694 } else { // Invalid "minus" operator
3697 "%s: Invalid operator \"%s\" in WHERE clause",
3701 jsonIteratorFree( search_itr );
3702 buffer_free( sql_buf );
3708 const char* class = class_info->class_name;
3709 osrfHash* fields = class_info->fields;
3710 osrfHash* field = osrfHashGet( fields, search_itr->key );
3713 const char* table = class_info->source_def;
3716 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3719 table ? table : "?",
3722 jsonIteratorFree( search_itr );
3723 buffer_free( sql_buf );
3727 char* subpred = searchPredicate( class_info, field, node, ctx );
3729 buffer_free( sql_buf );
3730 jsonIteratorFree( search_itr );
3734 buffer_add( sql_buf, subpred );
3738 jsonIteratorFree( search_itr );
3741 // ERROR ... only hash and array allowed at this level
3742 char* predicate_string = jsonObjectToJSON( search_hash );
3745 "%s: Invalid predicate structure: %s",
3749 buffer_free( sql_buf );
3750 free( predicate_string );
3754 return buffer_release( sql_buf );
3757 /* Build a JSON_ARRAY of field names for a given table alias
3759 static jsonObject* defaultSelectList( const char* table_alias ) {
3764 ClassInfo* class_info = search_all_alias( table_alias );
3765 if( ! class_info ) {
3768 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3775 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3776 osrfHash* field_def = NULL;
3777 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3778 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3779 const char* field_name = osrfHashIteratorKey( field_itr );
3780 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3781 jsonObjectPush( array, jsonNewObject( field_name ) );
3784 osrfHashIteratorFree( field_itr );
3789 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3790 // The jsonObject must be a JSON_HASH with an single entry for "union",
3791 // "intersect", or "except". The data associated with this key must be an
3792 // array of hashes, each hash being a query.
3793 // Also allowed but currently ignored: entries for "order_by" and "alias".
3794 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3796 if( ! combo || combo->type != JSON_HASH )
3797 return NULL; // should be impossible; validated by caller
3799 const jsonObject* query_array = NULL; // array of subordinate queries
3800 const char* op = NULL; // name of operator, e.g. UNION
3801 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3802 int op_count = 0; // for detecting conflicting operators
3803 int excepting = 0; // boolean
3804 int all = 0; // boolean
3805 jsonObject* order_obj = NULL;
3807 // Identify the elements in the hash
3808 jsonIterator* query_itr = jsonNewIterator( combo );
3809 jsonObject* curr_obj = NULL;
3810 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3811 if( ! strcmp( "union", query_itr->key ) ) {
3814 query_array = curr_obj;
3815 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3818 query_array = curr_obj;
3819 } else if( ! strcmp( "except", query_itr->key ) ) {
3823 query_array = curr_obj;
3824 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3827 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3830 order_obj = curr_obj;
3831 } else if( ! strcmp( "alias", query_itr->key ) ) {
3832 if( curr_obj->type != JSON_STRING ) {
3833 jsonIteratorFree( query_itr );
3836 alias = jsonObjectGetString( curr_obj );
3837 } else if( ! strcmp( "all", query_itr->key ) ) {
3838 if( obj_is_true( curr_obj ) )
3842 osrfAppSessionStatus(
3844 OSRF_STATUS_INTERNALSERVERERROR,
3845 "osrfMethodException",
3847 "Malformed query; unexpected entry in query object"
3851 "%s: Unexpected entry for \"%s\" in%squery",
3856 jsonIteratorFree( query_itr );
3860 jsonIteratorFree( query_itr );
3862 // More sanity checks
3863 if( ! query_array ) {
3865 osrfAppSessionStatus(
3867 OSRF_STATUS_INTERNALSERVERERROR,
3868 "osrfMethodException",
3870 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3874 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3877 return NULL; // should be impossible...
3878 } else if( op_count > 1 ) {
3880 osrfAppSessionStatus(
3882 OSRF_STATUS_INTERNALSERVERERROR,
3883 "osrfMethodException",
3885 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3889 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3893 } if( query_array->type != JSON_ARRAY ) {
3895 osrfAppSessionStatus(
3897 OSRF_STATUS_INTERNALSERVERERROR,
3898 "osrfMethodException",
3900 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3904 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3907 json_type( query_array->type )
3910 } if( query_array->size < 2 ) {
3912 osrfAppSessionStatus(
3914 OSRF_STATUS_INTERNALSERVERERROR,
3915 "osrfMethodException",
3917 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3921 "%s:%srequires multiple queries as operands",
3926 } else if( excepting && query_array->size > 2 ) {
3928 osrfAppSessionStatus(
3930 OSRF_STATUS_INTERNALSERVERERROR,
3931 "osrfMethodException",
3933 "EXCEPT operator has too many queries as operands"
3937 "%s:EXCEPT operator has too many queries as operands",
3941 } else if( order_obj && ! alias ) {
3943 osrfAppSessionStatus(
3945 OSRF_STATUS_INTERNALSERVERERROR,
3946 "osrfMethodException",
3948 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3952 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3958 // So far so good. Now build the SQL.
3959 growing_buffer* sql = buffer_init( 256 );
3961 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3962 // Add a layer of parentheses
3963 if( flags & SUBCOMBO )
3964 OSRF_BUFFER_ADD( sql, "( " );
3966 // Traverse the query array. Each entry should be a hash.
3967 int first = 1; // boolean
3969 jsonObject* query = NULL;
3970 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3971 if( query->type != JSON_HASH ) {
3973 osrfAppSessionStatus(
3975 OSRF_STATUS_INTERNALSERVERERROR,
3976 "osrfMethodException",
3978 "Malformed query under UNION, INTERSECT or EXCEPT"
3982 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3985 json_type( query->type )
3994 OSRF_BUFFER_ADD( sql, op );
3996 OSRF_BUFFER_ADD( sql, "ALL " );
3999 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
4003 "%s: Error building query under%s",
4011 OSRF_BUFFER_ADD( sql, query_str );
4014 if( flags & SUBCOMBO )
4015 OSRF_BUFFER_ADD_CHAR( sql, ')' );
4017 if( !(flags & SUBSELECT) )
4018 OSRF_BUFFER_ADD_CHAR( sql, ';' );
4020 return buffer_release( sql );
4023 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
4024 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
4025 // or "except" to indicate the type of query.
4026 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
4030 osrfAppSessionStatus(
4032 OSRF_STATUS_INTERNALSERVERERROR,
4033 "osrfMethodException",
4035 "Malformed query; no query object"
4037 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
4039 } else if( query->type != JSON_HASH ) {
4041 osrfAppSessionStatus(
4043 OSRF_STATUS_INTERNALSERVERERROR,
4044 "osrfMethodException",
4046 "Malformed query object"
4050 "%s: Query object is %s instead of JSON_HASH",
4052 json_type( query->type )
4057 // Determine what kind of query it purports to be, and dispatch accordingly.
4058 if( jsonObjectGetKeyConst( query, "union" ) ||
4059 jsonObjectGetKeyConst( query, "intersect" ) ||
4060 jsonObjectGetKeyConst( query, "except" )) {
4061 return doCombo( ctx, query, flags );
4063 // It is presumably a SELECT query
4065 // Push a node onto the stack for the current query. Every level of
4066 // subquery gets its own QueryFrame on the Stack.
4069 // Build an SQL SELECT statement
4072 jsonObjectGetKey( query, "select" ),
4073 jsonObjectGetKeyConst( query, "from" ),
4074 jsonObjectGetKeyConst( query, "where" ),
4075 jsonObjectGetKeyConst( query, "having" ),
4076 jsonObjectGetKeyConst( query, "order_by" ),
4077 jsonObjectGetKeyConst( query, "limit" ),
4078 jsonObjectGetKeyConst( query, "offset" ),
4087 /* method context */ osrfMethodContext* ctx,
4089 /* SELECT */ jsonObject* selhash,
4090 /* FROM */ const jsonObject* join_hash,
4091 /* WHERE */ const jsonObject* search_hash,
4092 /* HAVING */ const jsonObject* having_hash,
4093 /* ORDER BY */ const jsonObject* order_hash,
4094 /* LIMIT */ const jsonObject* limit,
4095 /* OFFSET */ const jsonObject* offset,
4096 /* flags */ int flags
4098 const char* locale = osrf_message_get_last_locale();
4100 // general tmp objects
4101 const jsonObject* tmp_const;
4102 jsonObject* selclass = NULL;
4103 jsonObject* snode = NULL;
4104 jsonObject* onode = NULL;
4106 char* string = NULL;
4107 int from_function = 0;
4112 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
4114 // punt if there's no FROM clause
4115 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
4118 "%s: FROM clause is missing or empty",
4122 osrfAppSessionStatus(
4124 OSRF_STATUS_INTERNALSERVERERROR,
4125 "osrfMethodException",
4127 "FROM clause is missing or empty in JSON query"
4132 // the core search class
4133 const char* core_class = NULL;
4135 // get the core class -- the only key of the top level FROM clause, or a string
4136 if( join_hash->type == JSON_HASH ) {
4137 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
4138 snode = jsonIteratorNext( tmp_itr );
4140 // Populate the current QueryFrame with information
4141 // about the core class
4142 if( add_query_core( NULL, tmp_itr->key ) ) {
4144 osrfAppSessionStatus(
4146 OSRF_STATUS_INTERNALSERVERERROR,
4147 "osrfMethodException",
4149 "Unable to look up core class"
4153 core_class = curr_query->core.class_name;
4156 jsonObject* extra = jsonIteratorNext( tmp_itr );
4158 jsonIteratorFree( tmp_itr );
4161 // There shouldn't be more than one entry in join_hash
4165 "%s: Malformed FROM clause: extra entry in JSON_HASH",
4169 osrfAppSessionStatus(
4171 OSRF_STATUS_INTERNALSERVERERROR,
4172 "osrfMethodException",
4174 "Malformed FROM clause in JSON query"
4176 return NULL; // Malformed join_hash; extra entry
4178 } else if( join_hash->type == JSON_ARRAY ) {
4179 // We're selecting from a function, not from a table
4181 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
4184 } else if( join_hash->type == JSON_STRING ) {
4185 // Populate the current QueryFrame with information
4186 // about the core class
4187 core_class = jsonObjectGetString( join_hash );
4189 if( add_query_core( NULL, core_class ) ) {
4191 osrfAppSessionStatus(
4193 OSRF_STATUS_INTERNALSERVERERROR,
4194 "osrfMethodException",
4196 "Unable to look up core class"
4204 "%s: FROM clause is unexpected JSON type: %s",
4206 json_type( join_hash->type )
4209 osrfAppSessionStatus(
4211 OSRF_STATUS_INTERNALSERVERERROR,
4212 "osrfMethodException",
4214 "Ill-formed FROM clause in JSON query"
4219 // Build the join clause, if any, while filling out the list
4220 // of joined classes in the current QueryFrame.
4221 char* join_clause = NULL;
4222 if( join_hash && ! from_function ) {
4224 join_clause = searchJOIN( join_hash, &curr_query->core );
4225 if( ! join_clause ) {
4227 osrfAppSessionStatus(
4229 OSRF_STATUS_INTERNALSERVERERROR,
4230 "osrfMethodException",
4232 "Unable to construct JOIN clause(s)"
4238 // For in case we don't get a select list
4239 jsonObject* defaultselhash = NULL;
4241 // if there is no select list, build a default select list ...
4242 if( !selhash && !from_function ) {
4243 jsonObject* default_list = defaultSelectList( core_class );
4244 if( ! default_list ) {
4246 osrfAppSessionStatus(
4248 OSRF_STATUS_INTERNALSERVERERROR,
4249 "osrfMethodException",
4251 "Unable to build default SELECT clause in JSON query"
4253 free( join_clause );
4258 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4259 jsonObjectSetKey( selhash, core_class, default_list );
4262 // The SELECT clause can be encoded only by a hash
4263 if( !from_function && selhash->type != JSON_HASH ) {
4266 "%s: Expected JSON_HASH for SELECT clause; found %s",
4268 json_type( selhash->type )
4272 osrfAppSessionStatus(
4274 OSRF_STATUS_INTERNALSERVERERROR,
4275 "osrfMethodException",
4277 "Malformed SELECT clause in JSON query"
4279 free( join_clause );
4283 // If you see a null or wild card specifier for the core class, or an
4284 // empty array, replace it with a default SELECT list
4285 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4287 int default_needed = 0; // boolean
4288 if( JSON_STRING == tmp_const->type
4289 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4291 else if( JSON_NULL == tmp_const->type )
4294 if( default_needed ) {
4295 // Build a default SELECT list
4296 jsonObject* default_list = defaultSelectList( core_class );
4297 if( ! default_list ) {
4299 osrfAppSessionStatus(
4301 OSRF_STATUS_INTERNALSERVERERROR,
4302 "osrfMethodException",
4304 "Can't build default SELECT clause in JSON query"
4306 free( join_clause );
4311 jsonObjectSetKey( selhash, core_class, default_list );
4315 // temp buffers for the SELECT list and GROUP BY clause
4316 growing_buffer* select_buf = buffer_init( 128 );
4317 growing_buffer* group_buf = buffer_init( 128 );
4319 int aggregate_found = 0; // boolean
4321 // Build a select list
4322 if( from_function ) // From a function we select everything
4323 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4326 // Build the SELECT list as SQL
4330 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4331 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4333 const char* cname = selclass_itr->key;
4335 // Make sure the target relation is in the FROM clause.
4337 // At this point join_hash is a step down from the join_hash we
4338 // received as a parameter. If the original was a JSON_STRING,
4339 // then json_hash is now NULL. If the original was a JSON_HASH,
4340 // then json_hash is now the first (and only) entry in it,
4341 // denoting the core class. We've already excluded the
4342 // possibility that the original was a JSON_ARRAY, because in
4343 // that case from_function would be non-NULL, and we wouldn't
4346 // If the current table alias isn't in scope, bail out
4347 ClassInfo* class_info = search_alias( cname );
4348 if( ! class_info ) {
4351 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4356 osrfAppSessionStatus(
4358 OSRF_STATUS_INTERNALSERVERERROR,
4359 "osrfMethodException",
4361 "Selected class not in FROM clause in JSON query"
4363 jsonIteratorFree( selclass_itr );
4364 buffer_free( select_buf );
4365 buffer_free( group_buf );
4366 if( defaultselhash )
4367 jsonObjectFree( defaultselhash );
4368 free( join_clause );
4372 if( selclass->type != JSON_ARRAY ) {
4375 "%s: Malformed SELECT list for class \"%s\"; not an array",
4380 osrfAppSessionStatus(
4382 OSRF_STATUS_INTERNALSERVERERROR,
4383 "osrfMethodException",
4385 "Selected class not in FROM clause in JSON query"
4388 jsonIteratorFree( selclass_itr );
4389 buffer_free( select_buf );
4390 buffer_free( group_buf );
4391 if( defaultselhash )
4392 jsonObjectFree( defaultselhash );
4393 free( join_clause );
4397 // Look up some attributes of the current class
4398 osrfHash* idlClass = class_info->class_def;
4399 osrfHash* class_field_set = class_info->fields;
4400 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4401 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4403 if( 0 == selclass->size ) {
4406 "%s: No columns selected from \"%s\"",
4412 // stitch together the column list for the current table alias...
4413 unsigned long field_idx = 0;
4414 jsonObject* selfield = NULL;
4415 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4417 // If we need a separator comma, add one
4421 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4424 // if the field specification is a string, add it to the list
4425 if( selfield->type == JSON_STRING ) {
4427 // Look up the field in the IDL
4428 const char* col_name = jsonObjectGetString( selfield );
4429 osrfHash* field_def = NULL;
4431 if (!osrfStringArrayContains(
4433 osrfHashGet( class_field_set, col_name ),
4434 "suppress_controller"),
4437 field_def = osrfHashGet( class_field_set, col_name );
4440 // No such field in current class
4443 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4449 osrfAppSessionStatus(
4451 OSRF_STATUS_INTERNALSERVERERROR,
4452 "osrfMethodException",
4454 "Selected column not defined in JSON query"
4456 jsonIteratorFree( selclass_itr );
4457 buffer_free( select_buf );
4458 buffer_free( group_buf );
4459 if( defaultselhash )
4460 jsonObjectFree( defaultselhash );
4461 free( join_clause );
4463 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4464 // Virtual field not allowed
4467 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4473 osrfAppSessionStatus(
4475 OSRF_STATUS_INTERNALSERVERERROR,
4476 "osrfMethodException",
4478 "Selected column may not be virtual in JSON query"
4480 jsonIteratorFree( selclass_itr );
4481 buffer_free( select_buf );
4482 buffer_free( group_buf );
4483 if( defaultselhash )
4484 jsonObjectFree( defaultselhash );
4485 free( join_clause );
4491 if( flags & DISABLE_I18N )
4494 i18n = osrfHashGet( field_def, "i18n" );
4496 if( str_is_true( i18n ) ) {
4497 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4498 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4499 class_tname, cname, col_name, class_pkey,
4500 cname, class_pkey, locale, col_name );
4502 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4503 cname, col_name, col_name );
4506 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4507 cname, col_name, col_name );
4510 // ... but it could be an object, in which case we check for a Field Transform
4511 } else if( selfield->type == JSON_HASH ) {
4513 const char* col_name = jsonObjectGetString(
4514 jsonObjectGetKeyConst( selfield, "column" ) );
4516 // Get the field definition from the IDL
4517 osrfHash* field_def = NULL;
4518 if (!osrfStringArrayContains(
4520 osrfHashGet( class_field_set, col_name ),
4521 "suppress_controller"),
4524 field_def = osrfHashGet( class_field_set, col_name );
4528 // No such field in current class
4531 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4537 osrfAppSessionStatus(
4539 OSRF_STATUS_INTERNALSERVERERROR,
4540 "osrfMethodException",
4542 "Selected column is not defined in JSON query"
4544 jsonIteratorFree( selclass_itr );
4545 buffer_free( select_buf );
4546 buffer_free( group_buf );
4547 if( defaultselhash )
4548 jsonObjectFree( defaultselhash );
4549 free( join_clause );
4551 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4552 // No such field in current class
4555 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4561 osrfAppSessionStatus(
4563 OSRF_STATUS_INTERNALSERVERERROR,
4564 "osrfMethodException",
4566 "Selected column is virtual in JSON query"
4568 jsonIteratorFree( selclass_itr );
4569 buffer_free( select_buf );
4570 buffer_free( group_buf );
4571 if( defaultselhash )
4572 jsonObjectFree( defaultselhash );
4573 free( join_clause );
4577 // Decide what to use as a column alias
4579 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4580 _alias = jsonObjectGetString( tmp_const );
4581 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4582 _alias = jsonObjectGetString( tmp_const );
4583 } else { // Use field name as the alias
4587 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4588 char* transform_str = searchFieldTransform(
4589 class_info->alias, field_def, selfield );
4590 if( transform_str ) {
4591 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4592 free( transform_str );
4595 osrfAppSessionStatus(
4597 OSRF_STATUS_INTERNALSERVERERROR,
4598 "osrfMethodException",
4600 "Unable to generate transform function in JSON query"
4602 jsonIteratorFree( selclass_itr );
4603 buffer_free( select_buf );
4604 buffer_free( group_buf );
4605 if( defaultselhash )
4606 jsonObjectFree( defaultselhash );
4607 free( join_clause );
4614 if( flags & DISABLE_I18N )
4617 i18n = osrfHashGet( field_def, "i18n" );
4619 if( str_is_true( i18n ) ) {
4620 buffer_fadd( select_buf,
4621 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4622 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4623 class_tname, cname, col_name, class_pkey, cname,
4624 class_pkey, locale, _alias );
4626 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4627 cname, col_name, _alias );
4630 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4631 cname, col_name, _alias );
4638 "%s: Selected item is unexpected JSON type: %s",
4640 json_type( selfield->type )
4643 osrfAppSessionStatus(
4645 OSRF_STATUS_INTERNALSERVERERROR,
4646 "osrfMethodException",
4648 "Ill-formed SELECT item in JSON query"
4650 jsonIteratorFree( selclass_itr );
4651 buffer_free( select_buf );
4652 buffer_free( group_buf );
4653 if( defaultselhash )
4654 jsonObjectFree( defaultselhash );
4655 free( join_clause );
4659 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4660 if( obj_is_true( agg_obj ) )
4661 aggregate_found = 1;
4663 // Append a comma (except for the first one)
4664 // and add the column to a GROUP BY clause
4668 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4670 buffer_fadd( group_buf, " %d", sel_pos );
4674 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4676 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( selfield, "aggregate");
4677 if ( ! obj_is_true( aggregate_obj ) ) {
4681 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4684 buffer_fadd(group_buf, " %d", sel_pos);
4687 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4691 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4694 _column = searchFieldTransform(class_info->alias, field, selfield);
4695 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4696 OSRF_BUFFER_ADD(group_buf, _column);
4697 _column = searchFieldTransform(class_info->alias, field, selfield);
4704 } // end while -- iterating across SELECT columns
4706 } // end while -- iterating across classes
4708 jsonIteratorFree( selclass_itr );
4711 char* col_list = buffer_release( select_buf );
4713 // Make sure the SELECT list isn't empty. This can happen, for example,
4714 // if we try to build a default SELECT clause from a non-core table.
4717 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4719 osrfAppSessionStatus(
4721 OSRF_STATUS_INTERNALSERVERERROR,
4722 "osrfMethodException",
4724 "SELECT list is empty"
4727 buffer_free( group_buf );
4728 if( defaultselhash )
4729 jsonObjectFree( defaultselhash );
4730 free( join_clause );
4736 table = searchValueTransform( join_hash );
4738 table = strdup( curr_query->core.source_def );
4742 osrfAppSessionStatus(
4744 OSRF_STATUS_INTERNALSERVERERROR,
4745 "osrfMethodException",
4747 "Unable to identify table for core class"
4750 buffer_free( group_buf );
4751 if( defaultselhash )
4752 jsonObjectFree( defaultselhash );
4753 free( join_clause );
4757 // Put it all together
4758 growing_buffer* sql_buf = buffer_init( 128 );
4759 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4763 // Append the join clause, if any
4765 buffer_add(sql_buf, join_clause );
4766 free( join_clause );
4769 char* order_by_list = NULL;
4770 char* having_buf = NULL;
4772 if( !from_function ) {
4774 // Build a WHERE clause, if there is one
4776 buffer_add( sql_buf, " WHERE " );
4778 // and it's on the WHERE clause
4779 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4782 osrfAppSessionStatus(
4784 OSRF_STATUS_INTERNALSERVERERROR,
4785 "osrfMethodException",
4787 "Severe query error in WHERE predicate -- see error log for more details"
4790 buffer_free( group_buf );
4791 buffer_free( sql_buf );
4792 if( defaultselhash )
4793 jsonObjectFree( defaultselhash );
4797 buffer_add( sql_buf, pred );
4801 // Build a HAVING clause, if there is one
4804 // and it's on the the WHERE clause
4805 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4807 if( ! having_buf ) {
4809 osrfAppSessionStatus(
4811 OSRF_STATUS_INTERNALSERVERERROR,
4812 "osrfMethodException",
4814 "Severe query error in HAVING predicate -- see error log for more details"
4817 buffer_free( group_buf );
4818 buffer_free( sql_buf );
4819 if( defaultselhash )
4820 jsonObjectFree( defaultselhash );
4825 // Build an ORDER BY clause, if there is one
4826 if( NULL == order_hash )
4827 ; // No ORDER BY? do nothing
4828 else if( JSON_ARRAY == order_hash->type ) {
4829 order_by_list = buildOrderByFromArray( ctx, order_hash );
4830 if( !order_by_list ) {
4832 buffer_free( group_buf );
4833 buffer_free( sql_buf );
4834 if( defaultselhash )
4835 jsonObjectFree( defaultselhash );
4838 } else if( JSON_HASH == order_hash->type ) {
4839 // This hash is keyed on class alias. Each class has either
4840 // an array of field names or a hash keyed on field name.
4841 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4842 jsonIterator* class_itr = jsonNewIterator( order_hash );
4843 while( (snode = jsonIteratorNext( class_itr )) ) {
4845 ClassInfo* order_class_info = search_alias( class_itr->key );
4846 if( ! order_class_info ) {
4847 osrfLogError( OSRF_LOG_MARK,
4848 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4849 modulename, class_itr->key );
4851 osrfAppSessionStatus(
4853 OSRF_STATUS_INTERNALSERVERERROR,
4854 "osrfMethodException",
4856 "Invalid class referenced in ORDER BY clause -- "
4857 "see error log for more details"
4859 jsonIteratorFree( class_itr );
4860 buffer_free( order_buf );
4862 buffer_free( group_buf );
4863 buffer_free( sql_buf );
4864 if( defaultselhash )
4865 jsonObjectFree( defaultselhash );
4869 osrfHash* field_list_def = order_class_info->fields;
4871 if( snode->type == JSON_HASH ) {
4873 // Hash is keyed on field names from the current class. For each field
4874 // there is another layer of hash to define the sorting details, if any,
4875 // or a string to indicate direction of sorting.
4876 jsonIterator* order_itr = jsonNewIterator( snode );
4877 while( (onode = jsonIteratorNext( order_itr )) ) {
4879 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4881 osrfLogError( OSRF_LOG_MARK,
4882 "%s: Invalid field \"%s\" in ORDER BY clause",
4883 modulename, order_itr->key );
4885 osrfAppSessionStatus(
4887 OSRF_STATUS_INTERNALSERVERERROR,
4888 "osrfMethodException",
4890 "Invalid field in ORDER BY clause -- "
4891 "see error log for more details"
4893 jsonIteratorFree( order_itr );
4894 jsonIteratorFree( class_itr );
4895 buffer_free( order_buf );
4897 buffer_free( group_buf );
4898 buffer_free( sql_buf );
4899 if( defaultselhash )
4900 jsonObjectFree( defaultselhash );
4902 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4903 osrfLogError( OSRF_LOG_MARK,
4904 "%s: Virtual field \"%s\" in ORDER BY clause",
4905 modulename, order_itr->key );
4907 osrfAppSessionStatus(
4909 OSRF_STATUS_INTERNALSERVERERROR,
4910 "osrfMethodException",
4912 "Virtual field in ORDER BY clause -- "
4913 "see error log for more details"
4915 jsonIteratorFree( order_itr );
4916 jsonIteratorFree( class_itr );
4917 buffer_free( order_buf );
4919 buffer_free( group_buf );
4920 buffer_free( sql_buf );
4921 if( defaultselhash )
4922 jsonObjectFree( defaultselhash );
4926 const char* direction = NULL;
4927 if( onode->type == JSON_HASH ) {
4928 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4929 string = searchFieldTransform(
4931 osrfHashGet( field_list_def, order_itr->key ),
4935 if( ctx ) osrfAppSessionStatus(
4937 OSRF_STATUS_INTERNALSERVERERROR,
4938 "osrfMethodException",
4940 "Severe query error in ORDER BY clause -- "
4941 "see error log for more details"
4943 jsonIteratorFree( order_itr );
4944 jsonIteratorFree( class_itr );
4946 buffer_free( group_buf );
4947 buffer_free( order_buf);
4948 buffer_free( sql_buf );
4949 if( defaultselhash )
4950 jsonObjectFree( defaultselhash );
4954 growing_buffer* field_buf = buffer_init( 16 );
4955 buffer_fadd( field_buf, "\"%s\".%s",
4956 class_itr->key, order_itr->key );
4957 string = buffer_release( field_buf );
4960 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4961 const char* dir = jsonObjectGetString( tmp_const );
4962 if(!strncasecmp( dir, "d", 1 )) {
4963 direction = " DESC";
4969 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4970 osrfLogError( OSRF_LOG_MARK,
4971 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4972 modulename, json_type( onode->type ) );
4974 osrfAppSessionStatus(
4976 OSRF_STATUS_INTERNALSERVERERROR,
4977 "osrfMethodException",
4979 "Malformed ORDER BY clause -- see error log for more details"
4981 jsonIteratorFree( order_itr );
4982 jsonIteratorFree( class_itr );
4984 buffer_free( group_buf );
4985 buffer_free( order_buf );
4986 buffer_free( sql_buf );
4987 if( defaultselhash )
4988 jsonObjectFree( defaultselhash );
4992 string = strdup( order_itr->key );
4993 const char* dir = jsonObjectGetString( onode );
4994 if( !strncasecmp( dir, "d", 1 )) {
4995 direction = " DESC";
5002 OSRF_BUFFER_ADD( order_buf, ", " );
5004 order_buf = buffer_init( 128 );
5006 OSRF_BUFFER_ADD( order_buf, string );
5010 OSRF_BUFFER_ADD( order_buf, direction );
5014 jsonIteratorFree( order_itr );
5016 } else if( snode->type == JSON_ARRAY ) {
5018 // Array is a list of fields from the current class
5019 unsigned long order_idx = 0;
5020 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
5022 const char* _f = jsonObjectGetString( onode );
5024 osrfHash* field_def = osrfHashGet( field_list_def, _f );
5026 osrfLogError( OSRF_LOG_MARK,
5027 "%s: Invalid field \"%s\" in ORDER BY clause",
5030 osrfAppSessionStatus(
5032 OSRF_STATUS_INTERNALSERVERERROR,
5033 "osrfMethodException",
5035 "Invalid field in ORDER BY clause -- "
5036 "see error log for more details"
5038 jsonIteratorFree( class_itr );
5039 buffer_free( order_buf );
5041 buffer_free( group_buf );
5042 buffer_free( sql_buf );
5043 if( defaultselhash )
5044 jsonObjectFree( defaultselhash );
5046 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5047 osrfLogError( OSRF_LOG_MARK,
5048 "%s: Virtual field \"%s\" in ORDER BY clause",
5051 osrfAppSessionStatus(
5053 OSRF_STATUS_INTERNALSERVERERROR,
5054 "osrfMethodException",
5056 "Virtual field in ORDER BY clause -- "
5057 "see error log for more details"
5059 jsonIteratorFree( class_itr );
5060 buffer_free( order_buf );
5062 buffer_free( group_buf );
5063 buffer_free( sql_buf );
5064 if( defaultselhash )
5065 jsonObjectFree( defaultselhash );
5070 OSRF_BUFFER_ADD( order_buf, ", " );
5072 order_buf = buffer_init( 128 );
5074 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
5078 // IT'S THE OOOOOOOOOOOLD STYLE!
5080 osrfLogError( OSRF_LOG_MARK,
5081 "%s: Possible SQL injection attempt; direct order by is not allowed",
5084 osrfAppSessionStatus(
5086 OSRF_STATUS_INTERNALSERVERERROR,
5087 "osrfMethodException",
5089 "Severe query error -- see error log for more details"
5094 buffer_free( group_buf );
5095 buffer_free( order_buf );
5096 buffer_free( sql_buf );
5097 if( defaultselhash )
5098 jsonObjectFree( defaultselhash );
5099 jsonIteratorFree( class_itr );
5103 jsonIteratorFree( class_itr );
5105 order_by_list = buffer_release( order_buf );
5107 osrfLogError( OSRF_LOG_MARK,
5108 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
5109 modulename, json_type( order_hash->type ) );
5111 osrfAppSessionStatus(
5113 OSRF_STATUS_INTERNALSERVERERROR,
5114 "osrfMethodException",
5116 "Malformed ORDER BY clause -- see error log for more details"
5119 buffer_free( group_buf );
5120 buffer_free( sql_buf );
5121 if( defaultselhash )
5122 jsonObjectFree( defaultselhash );
5127 string = buffer_release( group_buf );
5129 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
5130 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
5131 OSRF_BUFFER_ADD( sql_buf, string );
5136 if( having_buf && *having_buf ) {
5137 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
5138 OSRF_BUFFER_ADD( sql_buf, having_buf );
5142 if( order_by_list ) {
5144 if( *order_by_list ) {
5145 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5146 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5149 free( order_by_list );
5153 const char* str = jsonObjectGetString( limit );
5154 if (str) { // limit could be JSON_NULL, etc.
5155 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
5160 const char* str = jsonObjectGetString( offset );
5162 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
5166 if( !(flags & SUBSELECT) )
5167 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5169 if( defaultselhash )
5170 jsonObjectFree( defaultselhash );
5172 return buffer_release( sql_buf );
5174 } // end of SELECT()
5177 @brief Build a list of ORDER BY expressions.
5178 @param ctx Pointer to the method context.
5179 @param order_array Pointer to a JSON_ARRAY of field specifications.
5180 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
5181 Each expression may be either a column reference or a function call whose first parameter
5182 is a column reference.
5184 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
5185 It may optionally include entries for "direction" and/or "transform".
5187 The calling code is responsible for freeing the returned string.
5189 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
5190 if( ! order_array ) {
5191 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
5194 osrfAppSessionStatus(
5196 OSRF_STATUS_INTERNALSERVERERROR,
5197 "osrfMethodException",
5199 "Logic error: ORDER BY clause expected, not found; "
5200 "see error log for more details"
5203 } else if( order_array->type != JSON_ARRAY ) {
5204 osrfLogError( OSRF_LOG_MARK,
5205 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
5207 osrfAppSessionStatus(
5209 OSRF_STATUS_INTERNALSERVERERROR,
5210 "osrfMethodException",
5212 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
5216 growing_buffer* order_buf = buffer_init( 128 );
5217 int first = 1; // boolean
5219 jsonObject* order_spec;
5220 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
5222 if( JSON_HASH != order_spec->type ) {
5223 osrfLogError( OSRF_LOG_MARK,
5224 "%s: Malformed field specification in ORDER BY clause; "
5225 "expected JSON_HASH, found %s",
5226 modulename, json_type( order_spec->type ) );
5228 osrfAppSessionStatus(
5230 OSRF_STATUS_INTERNALSERVERERROR,
5231 "osrfMethodException",
5233 "Malformed ORDER BY clause -- see error log for more details"
5235 buffer_free( order_buf );
5239 const char* class_alias =
5240 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5242 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5244 jsonObject* compare_to = jsonObjectGetKey( order_spec, "compare" );
5246 if( !field || !class_alias ) {
5247 osrfLogError( OSRF_LOG_MARK,
5248 "%s: Missing class or field name in field specification of ORDER BY clause",
5251 osrfAppSessionStatus(
5253 OSRF_STATUS_INTERNALSERVERERROR,
5254 "osrfMethodException",
5256 "Malformed ORDER BY clause -- see error log for more details"
5258 buffer_free( order_buf );
5262 const ClassInfo* order_class_info = search_alias( class_alias );
5263 if( ! order_class_info ) {
5264 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5265 "not in FROM clause, skipping it", modulename, class_alias );
5269 // Add a separating comma, except at the beginning
5273 OSRF_BUFFER_ADD( order_buf, ", " );
5275 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5277 osrfLogError( OSRF_LOG_MARK,
5278 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5279 modulename, class_alias, field );
5281 osrfAppSessionStatus(
5283 OSRF_STATUS_INTERNALSERVERERROR,
5284 "osrfMethodException",
5286 "Invalid field referenced in ORDER BY clause -- "
5287 "see error log for more details"
5291 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5292 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5293 modulename, field );
5295 osrfAppSessionStatus(
5297 OSRF_STATUS_INTERNALSERVERERROR,
5298 "osrfMethodException",
5300 "Virtual field in ORDER BY clause -- see error log for more details"
5302 buffer_free( order_buf );
5306 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5307 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5308 if( ! transform_str ) {
5310 osrfAppSessionStatus(
5312 OSRF_STATUS_INTERNALSERVERERROR,
5313 "osrfMethodException",
5315 "Severe query error in ORDER BY clause -- "
5316 "see error log for more details"
5318 buffer_free( order_buf );
5322 OSRF_BUFFER_ADD( order_buf, transform_str );
5323 free( transform_str );
5324 } else if( compare_to ) {
5325 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5326 if( ! compare_str ) {
5328 osrfAppSessionStatus(
5330 OSRF_STATUS_INTERNALSERVERERROR,
5331 "osrfMethodException",
5333 "Severe query error in ORDER BY clause -- "
5334 "see error log for more details"
5336 buffer_free( order_buf );
5340 buffer_fadd( order_buf, "(%s)", compare_str );
5341 free( compare_str );
5344 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5346 const char* direction =
5347 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5349 if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5350 OSRF_BUFFER_ADD( order_buf, " DESC" );
5352 OSRF_BUFFER_ADD( order_buf, " ASC" );
5356 return buffer_release( order_buf );
5360 @brief Build a SELECT statement.
5361 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5362 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5363 @param meta Pointer to the class metadata for the core class.
5364 @param ctx Pointer to the method context.
5365 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5367 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5368 "order_by", "limit", and "offset".
5370 The SELECT statements built here are distinct from those built for the json_query method.
5372 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5373 osrfHash* meta, osrfMethodContext* ctx ) {
5375 const char* locale = osrf_message_get_last_locale();
5377 osrfHash* fields = osrfHashGet( meta, "fields" );
5378 const char* core_class = osrfHashGet( meta, "classname" );
5380 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5382 jsonObject* selhash = NULL;
5383 jsonObject* defaultselhash = NULL;
5385 growing_buffer* sql_buf = buffer_init( 128 );
5386 growing_buffer* select_buf = buffer_init( 128 );
5388 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5389 defaultselhash = jsonNewObjectType( JSON_HASH );
5390 selhash = defaultselhash;
5393 // If there's no SELECT list for the core class, build one
5394 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5395 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5397 // Add every non-virtual field to the field list
5398 osrfHash* field_def = NULL;
5399 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5400 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5401 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5402 const char* field = osrfHashIteratorKey( field_itr );
5403 jsonObjectPush( field_list, jsonNewObject( field ) );
5406 osrfHashIteratorFree( field_itr );
5407 jsonObjectSetKey( selhash, core_class, field_list );
5410 // Build a list of columns for the SELECT clause
5412 const jsonObject* snode = NULL;
5413 jsonIterator* class_itr = jsonNewIterator( selhash );
5414 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5416 // If the class isn't in the IDL, ignore it
5417 const char* cname = class_itr->key;
5418 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5422 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5423 if( strcmp( core_class, class_itr->key )) {
5427 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5428 if( !found->size ) {
5429 jsonObjectFree( found );
5433 jsonObjectFree( found );
5436 const jsonObject* node = NULL;
5437 jsonIterator* select_itr = jsonNewIterator( snode );
5438 while( (node = jsonIteratorNext( select_itr )) ) {
5439 const char* item_str = jsonObjectGetString( node );
5440 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5441 char* fname = osrfHashGet( field, "name" );
5446 if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5452 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5457 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5458 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5461 i18n = osrfHashGet( field, "i18n" );
5463 if( str_is_true( i18n ) ) {
5464 char* pkey = osrfHashGet( idlClass, "primarykey" );
5465 char* tname = osrfHashGet( idlClass, "tablename" );
5467 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5468 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5469 tname, cname, fname, pkey, cname, pkey, locale, fname );
5471 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5474 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5478 jsonIteratorFree( select_itr );
5481 jsonIteratorFree( class_itr );
5483 char* col_list = buffer_release( select_buf );
5484 char* table = oilsGetRelation( meta );
5486 table = strdup( "(null)" );
5488 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5492 // Clear the query stack (as a fail-safe precaution against possible
5493 // leftover garbage); then push the first query frame onto the stack.
5494 clear_query_stack();
5496 if( add_query_core( NULL, core_class ) ) {
5498 osrfAppSessionStatus(
5500 OSRF_STATUS_INTERNALSERVERERROR,
5501 "osrfMethodException",
5503 "Unable to build query frame for core class"
5505 buffer_free( sql_buf );
5506 if( defaultselhash )
5507 jsonObjectFree( defaultselhash );
5511 // Add the JOIN clauses, if any
5513 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5514 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5515 OSRF_BUFFER_ADD( sql_buf, join_clause );
5516 free( join_clause );
5519 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5520 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5522 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5524 // Add the conditions in the WHERE clause
5525 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5527 osrfAppSessionStatus(
5529 OSRF_STATUS_INTERNALSERVERERROR,
5530 "osrfMethodException",
5532 "Severe query error -- see error log for more details"
5534 buffer_free( sql_buf );
5535 if( defaultselhash )
5536 jsonObjectFree( defaultselhash );
5537 clear_query_stack();
5540 buffer_add( sql_buf, pred );
5544 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5545 if( rest_of_query ) {
5546 const jsonObject* order_by = NULL;
5547 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5549 char* order_by_list = NULL;
5551 if( JSON_ARRAY == order_by->type ) {
5552 order_by_list = buildOrderByFromArray( ctx, order_by );
5553 if( !order_by_list ) {
5554 buffer_free( sql_buf );
5555 if( defaultselhash )
5556 jsonObjectFree( defaultselhash );
5557 clear_query_stack();
5560 } else if( JSON_HASH == order_by->type ) {
5561 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5562 // and build a list of ORDER BY expressions.
5563 growing_buffer* order_buf = buffer_init( 128 );
5565 jsonIterator* class_itr = jsonNewIterator( order_by );
5566 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5568 ClassInfo* order_class_info = search_alias( class_itr->key );
5569 if( ! order_class_info )
5570 continue; // class not referenced by FROM clause? Ignore it.
5572 if( JSON_HASH == snode->type ) {
5574 // If the data for the current class is a JSON_HASH, then it is
5575 // keyed on field name.
5577 const jsonObject* onode = NULL;
5578 jsonIterator* order_itr = jsonNewIterator( snode );
5579 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5581 osrfHash* field_def = osrfHashGet(
5582 order_class_info->fields, order_itr->key );
5584 continue; // Field not defined in IDL? Ignore it.
5585 if( str_is_true( osrfHashGet( field_def, "virtual")))
5586 continue; // Field is virtual? Ignore it.
5588 char* field_str = NULL;
5589 char* direction = NULL;
5590 if( onode->type == JSON_HASH ) {
5591 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5592 field_str = searchFieldTransform(
5593 class_itr->key, field_def, onode );
5595 osrfAppSessionStatus(
5597 OSRF_STATUS_INTERNALSERVERERROR,
5598 "osrfMethodException",
5600 "Severe query error in ORDER BY clause -- "
5601 "see error log for more details"
5603 jsonIteratorFree( order_itr );
5604 jsonIteratorFree( class_itr );
5605 buffer_free( order_buf );
5606 buffer_free( sql_buf );
5607 if( defaultselhash )
5608 jsonObjectFree( defaultselhash );
5609 clear_query_stack();
5613 growing_buffer* field_buf = buffer_init( 16 );
5614 buffer_fadd( field_buf, "\"%s\".%s",
5615 class_itr->key, order_itr->key );
5616 field_str = buffer_release( field_buf );
5619 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5620 const char* dir = jsonObjectGetString( order_by );
5621 if(!strncasecmp( dir, "d", 1 )) {
5622 direction = " DESC";
5626 field_str = strdup( order_itr->key );
5627 const char* dir = jsonObjectGetString( onode );
5628 if( !strncasecmp( dir, "d", 1 )) {
5629 direction = " DESC";
5638 buffer_add( order_buf, ", " );
5641 buffer_add( order_buf, field_str );
5645 buffer_add( order_buf, direction );
5647 } // end while; looping over ORDER BY expressions
5649 jsonIteratorFree( order_itr );
5651 } else if( JSON_STRING == snode->type ) {
5652 // We expect a comma-separated list of sort fields.
5653 const char* str = jsonObjectGetString( snode );
5654 if( strchr( str, ';' )) {
5655 // No semicolons allowed. It is theoretically possible for a
5656 // legitimate semicolon to occur within quotes, but it's not likely
5657 // to occur in practice in the context of an ORDER BY list.
5658 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5659 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5661 osrfAppSessionStatus(
5663 OSRF_STATUS_INTERNALSERVERERROR,
5664 "osrfMethodException",
5666 "Possible attempt at SOL injection -- "
5667 "semicolon found in ORDER BY list"
5670 jsonIteratorFree( class_itr );
5671 buffer_free( order_buf );
5672 buffer_free( sql_buf );
5673 if( defaultselhash )
5674 jsonObjectFree( defaultselhash );
5675 clear_query_stack();
5678 buffer_add( order_buf, str );
5682 } // end while; looping over order_by classes
5684 jsonIteratorFree( class_itr );
5685 order_by_list = buffer_release( order_buf );
5688 osrfLogWarning( OSRF_LOG_MARK,
5689 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5690 "no ORDER BY generated" );
5693 if( order_by_list && *order_by_list ) {
5694 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5695 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5698 free( order_by_list );
5701 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5703 const char* str = jsonObjectGetString( limit );
5713 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5715 const char* str = jsonObjectGetString( offset );
5726 if( defaultselhash )
5727 jsonObjectFree( defaultselhash );
5728 clear_query_stack();
5730 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5731 return buffer_release( sql_buf );
5734 int doJSONSearch ( osrfMethodContext* ctx ) {
5735 if(osrfMethodVerifyContext( ctx )) {
5736 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5740 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5744 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5748 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5749 flags |= SELECT_DISTINCT;
5751 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5752 flags |= DISABLE_I18N;
5754 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5755 clear_query_stack(); // a possibly needless precaution
5756 char* sql = buildQuery( ctx, hash, flags );
5757 clear_query_stack();
5764 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5767 dbhandle = writehandle;
5769 dbi_result result = dbi_conn_query( dbhandle, sql );
5772 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5774 if( dbi_result_first_row( result )) {
5775 /* JSONify the result */
5776 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5779 jsonObject* return_val = oilsMakeJSONFromResult( result );
5780 osrfAppRespond( ctx, return_val );
5781 jsonObjectFree( return_val );
5782 } while( dbi_result_next_row( result ));
5785 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5788 osrfAppRespondComplete( ctx, NULL );
5790 /* clean up the query */
5791 dbi_result_free( result );
5796 int errnum = dbi_conn_error( dbhandle, &msg );
5797 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5798 modulename, sql, errnum, msg ? msg : "(No description available)" );
5799 osrfAppSessionStatus(
5801 OSRF_STATUS_INTERNALSERVERERROR,
5802 "osrfMethodException",
5804 "Severe query error -- see error log for more details"
5806 if( !oilsIsDBConnected( dbhandle ))
5807 osrfAppSessionPanic( ctx->session );
5814 // The last parameter, err, is used to report an error condition by updating an int owned by
5815 // the calling code.
5817 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5818 // It is the responsibility of the calling code to initialize *err before the
5819 // call, so that it will be able to make sense of the result.
5821 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5822 // redundant anyway.
5823 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5824 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5827 dbhandle = writehandle;
5829 char* core_class = osrfHashGet( class_meta, "classname" );
5830 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5832 char* pkey = osrfHashGet( class_meta, "primarykey" );
5834 if (!ctx->session->userData)
5835 (void) initSessionCache( ctx );
5837 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5838 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5839 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5841 int i_respond_directly = 0;
5842 int flesh_depth = 0;
5844 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5846 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5851 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5853 dbi_result result = dbi_conn_query( dbhandle, sql );
5854 if( NULL == result ) {
5856 int errnum = dbi_conn_error( dbhandle, &msg );
5857 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5858 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5859 msg ? msg : "(No description available)" );
5860 if( !oilsIsDBConnected( dbhandle ))
5861 osrfAppSessionPanic( ctx->session );
5862 osrfAppSessionStatus(
5864 OSRF_STATUS_INTERNALSERVERERROR,
5865 "osrfMethodException",
5867 "Severe query error -- see error log for more details"
5874 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5877 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5878 jsonObject* row_obj = NULL;
5880 // The following two steps are for verifyObjectPCRUD()'s benefit.
5881 // 1. get the flesh depth
5882 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5884 flesh_depth = (int) jsonObjectGetNumber( _tmp );
5885 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5886 flesh_depth = max_flesh_depth;
5889 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
5890 // over the whole life of this request. This means if we've already set
5891 // up a rs_size_req_%d, do nothing.
5892 // a. Incidentally, we can also use this opportunity to set i_respond_directly
5893 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
5894 if( !rs_size ) { // pointer null, so value not set in hash
5895 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
5896 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
5898 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
5899 unsigned long long result_count = dbi_result_get_numrows( result );
5900 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
5901 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
5904 if( dbi_result_first_row( result )) {
5906 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5907 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5908 // eliminate the duplicates.
5909 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5910 osrfHash* dedup = osrfNewHash();
5912 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5913 char* pkey_val = oilsFMGetString( row_obj, pkey );
5914 if( osrfHashGet( dedup, pkey_val ) ) {
5915 jsonObjectFree( row_obj );
5918 if( !enforce_pcrud || !need_to_verify ||
5919 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
5920 osrfHashSet( dedup, pkey_val, pkey_val );
5921 jsonObjectPush( res_list, row_obj );
5924 } while( dbi_result_next_row( result ));
5925 osrfHashFree( dedup );
5928 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5932 /* clean up the query */
5933 dbi_result_free( result );
5936 // If we're asked to flesh, and there's anything to flesh, then flesh it
5937 // (formerly we would skip fleshing if in pcrud mode, but now we support
5938 // fleshing even in PCRUD).
5939 if( res_list->size ) {
5940 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
5941 jsonObject* flesh_fields;
5942 jsonObject* flesh_blob = NULL;
5943 osrfStringArray* link_fields = NULL;
5944 osrfHash* links = NULL;
5948 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
5949 if( temp_blob && flesh_depth > 0 ) {
5951 flesh_blob = jsonObjectClone( temp_blob );
5952 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
5954 links = osrfHashGet( class_meta, "links" );
5956 // Make an osrfStringArray of the names of fields to be fleshed
5957 if( flesh_fields ) {
5958 if( flesh_fields->size == 1 ) {
5959 const char* _t = jsonObjectGetString(
5960 jsonObjectGetIndex( flesh_fields, 0 ) );
5961 if( !strcmp( _t, "*" ))
5962 link_fields = osrfHashKeys( links );
5965 if( !link_fields ) {
5967 link_fields = osrfNewStringArray( 1 );
5968 jsonIterator* _i = jsonNewIterator( flesh_fields );
5969 while ((_f = jsonIteratorNext( _i ))) {
5970 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5972 jsonIteratorFree( _i );
5975 want_flesh = link_fields ? 1 : 0;
5979 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5981 // Iterate over the JSON_ARRAY of rows
5983 unsigned long res_idx = 0;
5984 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5987 const char* link_field;
5989 // Iterate over the list of fleshable fields
5991 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5993 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5995 osrfHash* kid_link = osrfHashGet( links, link_field );
5997 continue; // Not a link field; skip it
5999 osrfHash* field = osrfHashGet( fields, link_field );
6001 continue; // Not a field at all; skip it (IDL is ill-formed)
6003 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
6004 osrfHashGet( kid_link, "class" ));
6006 continue; // The class it links to doesn't exist; skip it
6008 const char* reltype = osrfHashGet( kid_link, "reltype" );
6010 continue; // No reltype; skip it (IDL is ill-formed)
6012 osrfHash* value_field = field;
6014 if( !strcmp( reltype, "has_many" )
6015 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
6016 value_field = osrfHashGet(
6017 fields, osrfHashGet( class_meta, "primarykey" ) );
6020 int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
6021 // fleshing pcrud case: we require the controller in need_to_verify mode
6022 if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
6023 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
6027 (unsigned long) atoi( osrfHashGet(field, "array_position") ),
6029 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
6035 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
6037 if( link_map->size > 0 ) {
6038 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
6041 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
6046 osrfHashGet( kid_link, "class" ),
6053 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
6054 osrfHashGet( kid_link, "field" ),
6055 osrfHashGet( kid_link, "class" ),
6056 osrfHashGet( kid_link, "key" ),
6057 osrfHashGet( kid_link, "reltype" )
6060 const char* search_key = jsonObjectGetString(
6061 jsonObjectGetIndex( cur,
6062 atoi( osrfHashGet( value_field, "array_position" ) )
6067 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
6071 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
6073 // construct WHERE clause
6074 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
6077 osrfHashGet( kid_link, "key" ),
6078 jsonNewObject( search_key )
6081 // construct the rest of the query, mostly
6082 // by copying pieces of the previous level of query
6083 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
6084 jsonObjectSetKey( rest_of_query, "flesh",
6085 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
6089 jsonObjectSetKey( rest_of_query, "flesh_fields",
6090 jsonObjectClone( flesh_blob ));
6092 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
6093 jsonObjectSetKey( rest_of_query, "order_by",
6094 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
6098 if( jsonObjectGetKeyConst( query_hash, "select" )) {
6099 jsonObjectSetKey( rest_of_query, "select",
6100 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
6104 // do the query, recursively, to expand the fleshable field
6105 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
6106 where_clause, rest_of_query, err );
6108 jsonObjectFree( where_clause );
6109 jsonObjectFree( rest_of_query );
6112 osrfStringArrayFree( link_fields );
6113 jsonObjectFree( res_list );
6114 jsonObjectFree( flesh_blob );
6118 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
6119 osrfHashGet( kid_link, "class" ), kids->size );
6121 // Traverse the result set
6122 jsonObject* X = NULL;
6123 if( link_map->size > 0 && kids->size > 0 ) {
6125 kids = jsonNewObjectType( JSON_ARRAY );
6127 jsonObject* _k_node;
6128 unsigned long res_idx = 0;
6129 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
6135 (unsigned long) atoi(
6141 osrfHashGet( kid_link, "class" )
6145 osrfStringArrayGetString( link_map, 0 )
6153 } // end while loop traversing X
6156 if (kids->size > 0) {
6158 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
6159 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
6161 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
6162 osrfHashGet( kid_link, "field" ));
6165 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6166 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
6171 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
6173 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
6174 osrfHashGet( kid_link, "field" ) );
6177 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6178 jsonObjectClone( kids )
6183 jsonObjectFree( kids );
6187 jsonObjectFree( kids );
6189 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
6190 osrfHashGet( kid_link, "field" ) );
6191 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
6193 } // end while loop traversing list of fleshable fields
6196 if( i_respond_directly ) {
6197 if ( *methodtype == 'i' ) {
6198 osrfAppRespond( ctx,
6199 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
6201 osrfAppRespond( ctx, cur );
6204 } // end while loop traversing res_list
6205 jsonObjectFree( flesh_blob );
6206 osrfStringArrayFree( link_fields );
6209 if( i_respond_directly ) {
6210 jsonObjectFree( res_list );
6211 return jsonNewObjectType( JSON_ARRAY );
6218 int doUpdate( osrfMethodContext* ctx ) {
6219 if( osrfMethodVerifyContext( ctx )) {
6220 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6225 timeout_needs_resetting = 1;
6227 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6229 jsonObject* target = NULL;
6231 target = jsonObjectGetIndex( ctx->params, 1 );
6233 target = jsonObjectGetIndex( ctx->params, 0 );
6235 if(!verifyObjectClass( ctx, target )) {
6236 osrfAppRespondComplete( ctx, NULL );
6240 if( getXactId( ctx ) == NULL ) {
6241 osrfAppSessionStatus(
6243 OSRF_STATUS_BADREQUEST,
6244 "osrfMethodException",
6246 "No active transaction -- required for UPDATE"
6248 osrfAppRespondComplete( ctx, NULL );
6252 // The following test is harmless but redundant. If a class is
6253 // readonly, we don't register an update method for it.
6254 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6255 osrfAppSessionStatus(
6257 OSRF_STATUS_BADREQUEST,
6258 "osrfMethodException",
6260 "Cannot UPDATE readonly class"
6262 osrfAppRespondComplete( ctx, NULL );
6266 const char* trans_id = getXactId( ctx );
6268 // Set the last_xact_id
6269 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6271 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6272 trans_id, target->classname, index );
6273 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6276 char* pkey = osrfHashGet( meta, "primarykey" );
6277 osrfHash* fields = osrfHashGet( meta, "fields" );
6279 char* id = oilsFMGetString( target, pkey );
6283 "%s updating %s object with %s = %s",
6285 osrfHashGet( meta, "fieldmapper" ),
6290 dbhandle = writehandle;
6291 growing_buffer* sql = buffer_init( 128 );
6292 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6295 osrfHash* field_def = NULL;
6296 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6297 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6299 // Skip virtual fields, and the primary key
6300 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6303 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6307 const char* field_name = osrfHashIteratorKey( field_itr );
6308 if( ! strcmp( field_name, pkey ) )
6311 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6313 int value_is_numeric = 0; // boolean
6315 if( field_object && field_object->classname ) {
6316 value = oilsFMGetString(
6318 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6320 } else if( field_object && JSON_BOOL == field_object->type ) {
6321 if( jsonBoolIsTrue( field_object ) )
6322 value = strdup( "t" );
6324 value = strdup( "f" );
6326 value = jsonObjectToSimpleString( field_object );
6327 if( field_object && JSON_NUMBER == field_object->type )
6328 value_is_numeric = 1;
6331 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6332 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6334 if( !field_object || field_object->type == JSON_NULL ) {
6335 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6336 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6340 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6341 buffer_fadd( sql, " %s = NULL", field_name );
6344 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6348 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6350 const char* numtype = get_datatype( field_def );
6351 if( !strncmp( numtype, "INT", 3 ) ) {
6352 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6353 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6354 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6356 // Must really be intended as a string, so quote it
6357 if( dbi_conn_quote_string( dbhandle, &value )) {
6358 buffer_fadd( sql, " %s = %s", field_name, value );
6360 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6361 modulename, value );
6362 osrfAppSessionStatus(
6364 OSRF_STATUS_INTERNALSERVERERROR,
6365 "osrfMethodException",
6367 "Error quoting string -- please see the error log for more details"
6371 osrfHashIteratorFree( field_itr );
6373 osrfAppRespondComplete( ctx, NULL );
6378 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6381 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6385 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6386 buffer_fadd( sql, " %s = %s", field_name, value );
6388 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6389 osrfAppSessionStatus(
6391 OSRF_STATUS_INTERNALSERVERERROR,
6392 "osrfMethodException",
6394 "Error quoting string -- please see the error log for more details"
6398 osrfHashIteratorFree( field_itr );
6400 osrfAppRespondComplete( ctx, NULL );
6409 osrfHashIteratorFree( field_itr );
6411 jsonObject* obj = jsonNewObject( id );
6413 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6414 dbi_conn_quote_string( dbhandle, &id );
6416 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6418 char* query = buffer_release( sql );
6419 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6421 dbi_result result = dbi_conn_query( dbhandle, query );
6426 jsonObjectFree( obj );
6427 obj = jsonNewObject( NULL );
6429 int errnum = dbi_conn_error( dbhandle, &msg );
6432 "%s ERROR updating %s object with %s = %s: %d %s",
6434 osrfHashGet( meta, "fieldmapper" ),
6438 msg ? msg : "(No description available)"
6440 osrfAppSessionStatus(
6442 OSRF_STATUS_INTERNALSERVERERROR,
6443 "osrfMethodException",
6445 "Error in updating a row -- please see the error log for more details"
6447 if( !oilsIsDBConnected( dbhandle ))
6448 osrfAppSessionPanic( ctx->session );
6451 dbi_result_free( result );
6454 osrfAppRespondComplete( ctx, obj );
6455 jsonObjectFree( obj );
6459 int doDelete( osrfMethodContext* ctx ) {
6460 if( osrfMethodVerifyContext( ctx )) {
6461 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6466 timeout_needs_resetting = 1;
6468 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6470 if( getXactId( ctx ) == NULL ) {
6471 osrfAppSessionStatus(
6473 OSRF_STATUS_BADREQUEST,
6474 "osrfMethodException",
6476 "No active transaction -- required for DELETE"
6478 osrfAppRespondComplete( ctx, NULL );
6482 // The following test is harmless but redundant. If a class is
6483 // readonly, we don't register a delete method for it.
6484 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6485 osrfAppSessionStatus(
6487 OSRF_STATUS_BADREQUEST,
6488 "osrfMethodException",
6490 "Cannot DELETE readonly class"
6492 osrfAppRespondComplete( ctx, NULL );
6496 dbhandle = writehandle;
6498 char* pkey = osrfHashGet( meta, "primarykey" );
6505 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6506 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6507 osrfAppRespondComplete( ctx, NULL );
6511 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6513 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6514 osrfAppRespondComplete( ctx, NULL );
6517 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6522 "%s deleting %s object with %s = %s",
6524 osrfHashGet( meta, "fieldmapper" ),
6529 jsonObject* obj = jsonNewObject( id );
6531 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6532 dbi_conn_quote_string( writehandle, &id );
6534 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6535 osrfHashGet( meta, "tablename" ), pkey, id );
6540 jsonObjectFree( obj );
6541 obj = jsonNewObject( NULL );
6543 int errnum = dbi_conn_error( writehandle, &msg );
6546 "%s ERROR deleting %s object with %s = %s: %d %s",
6548 osrfHashGet( meta, "fieldmapper" ),
6552 msg ? msg : "(No description available)"
6554 osrfAppSessionStatus(
6556 OSRF_STATUS_INTERNALSERVERERROR,
6557 "osrfMethodException",
6559 "Error in deleting a row -- please see the error log for more details"
6561 if( !oilsIsDBConnected( writehandle ))
6562 osrfAppSessionPanic( ctx->session );
6564 dbi_result_free( result );
6568 osrfAppRespondComplete( ctx, obj );
6569 jsonObjectFree( obj );
6574 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6575 @param result An iterator for a result set; we only look at the current row.
6576 @param @meta Pointer to the class metadata for the core class.
6577 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6579 If a column is not defined in the IDL, or if it has no array_position defined for it in
6580 the IDL, or if it is defined as virtual, ignore it.
6582 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6583 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6584 array_position in the IDL.
6586 A field defined in the IDL but not represented in the returned row will leave a hole
6587 in the JSON_ARRAY. In effect it will be treated as a null value.
6589 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6590 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6591 classname corresponding to the @a meta argument.
6593 The calling code is responsible for freeing the the resulting jsonObject by calling
6596 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6597 if( !( result && meta )) return NULL;
6599 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6600 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6601 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6603 osrfHash* fields = osrfHashGet( meta, "fields" );
6605 int columnIndex = 1;
6606 const char* columnName;
6608 /* cycle through the columns in the row returned from the database */
6609 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6611 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6613 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6615 /* determine the field type and storage attributes */
6616 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6617 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6619 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6620 // or if it has no sequence number there, or if it's virtual, skip it.
6621 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6624 if( str_is_true( osrfHashGet( _f, "virtual" )))
6625 continue; // skip this column: IDL says it's virtual
6627 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6628 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6629 continue; // since we assign sequence numbers dynamically as we load the IDL.
6631 fmIndex = atoi( pos );
6632 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6634 continue; // This field is not defined in the IDL
6637 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6638 // sequence number from the IDL (which is likely to be different from the sequence
6639 // of columns in the SELECT clause).
6640 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6641 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6646 case DBI_TYPE_INTEGER :
6648 if( attr & DBI_INTEGER_SIZE8 )
6649 jsonObjectSetIndex( object, fmIndex,
6650 jsonNewNumberObject(
6651 dbi_result_get_longlong_idx( result, columnIndex )));
6653 jsonObjectSetIndex( object, fmIndex,
6654 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6658 case DBI_TYPE_DECIMAL :
6659 jsonObjectSetIndex( object, fmIndex,
6660 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6663 case DBI_TYPE_STRING :
6668 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6673 case DBI_TYPE_DATETIME : {
6675 char dt_string[ 256 ] = "";
6678 // Fetch the date column as a time_t
6679 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6681 // Translate the time_t to a human-readable string
6682 if( !( attr & DBI_DATETIME_DATE )) {
6683 gmtime_r( &_tmp_dt, &gmdt );
6684 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6685 } else if( !( attr & DBI_DATETIME_TIME )) {
6686 gmtime_r( &_tmp_dt, &gmdt );
6687 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6689 localtime_r( &_tmp_dt, &gmdt );
6690 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6693 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6697 case DBI_TYPE_BINARY :
6698 osrfLogError( OSRF_LOG_MARK,
6699 "Can't do binary at column %s : index %d", columnName, columnIndex );
6708 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6709 if( !result ) return NULL;
6711 jsonObject* object = jsonNewObject( NULL );
6714 char dt_string[ 256 ];
6718 int columnIndex = 1;
6720 unsigned short type;
6721 const char* columnName;
6723 /* cycle through the column list */
6724 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6726 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6728 fmIndex = -1; // reset the position
6730 /* determine the field type and storage attributes */
6731 type = dbi_result_get_field_type_idx( result, columnIndex );
6732 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6734 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6735 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6740 case DBI_TYPE_INTEGER :
6742 if( attr & DBI_INTEGER_SIZE8 )
6743 jsonObjectSetKey( object, columnName,
6744 jsonNewNumberObject( dbi_result_get_longlong_idx(
6745 result, columnIndex )) );
6747 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6748 dbi_result_get_int_idx( result, columnIndex )) );
6751 case DBI_TYPE_DECIMAL :
6752 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6753 dbi_result_get_double_idx( result, columnIndex )) );
6756 case DBI_TYPE_STRING :
6757 jsonObjectSetKey( object, columnName,
6758 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6761 case DBI_TYPE_DATETIME :
6763 memset( dt_string, '\0', sizeof( dt_string ));
6764 memset( &gmdt, '\0', sizeof( gmdt ));
6766 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6768 if( !( attr & DBI_DATETIME_DATE )) {
6769 gmtime_r( &_tmp_dt, &gmdt );
6770 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6771 } else if( !( attr & DBI_DATETIME_TIME )) {
6772 gmtime_r( &_tmp_dt, &gmdt );
6773 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6775 localtime_r( &_tmp_dt, &gmdt );
6776 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6779 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6782 case DBI_TYPE_BINARY :
6783 osrfLogError( OSRF_LOG_MARK,
6784 "Can't do binary at column %s : index %d", columnName, columnIndex );
6788 } // end while loop traversing result
6793 // Interpret a string as true or false
6794 int str_is_true( const char* str ) {
6795 if( NULL == str || strcasecmp( str, "true" ) )
6801 // Interpret a jsonObject as true or false
6802 static int obj_is_true( const jsonObject* obj ) {
6805 else switch( obj->type )
6813 if( strcasecmp( obj->value.s, "true" ) )
6817 case JSON_NUMBER : // Support 1/0 for perl's sake
6818 if( jsonObjectGetNumber( obj ) == 1.0 )
6827 // Translate a numeric code into a text string identifying a type of
6828 // jsonObject. To be used for building error messages.
6829 static const char* json_type( int code ) {
6835 return "JSON_ARRAY";
6837 return "JSON_STRING";
6839 return "JSON_NUMBER";
6845 return "(unrecognized)";
6849 // Extract the "primitive" attribute from an IDL field definition.
6850 // If we haven't initialized the app, then we must be running in
6851 // some kind of testbed. In that case, default to "string".
6852 static const char* get_primitive( osrfHash* field ) {
6853 const char* s = osrfHashGet( field, "primitive" );
6855 if( child_initialized )
6858 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6860 osrfHashGet( field, "name" )
6868 // Extract the "datatype" attribute from an IDL field definition.
6869 // If we haven't initialized the app, then we must be running in
6870 // some kind of testbed. In that case, default to to NUMERIC,
6871 // since we look at the datatype only for numbers.
6872 static const char* get_datatype( osrfHash* field ) {
6873 const char* s = osrfHashGet( field, "datatype" );
6875 if( child_initialized )
6878 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6880 osrfHashGet( field, "name" )
6889 @brief Determine whether a string is potentially a valid SQL identifier.
6890 @param s The identifier to be tested.
6891 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6893 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6894 need to follow all the rules exactly, such as requiring that the first character not
6897 We allow leading and trailing white space. In between, we do not allow punctuation
6898 (except for underscores and dollar signs), control characters, or embedded white space.
6900 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6901 for the foreseeable future such quoted identifiers are not likely to be an issue.
6903 int is_identifier( const char* s) {
6907 // Skip leading white space
6908 while( isspace( (unsigned char) *s ) )
6912 return 0; // Nothing but white space? Not okay.
6914 // Check each character until we reach white space or
6915 // end-of-string. Letters, digits, underscores, and
6916 // dollar signs are okay. With the exception of periods
6917 // (as in schema.identifier), control characters and other
6918 // punctuation characters are not okay. Anything else
6919 // is okay -- it could for example be part of a multibyte
6920 // UTF8 character such as a letter with diacritical marks,
6921 // and those are allowed.
6923 if( isalnum( (unsigned char) *s )
6927 ; // Fine; keep going
6928 else if( ispunct( (unsigned char) *s )
6929 || iscntrl( (unsigned char) *s ) )
6932 } while( *s && ! isspace( (unsigned char) *s ) );
6934 // If we found any white space in the above loop,
6935 // the rest had better be all white space.
6937 while( isspace( (unsigned char) *s ) )
6941 return 0; // White space was embedded within non-white space
6947 @brief Determine whether to accept a character string as a comparison operator.
6948 @param op The candidate comparison operator.
6949 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6951 We don't validate the operator for real. We just make sure that it doesn't contain
6952 any semicolons or white space (with special exceptions for a few specific operators).
6953 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6954 space but it's still not a valid operator, then the database will complain.
6956 Another approach would be to compare the string against a short list of approved operators.
6957 We don't do that because we want to allow custom operators like ">100*", which at this
6958 writing would be difficult or impossible to express otherwise in a JSON query.
6960 int is_good_operator( const char* op ) {
6961 if( !op ) return 0; // Sanity check
6965 if( isspace( (unsigned char) *s ) ) {
6966 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6967 // and IS NOT DISTINCT FROM.
6968 if( !strcasecmp( op, "similar to" ) )
6970 else if( !strcasecmp( op, "is distinct from" ) )
6972 else if( !strcasecmp( op, "is not distinct from" ) )
6977 else if( ';' == *s )
6985 @name Query Frame Management
6987 The following machinery supports a stack of query frames for use by SELECT().
6989 A query frame caches information about one level of a SELECT query. When we enter
6990 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6992 The query frame stores information about the core class, and about any joined classes
6995 The main purpose is to map table aliases to classes and tables, so that a query can
6996 join to the same table more than once. A secondary goal is to reduce the number of
6997 lookups in the IDL by caching the results.
7001 #define STATIC_CLASS_INFO_COUNT 3
7003 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
7006 @brief Allocate a ClassInfo as raw memory.
7007 @return Pointer to the newly allocated ClassInfo.
7009 Except for the in_use flag, which is used only by the allocation and deallocation
7010 logic, we don't initialize the ClassInfo here.
7012 static ClassInfo* allocate_class_info( void ) {
7013 // In order to reduce the number of mallocs and frees, we return a static
7014 // instance of ClassInfo, if we can find one that we're not already using.
7015 // We rely on the fact that the compiler will implicitly initialize the
7016 // static instances so that in_use == 0.
7019 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7020 if( ! static_class_info[ i ].in_use ) {
7021 static_class_info[ i ].in_use = 1;
7022 return static_class_info + i;
7026 // The static ones are all in use. Malloc one.
7028 return safe_malloc( sizeof( ClassInfo ) );
7032 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
7033 @param info Pointer to the ClassInfo to be cleared.
7035 static void clear_class_info( ClassInfo* info ) {
7040 // Free any malloc'd strings
7042 if( info->alias != info->alias_store )
7043 free( info->alias );
7045 if( info->class_name != info->class_name_store )
7046 free( info->class_name );
7048 free( info->source_def );
7050 info->alias = info->class_name = info->source_def = NULL;
7055 @brief Free a ClassInfo and everything it owns.
7056 @param info Pointer to the ClassInfo to be freed.
7058 static void free_class_info( ClassInfo* info ) {
7063 clear_class_info( info );
7065 // If it's one of the static instances, just mark it as not in use
7068 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7069 if( info == static_class_info + i ) {
7070 static_class_info[ i ].in_use = 0;
7075 // Otherwise it must have been malloc'd, so free it
7081 @brief Populate an already-allocated ClassInfo.
7082 @param info Pointer to the ClassInfo to be populated.
7083 @param alias Alias for the class. If it is NULL, or an empty string, use the class
7085 @param class Name of the class.
7086 @return Zero if successful, or 1 if not.
7088 Populate the ClassInfo with copies of the alias and class name, and with pointers to
7089 the relevant portions of the IDL for the specified class.
7091 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
7094 osrfLogError( OSRF_LOG_MARK,
7095 "%s ERROR: No ClassInfo available to populate", modulename );
7096 info->alias = info->class_name = info->source_def = NULL;
7097 info->class_def = info->fields = info->links = NULL;
7102 osrfLogError( OSRF_LOG_MARK,
7103 "%s ERROR: No class name provided for lookup", modulename );
7104 info->alias = info->class_name = info->source_def = NULL;
7105 info->class_def = info->fields = info->links = NULL;
7109 // Alias defaults to class name if not supplied
7110 if( ! alias || ! alias[ 0 ] )
7113 // Look up class info in the IDL
7114 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
7116 osrfLogError( OSRF_LOG_MARK,
7117 "%s ERROR: Class %s not defined in IDL", modulename, class );
7118 info->alias = info->class_name = info->source_def = NULL;
7119 info->class_def = info->fields = info->links = NULL;
7121 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
7122 osrfLogError( OSRF_LOG_MARK,
7123 "%s ERROR: Class %s is defined as virtual", modulename, class );
7124 info->alias = info->class_name = info->source_def = NULL;
7125 info->class_def = info->fields = info->links = NULL;
7129 osrfHash* links = osrfHashGet( class_def, "links" );
7131 osrfLogError( OSRF_LOG_MARK,
7132 "%s ERROR: No links defined in IDL for class %s", modulename, class );
7133 info->alias = info->class_name = info->source_def = NULL;
7134 info->class_def = info->fields = info->links = NULL;
7138 osrfHash* fields = osrfHashGet( class_def, "fields" );
7140 osrfLogError( OSRF_LOG_MARK,
7141 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
7142 info->alias = info->class_name = info->source_def = NULL;
7143 info->class_def = info->fields = info->links = NULL;
7147 char* source_def = oilsGetRelation( class_def );
7151 // We got everything we need, so populate the ClassInfo
7152 if( strlen( alias ) > ALIAS_STORE_SIZE )
7153 info->alias = strdup( alias );
7155 strcpy( info->alias_store, alias );
7156 info->alias = info->alias_store;
7159 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
7160 info->class_name = strdup( class );
7162 strcpy( info->class_name_store, class );
7163 info->class_name = info->class_name_store;
7166 info->source_def = source_def;
7168 info->class_def = class_def;
7169 info->links = links;
7170 info->fields = fields;
7175 #define STATIC_FRAME_COUNT 3
7177 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
7180 @brief Allocate a QueryFrame as raw memory.
7181 @return Pointer to the newly allocated QueryFrame.
7183 Except for the in_use flag, which is used only by the allocation and deallocation
7184 logic, we don't initialize the QueryFrame here.
7186 static QueryFrame* allocate_frame( void ) {
7187 // In order to reduce the number of mallocs and frees, we return a static
7188 // instance of QueryFrame, if we can find one that we're not already using.
7189 // We rely on the fact that the compiler will implicitly initialize the
7190 // static instances so that in_use == 0.
7193 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7194 if( ! static_frame[ i ].in_use ) {
7195 static_frame[ i ].in_use = 1;
7196 return static_frame + i;
7200 // The static ones are all in use. Malloc one.
7202 return safe_malloc( sizeof( QueryFrame ) );
7206 @brief Free a QueryFrame, and all the memory it owns.
7207 @param frame Pointer to the QueryFrame to be freed.
7209 static void free_query_frame( QueryFrame* frame ) {
7214 clear_class_info( &frame->core );
7216 // Free the join list
7218 ClassInfo* info = frame->join_list;
7221 free_class_info( info );
7225 frame->join_list = NULL;
7228 // If the frame is a static instance, just mark it as unused
7230 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7231 if( frame == static_frame + i ) {
7232 static_frame[ i ].in_use = 0;
7237 // Otherwise it must have been malloc'd, so free it
7243 @brief Search a given QueryFrame for a specified alias.
7244 @param frame Pointer to the QueryFrame to be searched.
7245 @param target The alias for which to search.
7246 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7248 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7249 if( ! frame || ! target ) {
7253 ClassInfo* found_class = NULL;
7255 if( !strcmp( target, frame->core.alias ) )
7256 return &(frame->core);
7258 ClassInfo* curr_class = frame->join_list;
7259 while( curr_class ) {
7260 if( strcmp( target, curr_class->alias ) )
7261 curr_class = curr_class->next;
7263 found_class = curr_class;
7273 @brief Push a new (blank) QueryFrame onto the stack.
7275 static void push_query_frame( void ) {
7276 QueryFrame* frame = allocate_frame();
7277 frame->join_list = NULL;
7278 frame->next = curr_query;
7280 // Initialize the ClassInfo for the core class
7281 ClassInfo* core = &frame->core;
7282 core->alias = core->class_name = core->source_def = NULL;
7283 core->class_def = core->fields = core->links = NULL;
7289 @brief Pop a QueryFrame off the stack and destroy it.
7291 static void pop_query_frame( void ) {
7296 QueryFrame* popped = curr_query;
7297 curr_query = popped->next;
7299 free_query_frame( popped );
7303 @brief Populate the ClassInfo for the core class.
7304 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7305 class name as an alias.
7306 @param class_name Name of the core class.
7307 @return Zero if successful, or 1 if not.
7309 Populate the ClassInfo of the core class with copies of the alias and class name, and
7310 with pointers to the relevant portions of the IDL for the core class.
7312 static int add_query_core( const char* alias, const char* class_name ) {
7315 if( ! curr_query ) {
7316 osrfLogError( OSRF_LOG_MARK,
7317 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7319 } else if( curr_query->core.alias ) {
7320 osrfLogError( OSRF_LOG_MARK,
7321 "%s ERROR: Core class %s already populated as %s",
7322 modulename, curr_query->core.class_name, curr_query->core.alias );
7326 build_class_info( &curr_query->core, alias, class_name );
7327 if( curr_query->core.alias )
7330 osrfLogError( OSRF_LOG_MARK,
7331 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7337 @brief Search the current QueryFrame for a specified alias.
7338 @param target The alias for which to search.
7339 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7341 static inline ClassInfo* search_alias( const char* target ) {
7342 return search_alias_in_frame( curr_query, target );
7346 @brief Search all levels of query for a specified alias, starting with the current query.
7347 @param target The alias for which to search.
7348 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7350 static ClassInfo* search_all_alias( const char* target ) {
7351 ClassInfo* found_class = NULL;
7352 QueryFrame* curr_frame = curr_query;
7354 while( curr_frame ) {
7355 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7358 curr_frame = curr_frame->next;
7365 @brief Add a class to the list of classes joined to the current query.
7366 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7367 the class name as an alias.
7368 @param classname The name of the class to be added.
7369 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7371 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7373 if( ! classname || ! *classname ) { // sanity check
7374 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7381 const ClassInfo* conflict = search_alias( alias );
7383 osrfLogError( OSRF_LOG_MARK,
7384 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7385 modulename, alias, conflict->class_name );
7389 ClassInfo* info = allocate_class_info();
7391 if( build_class_info( info, alias, classname ) ) {
7392 free_class_info( info );
7396 // Add the new ClassInfo to the join list of the current QueryFrame
7397 info->next = curr_query->join_list;
7398 curr_query->join_list = info;
7404 @brief Destroy all nodes on the query stack.
7406 static void clear_query_stack( void ) {
7412 @brief Implement the set_audit_info method.
7413 @param ctx Pointer to the method context.
7414 @return Zero if successful, or -1 if not.
7416 Issue a SAVEPOINT to the database server.
7421 - workstation id (int)
7423 If user id is not provided the authkey will be used.
7424 For PCRUD the authkey is always used, even if a user is provided.
7426 int setAuditInfo( osrfMethodContext* ctx ) {
7427 if(osrfMethodVerifyContext( ctx )) {
7428 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
7432 // Get the user id from the parameters
7433 const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7435 if( enforce_pcrud || !user_id ) {
7436 timeout_needs_resetting = 1;
7437 const jsonObject* user = verifyUserPCRUD( ctx );
7440 osrfAppRespondComplete( ctx, NULL );
7444 // Not PCRUD and have a user_id?
7445 int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7446 osrfAppRespondComplete( ctx, NULL );
7451 @brief Save a audit info
7452 @param ctx Pointer to the method context.
7453 @param user_id User ID to write as a string
7454 @param ws_id Workstation ID to write as a string
7456 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7457 if( ctx && ctx->session ) {
7458 osrfAppSession* session = ctx->session;
7460 osrfHash* cache = session->userData;
7462 // If the session doesn't already have a hash, create one. Make sure
7463 // that the application session frees the hash when it terminates.
7464 if( NULL == cache ) {
7465 session->userData = cache = osrfNewHash();
7466 osrfHashSetCallback( cache, &sessionDataFree );
7467 ctx->session->userDataFree = &userDataFree;
7470 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7472 osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7474 int errnum = dbi_conn_error( writehandle, &msg );
7477 "%s: Error setting auditor information: %d %s",
7480 msg ? msg : "(No description available)"
7482 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7483 "osrfMethodException", ctx->request, "Error setting auditor info" );
7484 if( !oilsIsDBConnected( writehandle ))
7485 osrfAppSessionPanic( ctx->session );
7488 dbi_result_free( result );
7495 @brief Remove all but safe character from savepoint name
7496 @param sp User-supplied savepoint name
7497 @return sanitized savepoint name, or NULL
7499 The caller is expected to free the returned string. Note that
7500 this function exists only because we can't use PQescapeLiteral
7501 without either forking libdbi or abandoning it.
7503 static char* _sanitize_savepoint_name( const char* sp ) {
7505 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_";
7507 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7508 // and the default value of NAMEDATALEN is 64; that should be long enough
7509 // for our purposes, and it's unlikely that anyone is going to recompile
7510 // PostgreSQL to have a smaller value, so cap the identifier name
7511 // accordingly to avoid the remote chance that someone manages to pass in a
7512 // 12GB savepoint name
7513 const int MAX_LITERAL_NAMELEN = 63;
7516 if (len > MAX_LITERAL_NAMELEN) {
7517 len = MAX_LITERAL_NAMELEN;
7520 char* safeSpName = safe_malloc( len + 1 );
7524 for (j = 0; j < len; j++) {
7525 found = strchr(safe_chars, sp[j]);
7527 safeSpName[ i++ ] = found[0];
7530 safeSpName[ i ] = '\0';