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 );
170 char* pg_app = osrf_settings_host_value( "/apps/%s/app_settings/database/application_name", mod_name );
172 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
173 dbi_conn handle = dbi_conn_new( driver );
176 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
179 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
181 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
182 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
184 if( host ) dbi_conn_set_option( handle, "host", host );
185 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
186 if( user ) dbi_conn_set_option( handle, "username", user );
187 if( pw ) dbi_conn_set_option( handle, "password", pw );
188 if( db ) dbi_conn_set_option( handle, "dbname", db );
189 if( pg_app ) dbi_conn_set_option( handle, "pgsql_application_name", pg_app );
198 if( dbi_conn_connect( handle ) < 0 ) {
200 if( dbi_conn_connect( handle ) < 0 ) {
202 dbi_conn_error( handle, &msg );
203 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
204 msg ? msg : "(No description available)" );
209 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
215 @brief Select some options.
216 @param module_name: Name of the server.
217 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
219 This source file is used (at this writing) to implement three different servers:
220 - open-ils.reporter-store
224 These servers behave mostly the same, but they implement different combinations of
225 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
227 Here we use the server name in messages to identify which kind of server issued them.
228 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
230 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
232 module_name = "open-ils.cstore"; // bulletproofing with a default
237 modulename = strdup( module_name );
238 enforce_pcrud = do_pcrud;
239 max_flesh_depth = flesh_depth;
243 @brief Install a database connection.
244 @param conn Pointer to a database connection.
246 In some contexts, @a conn may merely provide a driver so that we can process strings
247 properly, without providing an open database connection.
249 void oilsSetDBConnection( dbi_conn conn ) {
250 dbhandle = writehandle = conn;
254 @brief Determine whether a database connection is alive.
255 @param handle Handle for a database connection.
256 @return 1 if the connection is alive, or zero if it isn't.
258 int oilsIsDBConnected( dbi_conn handle ) {
259 // Do an innocuous SELECT. If it succeeds, the database connection is still good.
260 dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
262 dbi_result_free( result );
265 // This is a terrible, horrible, no good, very bad kludge.
266 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
267 // but because (due to a previous error) the database is ignoring all commands,
268 // even innocuous SELECTs, until the current transaction is rolled back. The only
269 // known way to detect this condition via the dbi library is by looking at the error
270 // message. This approach will break if the language or wording of the message ever
272 // Note: the dbi_conn_ping function purports to determine whether the database
273 // connection is live, but at this writing this function is unreliable and useless.
274 static const char* ok_msg = "ERROR: current transaction is aborted, commands "
275 "ignored until end of transaction block\n";
277 dbi_conn_error( handle, &msg );
278 // Newer versions of dbi_conn_error return codes within the error msg.
279 // E.g. 3624914: ERROR: current transaction is aborted, commands ignored until end of transaction block
280 // Substring test should work regardless.
281 const char* substr = strstr(msg, ok_msg);
282 if( substr == NULL ) {
283 osrfLogError( OSRF_LOG_MARK, "Database connection isn't working : %s", msg );
286 return 1; // ignoring SELECT due to previous error; that's okay
291 @brief Get a table name, view name, or subquery for use in a FROM clause.
292 @param class Pointer to the IDL class entry.
293 @return A table name, a view name, or a subquery in parentheses.
295 In some cases the IDL defines a class, not with a table name or a view name, but with
296 a SELECT statement, which may be used as a subquery.
298 char* oilsGetRelation( osrfHash* classdef ) {
300 char* source_def = NULL;
301 const char* tabledef = osrfHashGet( classdef, "tablename" );
304 source_def = strdup( tabledef ); // Return the name of a table or view
306 tabledef = osrfHashGet( classdef, "source_definition" );
308 // Return a subquery, enclosed in parentheses
309 source_def = safe_malloc( strlen( tabledef ) + 3 );
310 source_def[ 0 ] = '(';
311 strcpy( source_def + 1, tabledef );
312 strcat( source_def, ")" );
314 // Not found: return an error
315 const char* classname = osrfHashGet( classdef, "classname" );
320 "%s ERROR No tablename or source_definition for class \"%s\"",
331 @brief Add datatypes from the database to the fields in the IDL.
332 @param handle Handle for a database connection
333 @return Zero if successful, or 1 upon error.
335 For each relevant class in the IDL: ask the database for the datatype of every field.
336 In particular, determine which fields are text fields and which fields are numeric
337 fields, so that we know whether to enclose their values in quotes.
339 int oilsExtendIDL( dbi_conn handle ) {
340 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
341 osrfHash* class = NULL;
342 growing_buffer* query_buf = buffer_init( 64 );
343 int results_found = 0; // boolean
345 // For each class in the IDL...
346 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
347 const char* classname = osrfHashIteratorKey( class_itr );
348 osrfHash* fields = osrfHashGet( class, "fields" );
350 // If the class is virtual, ignore it
351 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
352 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
356 char* tabledef = oilsGetRelation( class );
358 continue; // No such relation -- a query of it would be doomed to failure
360 buffer_reset( query_buf );
361 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
365 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
366 modulename, OSRF_BUFFER_C_STR( query_buf ) );
368 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
373 const char* columnName;
374 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
376 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
379 /* fetch the fieldmapper index */
380 osrfHash* _f = osrfHashGet(fields, columnName);
383 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
385 /* determine the field type and storage attributes */
387 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
389 case DBI_TYPE_INTEGER : {
391 if( !osrfHashGet(_f, "primitive") )
392 osrfHashSet(_f, "number", "primitive");
394 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
395 if( attr & DBI_INTEGER_SIZE8 )
396 osrfHashSet( _f, "INT8", "datatype" );
398 osrfHashSet( _f, "INT", "datatype" );
401 case DBI_TYPE_DECIMAL :
402 if( !osrfHashGet( _f, "primitive" ))
403 osrfHashSet( _f, "number", "primitive" );
405 osrfHashSet( _f, "NUMERIC", "datatype" );
408 case DBI_TYPE_STRING :
409 if( !osrfHashGet( _f, "primitive" ))
410 osrfHashSet( _f, "string", "primitive" );
412 osrfHashSet( _f,"TEXT", "datatype" );
415 case DBI_TYPE_DATETIME :
416 if( !osrfHashGet( _f, "primitive" ))
417 osrfHashSet( _f, "string", "primitive" );
419 osrfHashSet( _f, "TIMESTAMP", "datatype" );
422 case DBI_TYPE_BINARY :
423 if( !osrfHashGet( _f, "primitive" ))
424 osrfHashSet( _f, "string", "primitive" );
426 osrfHashSet( _f, "BYTEA", "datatype" );
431 "Setting [%s] to primitive [%s] and datatype [%s]...",
433 osrfHashGet( _f, "primitive" ),
434 osrfHashGet( _f, "datatype" )
438 } // end while loop for traversing columns of result
439 dbi_result_free( result );
442 int errnum = dbi_conn_error( handle, &msg );
443 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
444 errnum, msg ? msg : "(No description available)" );
445 // We don't check the database connection here. It's routine to get failures at
446 // this point; we routinely try to query tables that don't exist, because they
447 // are defined in the IDL but not in the database.
449 } // end for each class in IDL
451 buffer_free( query_buf );
452 osrfHashIteratorFree( class_itr );
453 child_initialized = 1;
455 if( !results_found ) {
456 osrfLogError( OSRF_LOG_MARK,
457 "No results found for any class -- bad database connection?" );
459 } else if( ! oilsIsDBConnected( handle )) {
460 osrfLogError( OSRF_LOG_MARK,
461 "Unable to extend IDL: database connection isn't working" );
469 @brief Free an osrfHash that stores a transaction ID.
470 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
472 This function is a callback, to be called by the application session when it ends.
473 The application session stores the osrfHash via an opaque pointer.
475 If the osrfHash contains an entry for the key "xact_id", it means that an
476 uncommitted transaction is pending. Roll it back.
478 void userDataFree( void* blob ) {
479 osrfHash* hash = (osrfHash*) blob;
480 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
481 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
483 int errnum = dbi_conn_error( writehandle, &msg );
484 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
485 errnum, msg ? msg : "(No description available)" );
489 if( !dbi_conn_query( writehandle, "SELECT auditor.clear_audit_info();" ) ) {
491 int errnum = dbi_conn_error( writehandle, &msg );
492 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform audit info clearing: %d %s",
493 errnum, msg ? msg : "(No description available)" );
497 osrfHashFree( hash );
501 @name Managing session data
502 @brief Maintain data stored via the userData pointer of the application session.
504 Currently, session-level data is stored in an osrfHash. Other arrangements are
505 possible, and some would be more efficient. The application session calls a
506 callback function to free userData before terminating.
508 Currently, the only data we store at the session level is the transaction id. By this
509 means we can ensure that any pending transactions are rolled back before the application
515 @brief Free an item in the application session's userData.
516 @param key The name of a key for an osrfHash.
517 @param item An opaque pointer to the item associated with the key.
519 We store an osrfHash as userData with the application session, and arrange (by
520 installing userDataFree() as a different callback) for the session to free that
521 osrfHash before terminating.
523 This function is a callback for freeing items in the osrfHash. Currently we store
525 - Transaction id of a pending transaction; a character string. Key: "xact_id".
526 - Authkey; a character string. Key: "authkey".
527 - User object from the authentication server; a jsonObject. Key: "user_login".
529 If we ever store anything else in userData, we will need to revisit this function so
530 that it will free whatever else needs freeing.
532 static void sessionDataFree( char* key, void* item ) {
533 if( !strcmp( key, "xact_id" ) || !strcmp( key, "authkey" ) || !strncmp( key, "rs_size_", 8) )
535 else if( !strcmp( key, "user_login" ) )
536 jsonObjectFree( (jsonObject*) item );
537 else if( !strcmp( key, "pcache" ) )
538 osrfHashFree( (osrfHash*) item );
541 static void pcacheFree( char* key, void* item ) {
542 osrfStringArrayFree( (osrfStringArray*) item );
546 @brief Initialize session cache.
547 @param ctx Pointer to the method context.
549 Create a cache for the session by making the session's userData member point
550 to an osrfHash instance.
552 static osrfHash* initSessionCache( osrfMethodContext* ctx ) {
553 ctx->session->userData = osrfNewHash();
554 osrfHashSetCallback( (osrfHash*) ctx->session->userData, &sessionDataFree );
555 ctx->session->userDataFree = &userDataFree;
556 return ctx->session->userData;
560 @brief Save a transaction id.
561 @param ctx Pointer to the method context.
563 Save the session_id of the current application session as a transaction id.
565 static void setXactId( osrfMethodContext* ctx ) {
566 if( ctx && ctx->session ) {
567 osrfAppSession* session = ctx->session;
569 osrfHash* cache = session->userData;
571 // If the session doesn't already have a hash, create one. Make sure
572 // that the application session frees the hash when it terminates.
574 cache = initSessionCache( ctx );
576 // Save the transaction id in the hash, with the key "xact_id"
577 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
582 @brief Get the transaction ID for the current transaction, if any.
583 @param ctx Pointer to the method context.
584 @return Pointer to the transaction ID.
586 The return value points to an internal buffer, and will become invalid upon issuing
587 a commit or rollback.
589 static inline const char* getXactId( osrfMethodContext* ctx ) {
590 if( ctx && ctx->session && ctx->session->userData )
591 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
597 @brief Clear the current transaction id.
598 @param ctx Pointer to the method context.
600 static inline void clearXactId( osrfMethodContext* ctx ) {
601 if( ctx && ctx->session && ctx->session->userData )
602 osrfHashRemove( ctx->session->userData, "xact_id" );
607 @brief Stash the location for a particular perm in the sessionData cache
608 @param ctx Pointer to the method context.
609 @param perm Name of the permission we're looking at
610 @param array StringArray of perm location ids
612 static void setPermLocationCache( osrfMethodContext* ctx, const char* perm, osrfStringArray* locations ) {
613 if( ctx && ctx->session ) {
614 osrfAppSession* session = ctx->session;
616 osrfHash* cache = session->userData;
618 // If the session doesn't already have a hash, create one. Make sure
619 // that the application session frees the hash when it terminates.
621 cache = initSessionCache( ctx );
623 osrfHash* pcache = osrfHashGet(cache, "pcache");
625 if( NULL == pcache ) {
626 pcache = osrfNewHash();
627 osrfHashSetCallback( pcache, &pcacheFree );
628 osrfHashSet( cache, pcache, "pcache" );
631 if( perm && locations )
632 osrfHashSet( pcache, locations, strdup(perm) );
637 @brief Grab stashed location for a particular perm in the sessionData cache
638 @param ctx Pointer to the method context.
639 @param perm Name of the permission we're looking at
641 static osrfStringArray* getPermLocationCache( osrfMethodContext* ctx, const char* perm ) {
642 if( ctx && ctx->session ) {
643 osrfAppSession* session = ctx->session;
644 osrfHash* cache = session->userData;
646 osrfHash* pcache = osrfHashGet(cache, "pcache");
648 return osrfHashGet( pcache, perm );
657 @brief Save the user's login in the userData for the current application session.
658 @param ctx Pointer to the method context.
659 @param user_login Pointer to the user login object to be cached (we cache the original,
662 If @a user_login is NULL, remove the user login if one is already cached.
664 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
665 if( ctx && ctx->session ) {
666 osrfAppSession* session = ctx->session;
668 osrfHash* cache = session->userData;
670 // If the session doesn't already have a hash, create one. Make sure
671 // that the application session frees the hash when it terminates.
673 cache = initSessionCache( ctx );
676 osrfHashSet( cache, user_login, "user_login" );
678 osrfHashRemove( cache, "user_login" );
683 @brief Get the user login object for the current application session, if any.
684 @param ctx Pointer to the method context.
685 @return Pointer to the user login object if found; otherwise NULL.
687 The user login object was returned from the authentication server, and then cached so
688 we don't have to call the authentication server again for the same user.
690 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
691 if( ctx && ctx->session && ctx->session->userData )
692 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
698 @brief Save a copy of an authkey in the userData of the current application session.
699 @param ctx Pointer to the method context.
700 @param authkey The authkey to be saved.
702 If @a authkey is NULL, remove the authkey if one is already cached.
704 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
705 if( ctx && ctx->session && authkey ) {
706 osrfAppSession* session = ctx->session;
707 osrfHash* cache = session->userData;
709 // If the session doesn't already have a hash, create one. Make sure
710 // that the application session frees the hash when it terminates.
712 cache = initSessionCache( ctx );
714 // Save the transaction id in the hash, with the key "xact_id"
715 if( authkey && *authkey )
716 osrfHashSet( cache, strdup( authkey ), "authkey" );
718 osrfHashRemove( cache, "authkey" );
723 @brief Reset the login timeout.
724 @param authkey The authentication key for the current login session.
725 @param now The current time.
726 @return Zero if successful, or 1 if not.
728 Tell the authentication server to reset the timeout so that the login session won't
729 expire for a while longer.
731 We could dispense with the @a now parameter by calling time(). But we just called
732 time() in order to decide whether to reset the timeout, so we might as well reuse
733 the result instead of calling time() again.
735 static int reset_timeout( const char* authkey, time_t now ) {
736 jsonObject* auth_object = jsonNewObject( authkey );
738 // Ask the authentication server to reset the timeout. It returns an event
739 // indicating success or failure.
740 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
741 "open-ils.auth.session.reset_timeout", auth_object );
742 jsonObjectFree( auth_object );
744 if( !result || result->type != JSON_HASH ) {
745 osrfLogError( OSRF_LOG_MARK,
746 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
747 jsonObjectFree( result );
748 return 1; // Not the right sort of object returned
751 const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
752 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
753 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
754 jsonObjectFree( result );
755 return 1; // Return code from method not available
758 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
759 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
761 desc = "(No reason available)"; // failsafe; shouldn't happen
762 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
763 jsonObjectFree( result );
767 // Revise our local proxy for the timeout deadline
768 // by a smallish fraction of the timeout interval
769 const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
771 timeout = "1"; // failsafe; shouldn't happen
772 time_next_reset = now + atoi( timeout ) / 15;
774 jsonObjectFree( result );
775 return 0; // Successfully reset timeout
779 @brief Get the authkey string for the current application session, if any.
780 @param ctx Pointer to the method context.
781 @return Pointer to the cached authkey if found; otherwise NULL.
783 If present, the authkey string was cached from a previous method call.
785 static const char* getAuthkey( osrfMethodContext* ctx ) {
786 if( ctx && ctx->session && ctx->session->userData ) {
787 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
788 // LFW recent changes mean the userData hash gets set up earlier, but
789 // doesn't necessarily have an authkey yet
793 // Possibly reset the authentication timeout to keep the login alive. We do so
794 // no more than once per method call, and not at all if it has been only a short
795 // time since the last reset.
797 // Here we reset explicitly, if at all. We also implicitly reset the timeout
798 // whenever we call the "open-ils.auth.session.retrieve" method.
799 if( timeout_needs_resetting ) {
800 time_t now = time( NULL );
801 if( now >= time_next_reset && reset_timeout( authkey, now ) )
802 authkey = NULL; // timeout has apparently expired already
805 timeout_needs_resetting = 0;
813 @brief Implement the transaction.begin method.
814 @param ctx Pointer to the method context.
815 @return Zero if successful, or -1 upon error.
817 Start a transaction. Save a transaction ID for future reference.
820 - authkey (PCRUD only)
822 Return to client: Transaction ID
824 int beginTransaction( osrfMethodContext* ctx ) {
825 if(osrfMethodVerifyContext( ctx )) {
826 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
830 if( enforce_pcrud ) {
831 timeout_needs_resetting = 1;
832 const jsonObject* user = verifyUserPCRUD( ctx );
837 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
840 int errnum = dbi_conn_error( writehandle, &msg );
841 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
842 modulename, errnum, msg ? msg : "(No description available)" );
843 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
844 "osrfMethodException", ctx->request, "Error starting transaction" );
845 if( !oilsIsDBConnected( writehandle ))
846 osrfAppSessionPanic( ctx->session );
849 dbi_result_free( result );
851 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
852 osrfAppRespondComplete( ctx, ret );
853 jsonObjectFree( ret );
859 @brief Implement the savepoint.set method.
860 @param ctx Pointer to the method context.
861 @return Zero if successful, or -1 if not.
863 Issue a SAVEPOINT to the database server.
866 - authkey (PCRUD only)
869 Return to client: Savepoint name
871 int setSavepoint( osrfMethodContext* ctx ) {
872 if(osrfMethodVerifyContext( ctx )) {
873 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
878 if( enforce_pcrud ) {
880 timeout_needs_resetting = 1;
881 const jsonObject* user = verifyUserPCRUD( ctx );
886 // Verify that a transaction is pending
887 const char* trans_id = getXactId( ctx );
888 if( NULL == trans_id ) {
889 osrfAppSessionStatus(
891 OSRF_STATUS_INTERNALSERVERERROR,
892 "osrfMethodException",
894 "No active transaction -- required for savepoints"
899 // Get the savepoint name from the method params
900 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
903 osrfLogWarning(OSRF_LOG_MARK, "savepoint.set called with no name");
907 char *safeSpName = _sanitize_savepoint_name( spName );
909 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", safeSpName );
913 int errnum = dbi_conn_error( writehandle, &msg );
916 "%s: Error creating savepoint %s in transaction %s: %d %s",
921 msg ? msg : "(No description available)"
923 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
924 "osrfMethodException", ctx->request, "Error creating savepoint" );
925 if( !oilsIsDBConnected( writehandle ))
926 osrfAppSessionPanic( ctx->session );
929 dbi_result_free( result );
930 jsonObject* ret = jsonNewObject( spName );
931 osrfAppRespondComplete( ctx, ret );
932 jsonObjectFree( ret );
938 @brief Implement the savepoint.release method.
939 @param ctx Pointer to the method context.
940 @return Zero if successful, or -1 if not.
942 Issue a RELEASE SAVEPOINT to the database server.
945 - authkey (PCRUD only)
948 Return to client: Savepoint name
950 int releaseSavepoint( osrfMethodContext* ctx ) {
951 if(osrfMethodVerifyContext( ctx )) {
952 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
957 if( enforce_pcrud ) {
959 timeout_needs_resetting = 1;
960 const jsonObject* user = verifyUserPCRUD( ctx );
965 // Verify that a transaction is pending
966 const char* trans_id = getXactId( ctx );
967 if( NULL == trans_id ) {
968 osrfAppSessionStatus(
970 OSRF_STATUS_INTERNALSERVERERROR,
971 "osrfMethodException",
973 "No active transaction -- required for savepoints"
978 // Get the savepoint name from the method params
979 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
982 osrfLogWarning(OSRF_LOG_MARK, "savepoint.release called with no name");
986 char *safeSpName = _sanitize_savepoint_name( spName );
988 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", safeSpName );
992 int errnum = dbi_conn_error( writehandle, &msg );
995 "%s: Error releasing savepoint %s in transaction %s: %d %s",
1000 msg ? msg : "(No description available)"
1002 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1003 "osrfMethodException", ctx->request, "Error releasing savepoint" );
1004 if( !oilsIsDBConnected( writehandle ))
1005 osrfAppSessionPanic( ctx->session );
1008 dbi_result_free( result );
1009 jsonObject* ret = jsonNewObject( spName );
1010 osrfAppRespondComplete( ctx, ret );
1011 jsonObjectFree( ret );
1017 @brief Implement the savepoint.rollback method.
1018 @param ctx Pointer to the method context.
1019 @return Zero if successful, or -1 if not.
1021 Issue a ROLLBACK TO SAVEPOINT to the database server.
1024 - authkey (PCRUD only)
1027 Return to client: Savepoint name
1029 int rollbackSavepoint( osrfMethodContext* ctx ) {
1030 if(osrfMethodVerifyContext( ctx )) {
1031 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1036 if( enforce_pcrud ) {
1038 timeout_needs_resetting = 1;
1039 const jsonObject* user = verifyUserPCRUD( ctx );
1044 // Verify that a transaction is pending
1045 const char* trans_id = getXactId( ctx );
1046 if( NULL == trans_id ) {
1047 osrfAppSessionStatus(
1049 OSRF_STATUS_INTERNALSERVERERROR,
1050 "osrfMethodException",
1052 "No active transaction -- required for savepoints"
1057 // Get the savepoint name from the method params
1058 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1061 osrfLogWarning(OSRF_LOG_MARK, "savepoint.rollback called with no name");
1065 char *safeSpName = _sanitize_savepoint_name( spName );
1067 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", safeSpName );
1071 int errnum = dbi_conn_error( writehandle, &msg );
1074 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
1079 msg ? msg : "(No description available)"
1081 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1082 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1083 if( !oilsIsDBConnected( writehandle ))
1084 osrfAppSessionPanic( ctx->session );
1087 dbi_result_free( result );
1088 jsonObject* ret = jsonNewObject( spName );
1089 osrfAppRespondComplete( ctx, ret );
1090 jsonObjectFree( ret );
1096 @brief Implement the transaction.commit method.
1097 @param ctx Pointer to the method context.
1098 @return Zero if successful, or -1 if not.
1100 Issue a COMMIT to the database server.
1103 - authkey (PCRUD only)
1105 Return to client: Transaction ID.
1107 int commitTransaction( osrfMethodContext* ctx ) {
1108 if(osrfMethodVerifyContext( ctx )) {
1109 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1113 if( enforce_pcrud ) {
1114 timeout_needs_resetting = 1;
1115 const jsonObject* user = verifyUserPCRUD( ctx );
1120 // Verify that a transaction is pending
1121 const char* trans_id = getXactId( ctx );
1122 if( NULL == trans_id ) {
1123 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1124 "osrfMethodException", ctx->request, "No active transaction to commit" );
1128 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1131 int errnum = dbi_conn_error( writehandle, &msg );
1132 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1133 modulename, errnum, msg ? msg : "(No description available)" );
1134 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1135 "osrfMethodException", ctx->request, "Error committing transaction" );
1136 if( !oilsIsDBConnected( writehandle ))
1137 osrfAppSessionPanic( ctx->session );
1140 dbi_result_free( result );
1141 jsonObject* ret = jsonNewObject( trans_id );
1142 osrfAppRespondComplete( ctx, ret );
1143 jsonObjectFree( ret );
1150 @brief Implement the transaction.rollback method.
1151 @param ctx Pointer to the method context.
1152 @return Zero if successful, or -1 if not.
1154 Issue a ROLLBACK to the database server.
1157 - authkey (PCRUD only)
1159 Return to client: Transaction ID
1161 int rollbackTransaction( osrfMethodContext* ctx ) {
1162 if( osrfMethodVerifyContext( ctx )) {
1163 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1167 if( enforce_pcrud ) {
1168 timeout_needs_resetting = 1;
1169 const jsonObject* user = verifyUserPCRUD( ctx );
1174 // Verify that a transaction is pending
1175 const char* trans_id = getXactId( ctx );
1176 if( NULL == trans_id ) {
1177 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1178 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1182 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1185 int errnum = dbi_conn_error( writehandle, &msg );
1186 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1187 modulename, errnum, msg ? msg : "(No description available)" );
1188 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1189 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1190 if( !oilsIsDBConnected( writehandle ))
1191 osrfAppSessionPanic( ctx->session );
1194 dbi_result_free( result );
1195 jsonObject* ret = jsonNewObject( trans_id );
1196 osrfAppRespondComplete( ctx, ret );
1197 jsonObjectFree( ret );
1204 @brief Implement the "search" method.
1205 @param ctx Pointer to the method context.
1206 @return Zero if successful, or -1 if not.
1209 - authkey (PCRUD only)
1210 - WHERE clause, as jsonObject
1211 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1213 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1214 Optionally flesh linked fields.
1216 int doSearch( osrfMethodContext* ctx ) {
1217 if( osrfMethodVerifyContext( ctx )) {
1218 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1223 timeout_needs_resetting = 1;
1225 jsonObject* where_clause;
1226 jsonObject* rest_of_query;
1228 if( enforce_pcrud ) {
1229 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1230 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1232 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1233 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1236 if( !where_clause ) {
1237 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1241 // Get the class metadata
1242 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1243 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1247 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1249 osrfAppRespondComplete( ctx, NULL );
1253 // doFieldmapperSearch() now takes care of our responding for us
1254 // // Return each row to the client
1255 // jsonObject* cur = 0;
1256 // unsigned long res_idx = 0;
1258 // while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1259 // // We used to discard based on perms here, but now that's
1260 // // inside doFieldmapperSearch()
1261 // osrfAppRespond( ctx, cur );
1264 jsonObjectFree( obj );
1266 osrfAppRespondComplete( ctx, NULL );
1271 @brief Implement the "id_list" method.
1272 @param ctx Pointer to the method context.
1273 @param err Pointer through which to return an error code.
1274 @return Zero if successful, or -1 if not.
1277 - authkey (PCRUD only)
1278 - WHERE clause, as jsonObject
1279 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1281 Return to client: The primary key values for all rows of the relevant class that
1282 satisfy a specified WHERE clause.
1284 This method relies on the assumption that every class has a primary key consisting of
1287 int doIdList( osrfMethodContext* ctx ) {
1288 if( osrfMethodVerifyContext( ctx )) {
1289 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1294 timeout_needs_resetting = 1;
1296 jsonObject* where_clause;
1297 jsonObject* rest_of_query;
1299 // We use the where clause without change. But we need to massage the rest of the
1300 // query, so we work with a copy of it instead of modifying the original.
1302 if( enforce_pcrud ) {
1303 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1304 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1306 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1307 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1310 if( !where_clause ) {
1311 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1315 // Eliminate certain SQL clauses, if present.
1316 if( rest_of_query ) {
1317 jsonObjectRemoveKey( rest_of_query, "select" );
1318 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1319 jsonObjectRemoveKey( rest_of_query, "flesh" );
1320 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1322 rest_of_query = jsonNewObjectType( JSON_HASH );
1325 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1327 // Get the class metadata
1328 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1329 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1331 // Build a SELECT list containing just the primary key,
1332 // i.e. like { "classname":["keyname"] }
1333 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1335 // Load array with name of primary key
1336 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1337 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1338 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1340 jsonObjectSetKey( rest_of_query, "select", select_clause );
1345 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1347 jsonObjectFree( rest_of_query );
1349 osrfAppRespondComplete( ctx, NULL );
1353 // Return each primary key value to the client
1355 unsigned long res_idx = 0;
1356 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1357 // We used to discard based on perms here, but now that's
1358 // inside doFieldmapperSearch()
1359 osrfAppRespond( ctx,
1360 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1363 jsonObjectFree( obj );
1364 osrfAppRespondComplete( ctx, NULL );
1369 @brief Verify that we have a valid class reference.
1370 @param ctx Pointer to the method context.
1371 @param param Pointer to the method parameters.
1372 @return 1 if the class reference is valid, or zero if it isn't.
1374 The class of the method params must match the class to which the method id devoted.
1375 For PCRUD there are additional restrictions.
1377 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1379 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1380 osrfHash* class = osrfHashGet( method_meta, "class" );
1382 // Compare the method's class to the parameters' class
1383 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1385 // Oops -- they don't match. Complain.
1386 growing_buffer* msg = buffer_init( 128 );
1389 "%s: %s method for type %s was passed a %s",
1391 osrfHashGet( method_meta, "methodtype" ),
1392 osrfHashGet( class, "classname" ),
1393 param->classname ? param->classname : "(null)"
1396 char* m = buffer_release( msg );
1397 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1405 return verifyObjectPCRUD( ctx, class, param, 1 );
1411 @brief (PCRUD only) Verify that the user is properly logged in.
1412 @param ctx Pointer to the method context.
1413 @return If the user is logged in, a pointer to the user object from the authentication
1414 server; otherwise NULL.
1416 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1417 return verifyUserPCRUDfull( ctx, 0 );
1420 static const jsonObject* verifyUserPCRUDfull( osrfMethodContext* ctx, int anon_ok ) {
1422 // Get the authkey (the first method parameter)
1423 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1425 jsonObject* user = NULL;
1427 // If we are /not/ in anonymous mode
1428 if( strcmp( "ANONYMOUS", auth ) ) {
1429 // See if we have the same authkey, and a user object,
1430 // locally cached from a previous call
1431 const char* cached_authkey = getAuthkey( ctx );
1432 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1433 const jsonObject* cached_user = getUserLogin( ctx );
1438 // We have no matching authentication data in the cache. Authenticate from scratch.
1439 jsonObject* auth_object = jsonNewObject( auth );
1441 // Fetch the user object from the authentication server
1442 user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve", auth_object );
1443 jsonObjectFree( auth_object );
1445 if( !user->classname || strcmp(user->classname, "au" )) {
1447 growing_buffer* msg = buffer_init( 128 );
1450 "%s: permacrud received a bad auth token: %s",
1455 char* m = buffer_release( msg );
1456 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1460 jsonObjectFree( user );
1462 } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
1463 // Failed to set audit information - But note that write_audit_info already set error information.
1464 jsonObjectFree( user );
1469 } else if ( anon_ok ) { // we /are/ (attempting to be) anonymous
1470 user = jsonNewObjectType(JSON_ARRAY);
1471 jsonObjectSetClass( user, "aou" );
1472 oilsFMSetString(user, "id", "-1");
1475 setUserLogin( ctx, user );
1476 setAuthkey( ctx, auth );
1478 // Allow ourselves up to a second before we have to reset the login timeout.
1479 // It would be nice to use some fraction of the timeout interval enforced by the
1480 // authentication server, but that value is not readily available at this point.
1481 // Instead, we use a conservative default interval.
1482 time_next_reset = time( NULL ) + 1;
1488 @brief For PCRUD: Determine whether the current user may access the current row.
1489 @param ctx Pointer to the method context.
1490 @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1491 @param obj Pointer to the row being potentially accessed.
1492 @return 1 if access is permitted, or 0 if it isn't.
1494 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1496 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1498 dbhandle = writehandle;
1500 // Figure out what class and method are involved
1501 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1502 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1505 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1506 if (rs_size_from_hash) {
1507 rs_size = *rs_size_from_hash;
1508 osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1512 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1513 // contexts we will do another lookup of the current row, even if we already have a
1514 // previously fetched row image, because the row image in hand may not include the
1515 // foreign key(s) that we need.
1517 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1518 // but they aren't implemented yet.
1521 if( *method_type == 's' || *method_type == 'i' ) {
1522 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1524 } else if( *method_type == 'u' || *method_type == 'd' ) {
1525 fetch = 1; // MUST go to the db for the object for update and delete
1528 // In retrieve or search ONLY we allow anon. Later perm checks will fail as they should,
1529 // in the face of a fake user but required permissions.
1531 if( *method_type == 'r' )
1534 // Get the appropriate permacrud entry from the IDL, depending on method type
1535 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1537 // No permacrud for this method type on this class
1539 growing_buffer* msg = buffer_init( 128 );
1542 "%s: %s on class %s has no permacrud IDL entry",
1544 osrfHashGet( method_metadata, "methodtype" ),
1545 osrfHashGet( class, "classname" )
1548 char* m = buffer_release( msg );
1549 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1550 "osrfMethodException", ctx->request, m );
1557 // Get the user id, and make sure the user is logged in
1558 const jsonObject* user = verifyUserPCRUDfull( ctx, anon_ok );
1560 return 0; // Not logged in or anon? No access.
1562 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1564 // Get a list of permissions from the permacrud entry.
1565 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1566 if( permission->size == 0 ) {
1569 "No permissions required for this action (class %s), passing through",
1570 osrfHashGet(class, "classname")
1575 // But, if there are perms and the user is anonymous ... FAIL
1579 // Build a list of org units that own the row. This is fairly convoluted because there
1580 // are several different ways that an org unit may own the row, as defined by the
1583 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1584 // identifying an owning org_unit..
1585 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1587 // Foreign context adds a layer of indirection. The row points to some other row that
1588 // an org unit may own. The "jump" attribute, if present, adds another layer of
1590 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1592 // The following string array stores the list of org units. (We don't have a thingie
1593 // for storing lists of integers, so we fake it with a list of strings.)
1594 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1596 const char* context_org = NULL;
1597 const char* pkey = NULL;
1598 jsonObject *param = NULL;
1599 const char* perm = NULL;
1603 const char* pkey_value = NULL;
1604 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1605 // If the global_required attribute is present and true, then the only owning
1606 // org unit is the root org unit, i.e. the one with no parent.
1607 osrfLogDebug( OSRF_LOG_MARK,
1608 "global-level permissions required, fetching top of the org tree" );
1610 // no need to check perms for org tree root retrieval
1611 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1612 // check for perm at top of org tree
1613 const char* org_tree_root_id = org_tree_root( ctx );
1614 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1616 if( org_tree_root_id ) {
1617 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1618 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1620 osrfStringArrayFree( context_org_array );
1625 // If the global_required attribute is absent or false, then we look for
1626 // local and/or foreign context. In order to find the relevant foreign
1627 // keys, we must either read the relevant row from the database, or look at
1628 // the image of the row that we already have in memory.
1630 // Even if we have an image of the row in memory, that image may not include the
1631 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1632 // of the row to make sure that we have what we need.
1634 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1635 "fetching context org ids" );
1637 pkey = osrfHashGet( class, "primarykey" );
1640 // There is no primary key, so we can't do a fresh lookup. Use the row
1641 // image that we already have. If it doesn't have everything we need, too bad.
1643 param = jsonObjectClone( obj );
1644 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1645 } else if( obj->classname ) {
1646 pkey_value = oilsFMGetStringConst( obj, pkey );
1648 param = jsonObjectClone( obj );
1649 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1652 pkey_value = jsonObjectGetString( obj );
1654 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1655 "of %s and retrieving from the database", pkey_value );
1659 // Fetch the row so that we can look at the foreign key(s)
1660 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1661 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1662 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1663 jsonObjectFree( _tmp_params );
1664 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1666 param = jsonObjectExtractIndex( _list, 0 );
1667 jsonObjectFree( _list );
1673 // The row doesn't exist. Complain, and deny access.
1674 osrfLogDebug( OSRF_LOG_MARK,
1675 "Object not found in the database with primary key %s of %s",
1678 growing_buffer* msg = buffer_init( 128 );
1681 "%s: no object found with primary key %s of %s",
1687 char* m = buffer_release( msg );
1688 osrfAppSessionStatus(
1690 OSRF_STATUS_INTERNALSERVERERROR,
1691 "osrfMethodException",
1700 if( local_context && local_context->size > 0 ) {
1701 // The IDL provides a list of column names for the foreign keys denoting
1702 // local context, i.e. columns identifying owing org units directly. Look up
1703 // the value of each one, and if it isn't null, add it to the list of org units.
1704 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1705 local_context->size );
1707 const char* lcontext = NULL;
1708 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1709 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1710 if( fkey_value ) { // if not null
1711 osrfStringArrayAdd( context_org_array, fkey_value );
1714 "adding class-local field %s (value: %s) to the context org list",
1716 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1722 if( foreign_context ) {
1723 unsigned long class_count = osrfHashGetCount( foreign_context );
1724 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1726 if( class_count > 0 ) {
1728 // The IDL provides a list of foreign key columns pointing to rows that
1729 // an org unit may own. Follow each link, identify the owning org unit,
1730 // and add it to the list.
1731 osrfHash* fcontext = NULL;
1732 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1733 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1734 // For each class to which a foreign key points:
1735 const char* class_name = osrfHashIteratorKey( class_itr );
1736 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1740 "%d foreign context fields(s) specified for class %s",
1741 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1745 // Get the name of the key field in the foreign table
1746 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1748 // Get the value of the foreign key pointing to the foreign table
1749 char* foreign_pkey_value =
1750 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1751 if( !foreign_pkey_value )
1752 continue; // Foreign key value is null; skip it
1754 // Look up the row to which the foreign key points
1755 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1757 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1758 jsonObject* _list = doFieldmapperSearch(
1759 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1760 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1762 jsonObject* _fparam = NULL;
1763 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1764 _fparam = jsonObjectExtractIndex( _list, 0 );
1766 jsonObjectFree( _tmp_params );
1767 jsonObjectFree( _list );
1769 // At this point _fparam either points to the row identified by the
1770 // foreign key, or it's NULL (no such row found).
1772 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1774 const char* bad_class = NULL; // For noting failed lookups
1776 bad_class = class_name; // Referenced row not found
1777 else if( jump_list ) {
1778 // Follow a chain of rows, linked by foreign keys, to find an owner
1779 const char* flink = NULL;
1781 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1782 // For each entry in the jump list. Each entry (i.e. flink) is
1783 // the name of a foreign key column in the current row.
1785 // From the IDL, get the linkage information for the next jump
1786 osrfHash* foreign_link_hash =
1787 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1789 // Get the class metadata for the class
1790 // to which the foreign key points
1791 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1792 osrfHashGet( foreign_link_hash, "class" ));
1794 // Get the name of the referenced key of that class
1795 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1797 // Get the value of the foreign key pointing to that class
1798 free( foreign_pkey_value );
1799 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1800 if( !foreign_pkey_value )
1801 break; // Foreign key is null; quit looking
1803 // Build a WHERE clause for the lookup
1804 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1807 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1808 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1809 _tmp_params, NULL, &err );
1810 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1812 // Get the resulting row
1813 jsonObjectFree( _fparam );
1814 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1815 _fparam = jsonObjectExtractIndex( _list, 0 );
1817 // Referenced row not found
1819 bad_class = osrfHashGet( foreign_link_hash, "class" );
1822 jsonObjectFree( _tmp_params );
1823 jsonObjectFree( _list );
1829 // We had a foreign key pointing to such-and-such a row, but then
1830 // we couldn't fetch that row. The data in the database are in an
1831 // inconsistent state; the database itself may even be corrupted.
1832 growing_buffer* msg = buffer_init( 128 );
1835 "%s: no object of class %s found with primary key %s of %s",
1839 foreign_pkey_value ? foreign_pkey_value : "(null)"
1842 char* m = buffer_release( msg );
1843 osrfAppSessionStatus(
1845 OSRF_STATUS_INTERNALSERVERERROR,
1846 "osrfMethodException",
1852 osrfHashIteratorFree( class_itr );
1853 free( foreign_pkey_value );
1854 jsonObjectFree( param );
1859 free( foreign_pkey_value );
1862 // Examine each context column of the foreign row,
1863 // and add its value to the list of org units.
1865 const char* foreign_field = NULL;
1866 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1867 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1868 osrfStringArrayAdd( context_org_array,
1869 oilsFMGetStringConst( _fparam, foreign_field ));
1870 osrfLogDebug( OSRF_LOG_MARK,
1871 "adding foreign class %s field %s (value: %s) "
1872 "to the context org list",
1875 osrfStringArrayGetString(
1876 context_org_array, context_org_array->size - 1 )
1880 jsonObjectFree( _fparam );
1884 osrfHashIteratorFree( class_itr );
1889 // If there is an owning_user attached to the action, we allow that user and users with
1890 // object perms on the object. CREATE can't use this. We only do this when we're not
1891 // ignoring object perms.
1892 char* owning_user_field = osrfHashGet( pcrud, "owning_user" );
1894 *method_type != 'c' &&
1895 (!str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) || // Always honor
1898 if (owning_user_field) { // see if we can short-cut by comparing the owner to the requestor
1900 if (!param) { // We didn't get it during the context lookup
1901 pkey = osrfHashGet( class, "primarykey" );
1904 // There is no primary key, so we can't do a fresh lookup. Use the row
1905 // image that we already have. If it doesn't have everything we need, too bad.
1907 param = jsonObjectClone( obj );
1908 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1909 } else if( obj->classname ) {
1910 pkey_value = oilsFMGetStringConst( obj, pkey );
1912 param = jsonObjectClone( obj );
1913 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1916 pkey_value = jsonObjectGetString( obj );
1918 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1919 "of %s and retrieving from the database", pkey_value );
1923 // Fetch the row so that we can look at the foreign key(s)
1924 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1925 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1926 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1927 jsonObjectFree( _tmp_params );
1928 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1930 param = jsonObjectExtractIndex( _list, 0 );
1931 jsonObjectFree( _list );
1936 // The row doesn't exist. Complain, and deny access.
1937 osrfLogDebug( OSRF_LOG_MARK,
1938 "Object not found in the database with primary key %s of %s",
1941 growing_buffer* msg = buffer_init( 128 );
1944 "%s: no object found with primary key %s of %s",
1950 char* m = buffer_release( msg );
1951 osrfAppSessionStatus(
1953 OSRF_STATUS_INTERNALSERVERERROR,
1954 "osrfMethodException",
1963 int ownerid = atoi( oilsFMGetStringConst( param, owning_user_field ) );
1965 // Allow the owner to do whatever
1966 if (ownerid == userid)
1973 (perm = osrfStringArrayGetString(permission, i++)) &&
1974 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms"))
1980 "Checking object permission [%s] for user %d "
1981 "on object %s (class %s)",
1985 osrfHashGet( class, "classname" )
1988 result = dbi_conn_queryf(
1990 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s') AS has_perm;",
1993 osrfHashGet( class, "classname" ),
2000 "Received a result for object permission [%s] "
2001 "for user %d on object %s (class %s)",
2005 osrfHashGet( class, "classname" )
2008 if( dbi_result_first_row( result )) {
2009 jsonObject* return_val = oilsMakeJSONFromResult( result );
2010 const char* has_perm = jsonObjectGetString(
2011 jsonObjectGetKeyConst( return_val, "has_perm" ));
2015 "Status of object permission [%s] for user %d "
2016 "on object %s (class %s) is %s",
2020 osrfHashGet(class, "classname"),
2024 if( *has_perm == 't' )
2026 jsonObjectFree( return_val );
2029 dbi_result_free( result );
2034 int errnum = dbi_conn_error( writehandle, &msg );
2035 osrfLogWarning( OSRF_LOG_MARK,
2036 "Unable to call check object permissions: %d, %s",
2037 errnum, msg ? msg : "(No description available)" );
2038 if( !oilsIsDBConnected( writehandle ))
2039 osrfAppSessionPanic( ctx->session );
2044 // For every combination of permission and context org unit: call a stored procedure
2045 // to determine if the user has this permission in the context of this org unit.
2046 // If the answer is yes at any point, then we're done, and the user has permission.
2047 // In other words permissions are additive.
2049 while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
2052 osrfStringArray* pcache = NULL;
2053 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
2054 pcache = getPermLocationCache(ctx, perm);
2057 pcache = osrfNewStringArray(0);
2059 result = dbi_conn_queryf(
2061 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
2069 "Received a result for permission [%s] for user %d",
2074 if( dbi_result_first_row( result )) {
2076 jsonObject* return_val = oilsMakeJSONFromResult( result );
2077 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
2078 jsonObjectFree( return_val );
2079 } while( dbi_result_next_row( result ));
2081 setPermLocationCache(ctx, perm, pcache);
2084 dbi_result_free( result );
2090 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
2092 if (rs_size > perm_at_threshold) {
2093 if (osrfStringArrayContains( pcache, context_org )) {
2101 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
2103 !str_is_true( osrfHashGet(pcrud, "global_required") ) ||
2104 osrfHashGet(pcrud, "owning_user")
2109 "Checking object permission [%s] for user %d "
2110 "on object %s (class %s) at org %d",
2114 osrfHashGet( class, "classname" ),
2118 result = dbi_conn_queryf(
2120 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
2123 osrfHashGet( class, "classname" ),
2131 "Received a result for object permission [%s] "
2132 "for user %d on object %s (class %s) at org %d",
2136 osrfHashGet( class, "classname" ),
2140 if( dbi_result_first_row( result )) {
2141 jsonObject* return_val = oilsMakeJSONFromResult( result );
2142 const char* has_perm = jsonObjectGetString(
2143 jsonObjectGetKeyConst( return_val, "has_perm" ));
2147 "Status of object permission [%s] for user %d "
2148 "on object %s (class %s) at org %d is %s",
2152 osrfHashGet(class, "classname"),
2157 if( *has_perm == 't' )
2159 jsonObjectFree( return_val );
2162 dbi_result_free( result );
2167 int errnum = dbi_conn_error( writehandle, &msg );
2168 osrfLogWarning( OSRF_LOG_MARK,
2169 "Unable to call check object permissions: %d, %s",
2170 errnum, msg ? msg : "(No description available)" );
2171 if( !oilsIsDBConnected( writehandle ))
2172 osrfAppSessionPanic( ctx->session );
2176 if (rs_size > perm_at_threshold) break;
2178 osrfLogDebug( OSRF_LOG_MARK,
2179 "Checking non-object permission [%s] for user %d at org %d",
2180 perm, userid, atoi(context_org) );
2181 result = dbi_conn_queryf(
2183 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
2190 osrfLogDebug( OSRF_LOG_MARK,
2191 "Received a result for permission [%s] for user %d at org %d",
2192 perm, userid, atoi( context_org ));
2193 if( dbi_result_first_row( result )) {
2194 jsonObject* return_val = oilsMakeJSONFromResult( result );
2195 const char* has_perm = jsonObjectGetString(
2196 jsonObjectGetKeyConst( return_val, "has_perm" ));
2197 osrfLogDebug( OSRF_LOG_MARK,
2198 "Status of permission [%s] for user %d at org %d is [%s]",
2199 perm, userid, atoi( context_org ), has_perm );
2200 if( *has_perm == 't' )
2202 jsonObjectFree( return_val );
2205 dbi_result_free( result );
2210 int errnum = dbi_conn_error( writehandle, &msg );
2211 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
2212 errnum, msg ? msg : "(No description available)" );
2213 if( !oilsIsDBConnected( writehandle ))
2214 osrfAppSessionPanic( ctx->session );
2223 osrfStringArrayFree( context_org_array );
2229 @brief Look up the root of the org_unit tree.
2230 @param ctx Pointer to the method context.
2231 @return The id of the root org unit, as a character string.
2233 Query actor.org_unit where parent_ou is null, and return the id as a string.
2235 This function assumes that there is only one root org unit, i.e. that we
2236 have a single tree, not a forest.
2238 The calling code is responsible for freeing the returned string.
2240 static const char* org_tree_root( osrfMethodContext* ctx ) {
2242 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
2243 static time_t last_lookup_time = 0;
2244 time_t current_time = time( NULL );
2246 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2247 // We successfully looked this up less than an hour ago.
2248 // It's not likely to have changed since then.
2249 return strdup( cached_root_id );
2251 last_lookup_time = current_time;
2254 jsonObject* where_clause = single_hash( "parent_ou", NULL );
2255 jsonObject* result = doFieldmapperSearch(
2256 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2257 jsonObjectFree( where_clause );
2259 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2262 jsonObjectFree( result );
2264 growing_buffer* msg = buffer_init( 128 );
2265 OSRF_BUFFER_ADD( msg, modulename );
2266 OSRF_BUFFER_ADD( msg,
2267 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2269 char* m = buffer_release( msg );
2270 osrfAppSessionStatus( ctx->session,
2271 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2274 cached_root_id[ 0 ] = '\0';
2278 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2279 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2281 strcpy( cached_root_id, root_org_unit_id );
2282 jsonObjectFree( result );
2283 return cached_root_id;
2287 @brief Create a JSON_HASH with a single key/value pair.
2288 @param key The key of the key/value pair.
2289 @param value the value of the key/value pair.
2290 @return Pointer to a newly created jsonObject of type JSON_HASH.
2292 The value of the key/value is either a string or (if @a value is NULL) a null.
2294 static jsonObject* single_hash( const char* key, const char* value ) {
2296 if( ! key ) key = "";
2298 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2299 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2304 int doCreate( osrfMethodContext* ctx ) {
2305 if(osrfMethodVerifyContext( ctx )) {
2306 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2311 timeout_needs_resetting = 1;
2313 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2314 jsonObject* target = NULL;
2315 jsonObject* options = NULL;
2317 if( enforce_pcrud ) {
2318 target = jsonObjectGetIndex( ctx->params, 1 );
2319 options = jsonObjectGetIndex( ctx->params, 2 );
2321 target = jsonObjectGetIndex( ctx->params, 0 );
2322 options = jsonObjectGetIndex( ctx->params, 1 );
2325 if( !verifyObjectClass( ctx, target )) {
2326 osrfAppRespondComplete( ctx, NULL );
2330 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2332 const char* trans_id = getXactId( ctx );
2334 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2336 osrfAppSessionStatus(
2338 OSRF_STATUS_BADREQUEST,
2339 "osrfMethodException",
2341 "No active transaction -- required for CREATE"
2343 osrfAppRespondComplete( ctx, NULL );
2347 // The following test is harmless but redundant. If a class is
2348 // readonly, we don't register a create method for it.
2349 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2350 osrfAppSessionStatus(
2352 OSRF_STATUS_BADREQUEST,
2353 "osrfMethodException",
2355 "Cannot INSERT readonly class"
2357 osrfAppRespondComplete( ctx, NULL );
2361 // Set the last_xact_id
2362 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2364 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2365 trans_id, target->classname, index);
2366 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2369 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2371 dbhandle = writehandle;
2373 osrfHash* fields = osrfHashGet( meta, "fields" );
2374 char* pkey = osrfHashGet( meta, "primarykey" );
2375 char* seq = osrfHashGet( meta, "sequence" );
2377 growing_buffer* table_buf = buffer_init( 128 );
2378 growing_buffer* col_buf = buffer_init( 128 );
2379 growing_buffer* val_buf = buffer_init( 128 );
2381 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2382 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2383 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2384 buffer_add( val_buf,"VALUES (" );
2388 osrfHash* field = NULL;
2389 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2390 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2392 const char* field_name = osrfHashIteratorKey( field_itr );
2394 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2397 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2400 if( field_object && field_object->classname ) {
2401 value = oilsFMGetString(
2403 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2405 } else if( field_object && JSON_BOOL == field_object->type ) {
2406 if( jsonBoolIsTrue( field_object ) )
2407 value = strdup( "t" );
2409 value = strdup( "f" );
2411 value = jsonObjectToSimpleString( field_object );
2417 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2418 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2421 buffer_add( col_buf, field_name );
2423 if( !field_object || field_object->type == JSON_NULL ) {
2424 buffer_add( val_buf, "DEFAULT" );
2426 } else if( !strcmp( get_primitive( field ), "number" )) {
2427 const char* numtype = get_datatype( field );
2428 if( !strcmp( numtype, "INT8" )) {
2429 buffer_fadd( val_buf, "%lld", atoll( value ));
2431 } else if( !strcmp( numtype, "INT" )) {
2432 buffer_fadd( val_buf, "%d", atoi( value ));
2434 } else if( !strcmp( numtype, "NUMERIC" )) {
2435 buffer_fadd( val_buf, "%f", atof( value ));
2438 if( dbi_conn_quote_string( writehandle, &value )) {
2439 OSRF_BUFFER_ADD( val_buf, value );
2442 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2443 osrfAppSessionStatus(
2445 OSRF_STATUS_INTERNALSERVERERROR,
2446 "osrfMethodException",
2448 "Error quoting string -- please see the error log for more details"
2451 buffer_free( table_buf );
2452 buffer_free( col_buf );
2453 buffer_free( val_buf );
2454 osrfAppRespondComplete( ctx, NULL );
2462 osrfHashIteratorFree( field_itr );
2464 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2465 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2467 char* table_str = buffer_release( table_buf );
2468 char* col_str = buffer_release( col_buf );
2469 char* val_str = buffer_release( val_buf );
2470 growing_buffer* sql = buffer_init( 128 );
2471 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2476 char* query = buffer_release( sql );
2478 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2480 jsonObject* obj = NULL;
2483 dbi_result result = dbi_conn_query( writehandle, query );
2485 obj = jsonNewObject( NULL );
2487 int errnum = dbi_conn_error( writehandle, &msg );
2490 "%s ERROR inserting %s object using query [%s]: %d %s",
2492 osrfHashGet(meta, "fieldmapper"),
2495 msg ? msg : "(No description available)"
2497 osrfAppSessionStatus(
2499 OSRF_STATUS_INTERNALSERVERERROR,
2500 "osrfMethodException",
2502 "INSERT error -- please see the error log for more details"
2504 if( !oilsIsDBConnected( writehandle ))
2505 osrfAppSessionPanic( ctx->session );
2508 dbi_result_free( result );
2510 char* id = oilsFMGetString( target, pkey );
2512 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2513 growing_buffer* _id = buffer_init( 10 );
2514 buffer_fadd( _id, "%lld", new_id );
2515 id = buffer_release( _id );
2518 // Find quietness specification, if present
2519 const char* quiet_str = NULL;
2521 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2523 quiet_str = jsonObjectGetString( quiet_obj );
2526 if( str_is_true( quiet_str )) { // if quietness is specified
2527 obj = jsonNewObject( id );
2531 // Fetch the row that we just inserted, so that we can return it to the client
2532 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2533 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2536 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2540 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2542 jsonObjectFree( list );
2543 jsonObjectFree( where_clause );
2550 osrfAppRespondComplete( ctx, obj );
2551 jsonObjectFree( obj );
2556 @brief Implement the retrieve method.
2557 @param ctx Pointer to the method context.
2558 @param err Pointer through which to return an error code.
2559 @return If successful, a pointer to the result to be returned to the client;
2562 From the method's class, fetch a row with a specified value in the primary key. This
2563 method relies on the database design convention that a primary key consists of a single
2567 - authkey (PCRUD only)
2568 - value of the primary key for the desired row, for building the WHERE clause
2569 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2571 Return to client: One row from the query.
2573 int doRetrieve( osrfMethodContext* ctx ) {
2574 if(osrfMethodVerifyContext( ctx )) {
2575 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2580 timeout_needs_resetting = 1;
2585 if( enforce_pcrud ) {
2590 // Get the class metadata
2591 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2593 // Get the value of the primary key, from a method parameter
2594 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2598 "%s retrieving %s object with primary key value of %s",
2600 osrfHashGet( class_def, "fieldmapper" ),
2601 jsonObjectGetString( id_obj )
2604 // Build a WHERE clause based on the key value
2605 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2608 osrfHashGet( class_def, "primarykey" ), // name of key column
2609 jsonObjectClone( id_obj ) // value of key column
2612 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2616 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2618 jsonObjectFree( where_clause );
2620 osrfAppRespondComplete( ctx, NULL );
2624 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2625 jsonObjectFree( list );
2627 if( enforce_pcrud ) {
2628 // no result, skip this entirely
2629 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2630 jsonObjectFree( obj );
2632 growing_buffer* msg = buffer_init( 128 );
2633 OSRF_BUFFER_ADD( msg, modulename );
2634 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2636 char* m = buffer_release( msg );
2637 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2641 osrfAppRespondComplete( ctx, NULL );
2646 // doFieldmapperSearch() now does the responding for us
2647 //osrfAppRespondComplete( ctx, obj );
2648 osrfAppRespondComplete( ctx, NULL );
2650 jsonObjectFree( obj );
2655 @brief Translate a numeric value to a string representation for the database.
2656 @param field Pointer to the IDL field definition.
2657 @param value Pointer to a jsonObject holding the value of a field.
2658 @return Pointer to a newly allocated string.
2660 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2661 its contents are numeric. A non-numeric string is likely to result in invalid SQL.
2663 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2665 The calling code is responsible for freeing the resulting string by calling free().
2667 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2668 growing_buffer* val_buf = buffer_init( 32 );
2670 // If the value is a number and the DB field is numeric, no quotes needed
2671 if( value->type == JSON_NUMBER && !strcmp( get_primitive( field ), "number") ) {
2672 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2674 // Presumably this was really intended to be a string, so quote it
2675 char* str = jsonObjectToSimpleString( value );
2676 if( dbi_conn_quote_string( dbhandle, &str )) {
2677 OSRF_BUFFER_ADD( val_buf, str );
2680 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2682 buffer_free( val_buf );
2687 return buffer_release( val_buf );
2690 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2691 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2692 growing_buffer* sql_buf = buffer_init( 32 );
2698 osrfHashGet( field, "name" )
2702 buffer_add( sql_buf, "IN (" );
2703 } else if( !strcasecmp( op,"not in" )) {
2704 buffer_add( sql_buf, "NOT IN (" );
2706 buffer_add( sql_buf, "IN (" );
2709 if( node->type == JSON_HASH ) {
2710 // subquery predicate
2711 char* subpred = buildQuery( ctx, node, SUBSELECT );
2713 buffer_free( sql_buf );
2717 buffer_add( sql_buf, subpred );
2720 } else if( node->type == JSON_ARRAY ) {
2721 // literal value list
2722 int in_item_index = 0;
2723 int in_item_first = 1;
2724 const jsonObject* in_item;
2725 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2730 buffer_add( sql_buf, ", " );
2733 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2734 osrfLogError( OSRF_LOG_MARK,
2735 "%s: Expected string or number within IN list; found %s",
2736 modulename, json_type( in_item->type ) );
2737 buffer_free( sql_buf );
2741 // Append the literal value -- quoted if not a number
2742 if( JSON_NUMBER == in_item->type ) {
2743 char* val = jsonNumberToDBString( field, in_item );
2744 OSRF_BUFFER_ADD( sql_buf, val );
2747 } else if( !strcmp( get_primitive( field ), "number" )) {
2748 char* val = jsonNumberToDBString( field, in_item );
2749 OSRF_BUFFER_ADD( sql_buf, val );
2753 char* key_string = jsonObjectToSimpleString( in_item );
2754 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2755 OSRF_BUFFER_ADD( sql_buf, key_string );
2758 osrfLogError( OSRF_LOG_MARK,
2759 "%s: Error quoting key string [%s]", modulename, key_string );
2761 buffer_free( sql_buf );
2767 if( in_item_first ) {
2768 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2769 buffer_free( sql_buf );
2773 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2774 modulename, json_type( node->type ));
2775 buffer_free( sql_buf );
2779 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2781 return buffer_release( sql_buf );
2784 // Receive a JSON_ARRAY representing a function call. The first
2785 // entry in the array is the function name. The rest are parameters.
2786 static char* searchValueTransform( const jsonObject* array ) {
2788 if( array->size < 1 ) {
2789 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2793 // Get the function name
2794 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2795 if( func_item->type != JSON_STRING ) {
2796 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2797 modulename, json_type( func_item->type ));
2801 growing_buffer* sql_buf = buffer_init( 32 );
2803 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2804 OSRF_BUFFER_ADD( sql_buf, "( " );
2806 // Get the parameters
2807 int func_item_index = 1; // We already grabbed the zeroth entry
2808 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2810 // Add a separator comma, if we need one
2811 if( func_item_index > 2 )
2812 buffer_add( sql_buf, ", " );
2814 // Add the current parameter
2815 if( func_item->type == JSON_NULL ) {
2816 buffer_add( sql_buf, "NULL" );
2818 if( func_item->type == JSON_BOOL ) {
2819 if( jsonBoolIsTrue(func_item) ) {
2820 buffer_add( sql_buf, "TRUE" );
2822 buffer_add( sql_buf, "FALSE" );
2825 char* val = jsonObjectToSimpleString( func_item );
2826 if( dbi_conn_quote_string( dbhandle, &val )) {
2827 OSRF_BUFFER_ADD( sql_buf, val );
2830 osrfLogError( OSRF_LOG_MARK,
2831 "%s: Error quoting key string [%s]", modulename, val );
2832 buffer_free( sql_buf );
2840 buffer_add( sql_buf, " )" );
2842 return buffer_release( sql_buf );
2845 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2846 const jsonObject* node, const char* op ) {
2848 if( ! is_good_operator( op ) ) {
2849 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2853 char* val = searchValueTransform( node );
2857 const char* right_percent = "";
2858 const char* real_op = op;
2860 if( !strcasecmp( op, "startwith") ) {
2862 right_percent = "|| '%'";
2865 growing_buffer* sql_buf = buffer_init( 32 );
2868 "\"%s\".%s %s %s%s",
2870 osrfHashGet( field, "name" ),
2878 return buffer_release( sql_buf );
2881 // class_alias is a class name or other table alias
2882 // field is a field definition as stored in the IDL
2883 // node comes from the method parameter, and may represent an entry in the SELECT list
2884 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2885 const jsonObject* node ) {
2886 growing_buffer* sql_buf = buffer_init( 32 );
2888 const char* field_transform = jsonObjectGetString(
2889 jsonObjectGetKeyConst( node, "transform" ) );
2890 const char* transform_subcolumn = jsonObjectGetString(
2891 jsonObjectGetKeyConst( node, "result_field" ) );
2893 if( transform_subcolumn ) {
2894 if( ! is_identifier( transform_subcolumn ) ) {
2895 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2896 modulename, transform_subcolumn );
2897 buffer_free( sql_buf );
2900 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2903 if( field_transform ) {
2905 if( ! is_identifier( field_transform ) ) {
2906 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2907 modulename, field_transform );
2908 buffer_free( sql_buf );
2912 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2913 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2914 field_transform, class_alias, osrfHashGet( field, "name" ));
2916 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2917 field_transform, class_alias, osrfHashGet( field, "name" ));
2920 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2923 if( array->type != JSON_ARRAY ) {
2924 osrfLogError( OSRF_LOG_MARK,
2925 "%s: Expected JSON_ARRAY for function params; found %s",
2926 modulename, json_type( array->type ) );
2927 buffer_free( sql_buf );
2930 int func_item_index = 0;
2931 jsonObject* func_item;
2932 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2934 char* val = jsonObjectToSimpleString( func_item );
2937 buffer_add( sql_buf, ",NULL" );
2938 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2939 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2940 OSRF_BUFFER_ADD( sql_buf, val );
2942 osrfLogError( OSRF_LOG_MARK,
2943 "%s: Error quoting key string [%s]", modulename, val );
2945 buffer_free( sql_buf );
2952 buffer_add( sql_buf, " )" );
2955 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2958 if( transform_subcolumn )
2959 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2961 return buffer_release( sql_buf );
2964 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2965 const jsonObject* node, const char* op ) {
2967 if( ! is_good_operator( op ) ) {
2968 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2972 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2973 if( ! field_transform )
2976 int extra_parens = 0; // boolean
2978 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2980 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2982 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2984 free( field_transform );
2988 } else if( value_obj->type == JSON_ARRAY ) {
2989 value = searchValueTransform( value_obj );
2991 osrfLogError( OSRF_LOG_MARK,
2992 "%s: Error building value transform for field transform", modulename );
2993 free( field_transform );
2996 } else if( value_obj->type == JSON_HASH ) {
2997 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2999 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
3001 free( field_transform );
3005 } else if( value_obj->type == JSON_NUMBER ) {
3006 value = jsonNumberToDBString( field, value_obj );
3007 } else if( value_obj->type == JSON_NULL ) {
3008 osrfLogError( OSRF_LOG_MARK,
3009 "%s: Error building predicate for field transform: null value", modulename );
3010 free( field_transform );
3012 } else if( value_obj->type == JSON_BOOL ) {
3013 osrfLogError( OSRF_LOG_MARK,
3014 "%s: Error building predicate for field transform: boolean value", modulename );
3015 free( field_transform );
3018 if( !strcmp( get_primitive( field ), "number") ) {
3019 value = jsonNumberToDBString( field, value_obj );
3021 value = jsonObjectToSimpleString( value_obj );
3022 if( !dbi_conn_quote_string( dbhandle, &value )) {
3023 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3024 modulename, value );
3026 free( field_transform );
3032 const char* left_parens = "";
3033 const char* right_parens = "";
3035 if( extra_parens ) {
3040 const char* right_percent = "";
3041 const char* real_op = op;
3043 if( !strcasecmp( op, "startwith") ) {
3045 right_percent = "|| '%'";
3048 growing_buffer* sql_buf = buffer_init( 32 );
3052 "%s%s %s %s %s%s %s%s",
3064 free( field_transform );
3066 return buffer_release( sql_buf );
3069 static char* searchSimplePredicate( const char* op, const char* class_alias,
3070 osrfHash* field, const jsonObject* node ) {
3072 if( ! is_good_operator( op ) ) {
3073 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
3079 // Get the value to which we are comparing the specified column
3080 if( node->type != JSON_NULL ) {
3081 if( node->type == JSON_NUMBER ) {
3082 val = jsonNumberToDBString( field, node );
3083 } else if( !strcmp( get_primitive( field ), "number" ) ) {
3084 val = jsonNumberToDBString( field, node );
3086 val = jsonObjectToSimpleString( node );
3091 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
3092 // Value is not numeric; enclose it in quotes
3093 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
3094 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3101 // Compare to a null value
3102 val = strdup( "NULL" );
3103 if( strcmp( op, "=" ))
3109 const char* right_percent = "";
3110 const char* real_op = op;
3112 if( !strcasecmp( op, "startwith") ) {
3114 right_percent = "|| '%'";
3117 growing_buffer* sql_buf = buffer_init( 32 );
3118 buffer_fadd( sql_buf, "\"%s\".%s %s %s%s", class_alias, osrfHashGet(field, "name"), real_op, val, right_percent );
3119 char* pred = buffer_release( sql_buf );
3126 static char* searchBETWEENPredicate( const char* class_alias,
3127 osrfHash* field, const jsonObject* node ) {
3129 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
3130 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
3132 if( NULL == y_node ) {
3133 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
3136 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
3137 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
3144 if( !strcmp( get_primitive( field ), "number") ) {
3145 x_string = jsonNumberToDBString( field, x_node );
3146 y_string = jsonNumberToDBString( field, y_node );
3149 x_string = jsonObjectToSimpleString( x_node );
3150 y_string = jsonObjectToSimpleString( y_node );
3151 if( !(dbi_conn_quote_string( dbhandle, &x_string )
3152 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
3153 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
3154 modulename, x_string, y_string );
3161 growing_buffer* sql_buf = buffer_init( 32 );
3162 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
3163 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
3167 return buffer_release( sql_buf );
3170 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
3171 jsonObject* node, osrfMethodContext* ctx ) {
3174 if( node->type == JSON_ARRAY ) { // equality IN search
3175 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
3176 } else if( node->type == JSON_HASH ) { // other search
3177 jsonIterator* pred_itr = jsonNewIterator( node );
3178 if( !jsonIteratorHasNext( pred_itr ) ) {
3179 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
3180 modulename, osrfHashGet(field, "name" ));
3182 jsonObject* pred_node = jsonIteratorNext( pred_itr );
3184 // Verify that there are no additional predicates
3185 if( jsonIteratorHasNext( pred_itr ) ) {
3186 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
3187 modulename, osrfHashGet(field, "name" ));
3188 } else if( !(strcasecmp( pred_itr->key,"between" )) )
3189 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
3190 else if( !(strcasecmp( pred_itr->key,"in" ))
3191 || !(strcasecmp( pred_itr->key,"not in" )) )
3192 pred = searchINPredicate(
3193 class_info->alias, field, pred_node, pred_itr->key, ctx );
3194 else if( pred_node->type == JSON_ARRAY )
3195 pred = searchFunctionPredicate(
3196 class_info->alias, field, pred_node, pred_itr->key );
3197 else if( pred_node->type == JSON_HASH )
3198 pred = searchFieldTransformPredicate(
3199 class_info, field, pred_node, pred_itr->key );
3201 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
3203 jsonIteratorFree( pred_itr );
3205 } else if( node->type == JSON_NULL ) { // IS NULL search
3206 growing_buffer* _p = buffer_init( 64 );
3209 "\"%s\".%s IS NULL",
3211 osrfHashGet( field, "name" )
3213 pred = buffer_release( _p );
3214 } else { // equality search
3215 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3234 field : call_number,
3250 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3252 const jsonObject* working_hash;
3253 jsonObject* freeable_hash = NULL;
3255 if( join_hash->type == JSON_HASH ) {
3256 working_hash = join_hash;
3257 } else if( join_hash->type == JSON_STRING ) {
3258 // turn it into a JSON_HASH by creating a wrapper
3259 // around a copy of the original
3260 const char* _tmp = jsonObjectGetString( join_hash );
3261 freeable_hash = jsonNewObjectType( JSON_HASH );
3262 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3263 working_hash = freeable_hash;
3267 "%s: JOIN failed; expected JSON object type not found",
3273 growing_buffer* join_buf = buffer_init( 128 );
3274 const char* leftclass = left_info->class_name;
3276 jsonObject* snode = NULL;
3277 jsonIterator* search_itr = jsonNewIterator( working_hash );
3279 while ( (snode = jsonIteratorNext( search_itr )) ) {
3280 const char* right_alias = search_itr->key;
3282 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3284 class = right_alias;
3286 const ClassInfo* right_info = add_joined_class( right_alias, class );
3290 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3294 jsonIteratorFree( search_itr );
3295 buffer_free( join_buf );
3297 jsonObjectFree( freeable_hash );
3300 osrfHash* links = right_info->links;
3301 const char* table = right_info->source_def;
3303 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3304 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3306 if( field && !fkey ) {
3307 // Look up the corresponding join column in the IDL.
3308 // The link must be defined in the child table,
3309 // and point to the right parent table.
3310 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3311 const char* reltype = NULL;
3312 const char* other_class = NULL;
3313 reltype = osrfHashGet( idl_link, "reltype" );
3314 if( reltype && strcmp( reltype, "has_many" ) )
3315 other_class = osrfHashGet( idl_link, "class" );
3316 if( other_class && !strcmp( other_class, leftclass ) )
3317 fkey = osrfHashGet( idl_link, "key" );
3321 "%s: JOIN failed. No link defined from %s.%s to %s",
3327 buffer_free( join_buf );
3329 jsonObjectFree( freeable_hash );
3330 jsonIteratorFree( search_itr );
3334 } else if( !field && fkey ) {
3335 // Look up the corresponding join column in the IDL.
3336 // The link must be defined in the child table,
3337 // and point to the right parent table.
3338 osrfHash* left_links = left_info->links;
3339 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3340 const char* reltype = NULL;
3341 const char* other_class = NULL;
3342 reltype = osrfHashGet( idl_link, "reltype" );
3343 if( reltype && strcmp( reltype, "has_many" ) )
3344 other_class = osrfHashGet( idl_link, "class" );
3345 if( other_class && !strcmp( other_class, class ) )
3346 field = osrfHashGet( idl_link, "key" );
3350 "%s: JOIN failed. No link defined from %s.%s to %s",
3356 buffer_free( join_buf );
3358 jsonObjectFree( freeable_hash );
3359 jsonIteratorFree( search_itr );
3363 } else if( !field && !fkey ) {
3364 osrfHash* left_links = left_info->links;
3366 // For each link defined for the left class:
3367 // see if the link references the joined class
3368 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3369 osrfHash* curr_link = NULL;
3370 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3371 const char* other_class = osrfHashGet( curr_link, "class" );
3372 if( other_class && !strcmp( other_class, class ) ) {
3374 // In the IDL, the parent class doesn't always know then names of the child
3375 // columns that are pointing to it, so don't use that end of the link
3376 const char* reltype = osrfHashGet( curr_link, "reltype" );
3377 if( reltype && strcmp( reltype, "has_many" ) ) {
3378 // Found a link between the classes
3379 fkey = osrfHashIteratorKey( itr );
3380 field = osrfHashGet( curr_link, "key" );
3385 osrfHashIteratorFree( itr );
3387 if( !field || !fkey ) {
3388 // Do another such search, with the classes reversed
3390 // For each link defined for the joined class:
3391 // see if the link references the left class
3392 osrfHashIterator* itr = osrfNewHashIterator( links );
3393 osrfHash* curr_link = NULL;
3394 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3395 const char* other_class = osrfHashGet( curr_link, "class" );
3396 if( other_class && !strcmp( other_class, leftclass ) ) {
3398 // In the IDL, the parent class doesn't know then names of the child
3399 // columns that are pointing to it, so don't use that end of the link
3400 const char* reltype = osrfHashGet( curr_link, "reltype" );
3401 if( reltype && strcmp( reltype, "has_many" ) ) {
3402 // Found a link between the classes
3403 field = osrfHashIteratorKey( itr );
3404 fkey = osrfHashGet( curr_link, "key" );
3409 osrfHashIteratorFree( itr );
3412 if( !field || !fkey ) {
3415 "%s: JOIN failed. No link defined between %s and %s",
3420 buffer_free( join_buf );
3422 jsonObjectFree( freeable_hash );
3423 jsonIteratorFree( search_itr );
3428 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3430 if( !strcasecmp( type,"left" )) {
3431 buffer_add( join_buf, " LEFT JOIN" );
3432 } else if( !strcasecmp( type,"right" )) {
3433 buffer_add( join_buf, " RIGHT JOIN" );
3434 } else if( !strcasecmp( type,"full" )) {
3435 buffer_add( join_buf, " FULL JOIN" );
3437 buffer_add( join_buf, " INNER JOIN" );
3440 buffer_add( join_buf, " INNER JOIN" );
3443 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3444 table, right_alias, right_alias, field, left_info->alias, fkey );
3446 // Add any other join conditions as specified by "filter"
3447 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3449 const char* filter_op = jsonObjectGetString(
3450 jsonObjectGetKeyConst( snode, "filter_op" ) );
3451 if( filter_op && !strcasecmp( "or",filter_op )) {
3452 buffer_add( join_buf, " OR " );
3454 buffer_add( join_buf, " AND " );
3457 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3459 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3460 OSRF_BUFFER_ADD( join_buf, jpred );
3465 "%s: JOIN failed. Invalid conditional expression.",
3468 jsonIteratorFree( search_itr );
3469 buffer_free( join_buf );
3471 jsonObjectFree( freeable_hash );
3476 buffer_add( join_buf, " ) " );
3478 // Recursively add a nested join, if one is present
3479 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3481 char* jpred = searchJOIN( join_filter, right_info );
3483 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3484 OSRF_BUFFER_ADD( join_buf, jpred );
3487 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3488 jsonIteratorFree( search_itr );
3489 buffer_free( join_buf );
3491 jsonObjectFree( freeable_hash );
3498 jsonObjectFree( freeable_hash );
3499 jsonIteratorFree( search_itr );
3501 return buffer_release( join_buf );
3506 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3507 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3508 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3510 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3512 search_hash is the JSON expression of the conditions.
3513 meta is the class definition from the IDL, for the relevant table.
3514 opjoin_type indicates whether multiple conditions, if present, should be
3515 connected by AND or OR.
3516 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3517 to pass it to other functions -- and all they do with it is to use the session
3518 and request members to send error messages back to the client.
3522 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3523 int opjoin_type, osrfMethodContext* ctx ) {
3527 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3528 "opjoin_type = %d, ctx addr = %p",
3531 class_info->class_def,
3536 growing_buffer* sql_buf = buffer_init( 128 );
3538 jsonObject* node = NULL;
3541 if( search_hash->type == JSON_ARRAY ) {
3542 if( 0 == search_hash->size ) {
3545 "%s: Invalid predicate structure: empty JSON array",
3548 buffer_free( sql_buf );
3552 unsigned long i = 0;
3553 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3557 if( opjoin_type == OR_OP_JOIN )
3558 buffer_add( sql_buf, " OR " );
3560 buffer_add( sql_buf, " AND " );
3563 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3565 buffer_free( sql_buf );
3569 buffer_fadd( sql_buf, "( %s )", subpred );
3573 } else if( search_hash->type == JSON_HASH ) {
3574 osrfLogDebug( OSRF_LOG_MARK,
3575 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3576 jsonIterator* search_itr = jsonNewIterator( search_hash );
3577 if( !jsonIteratorHasNext( search_itr ) ) {
3580 "%s: Invalid predicate structure: empty JSON object",
3583 jsonIteratorFree( search_itr );
3584 buffer_free( sql_buf );
3588 while( (node = jsonIteratorNext( search_itr )) ) {
3593 if( opjoin_type == OR_OP_JOIN )
3594 buffer_add( sql_buf, " OR " );
3596 buffer_add( sql_buf, " AND " );
3599 if( '+' == search_itr->key[ 0 ] ) {
3601 // This plus sign prefixes a class name or other table alias;
3602 // make sure the table alias is in scope
3603 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3604 if( ! alias_info ) {
3607 "%s: Invalid table alias \"%s\" in WHERE clause",
3611 jsonIteratorFree( search_itr );
3612 buffer_free( sql_buf );
3616 if( node->type == JSON_STRING ) {
3617 // It's the name of a column; make sure it belongs to the class
3618 const char* fieldname = jsonObjectGetString( node );
3619 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3622 "%s: Invalid column name \"%s\" in WHERE clause "
3623 "for table alias \"%s\"",
3628 jsonIteratorFree( search_itr );
3629 buffer_free( sql_buf );
3633 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3635 // It's something more complicated
3636 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3638 jsonIteratorFree( search_itr );
3639 buffer_free( sql_buf );
3643 buffer_fadd( sql_buf, "( %s )", subpred );
3646 } else if( '-' == search_itr->key[ 0 ] ) {
3647 if( !strcasecmp( "-or", search_itr->key )) {
3648 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3650 jsonIteratorFree( search_itr );
3651 buffer_free( sql_buf );
3655 buffer_fadd( sql_buf, "( %s )", subpred );
3657 } else if( !strcasecmp( "-and", search_itr->key )) {
3658 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3660 jsonIteratorFree( search_itr );
3661 buffer_free( sql_buf );
3665 buffer_fadd( sql_buf, "( %s )", subpred );
3667 } else if( !strcasecmp("-not",search_itr->key) ) {
3668 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3670 jsonIteratorFree( search_itr );
3671 buffer_free( sql_buf );
3675 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3677 } else if( !strcasecmp( "-exists", search_itr->key )) {
3678 char* subpred = buildQuery( ctx, node, SUBSELECT );
3680 jsonIteratorFree( search_itr );
3681 buffer_free( sql_buf );
3685 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3687 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3688 char* subpred = buildQuery( ctx, node, SUBSELECT );
3690 jsonIteratorFree( search_itr );
3691 buffer_free( sql_buf );
3695 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3697 } else { // Invalid "minus" operator
3700 "%s: Invalid operator \"%s\" in WHERE clause",
3704 jsonIteratorFree( search_itr );
3705 buffer_free( sql_buf );
3711 const char* class = class_info->class_name;
3712 osrfHash* fields = class_info->fields;
3713 osrfHash* field = osrfHashGet( fields, search_itr->key );
3716 const char* table = class_info->source_def;
3719 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3722 table ? table : "?",
3725 jsonIteratorFree( search_itr );
3726 buffer_free( sql_buf );
3730 char* subpred = searchPredicate( class_info, field, node, ctx );
3732 buffer_free( sql_buf );
3733 jsonIteratorFree( search_itr );
3737 buffer_add( sql_buf, subpred );
3741 jsonIteratorFree( search_itr );
3744 // ERROR ... only hash and array allowed at this level
3745 char* predicate_string = jsonObjectToJSON( search_hash );
3748 "%s: Invalid predicate structure: %s",
3752 buffer_free( sql_buf );
3753 free( predicate_string );
3757 return buffer_release( sql_buf );
3760 /* Build a JSON_ARRAY of field names for a given table alias
3762 static jsonObject* defaultSelectList( const char* table_alias ) {
3767 ClassInfo* class_info = search_all_alias( table_alias );
3768 if( ! class_info ) {
3771 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3778 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3779 osrfHash* field_def = NULL;
3780 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3781 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3782 const char* field_name = osrfHashIteratorKey( field_itr );
3783 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3784 jsonObjectPush( array, jsonNewObject( field_name ) );
3787 osrfHashIteratorFree( field_itr );
3792 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3793 // The jsonObject must be a JSON_HASH with an single entry for "union",
3794 // "intersect", or "except". The data associated with this key must be an
3795 // array of hashes, each hash being a query.
3796 // Also allowed but currently ignored: entries for "order_by" and "alias".
3797 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3799 if( ! combo || combo->type != JSON_HASH )
3800 return NULL; // should be impossible; validated by caller
3802 const jsonObject* query_array = NULL; // array of subordinate queries
3803 const char* op = NULL; // name of operator, e.g. UNION
3804 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3805 int op_count = 0; // for detecting conflicting operators
3806 int excepting = 0; // boolean
3807 int all = 0; // boolean
3808 jsonObject* order_obj = NULL;
3810 // Identify the elements in the hash
3811 jsonIterator* query_itr = jsonNewIterator( combo );
3812 jsonObject* curr_obj = NULL;
3813 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3814 if( ! strcmp( "union", query_itr->key ) ) {
3817 query_array = curr_obj;
3818 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3821 query_array = curr_obj;
3822 } else if( ! strcmp( "except", query_itr->key ) ) {
3826 query_array = curr_obj;
3827 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3830 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3833 order_obj = curr_obj;
3834 } else if( ! strcmp( "alias", query_itr->key ) ) {
3835 if( curr_obj->type != JSON_STRING ) {
3836 jsonIteratorFree( query_itr );
3839 alias = jsonObjectGetString( curr_obj );
3840 } else if( ! strcmp( "all", query_itr->key ) ) {
3841 if( obj_is_true( curr_obj ) )
3845 osrfAppSessionStatus(
3847 OSRF_STATUS_INTERNALSERVERERROR,
3848 "osrfMethodException",
3850 "Malformed query; unexpected entry in query object"
3854 "%s: Unexpected entry for \"%s\" in%squery",
3859 jsonIteratorFree( query_itr );
3863 jsonIteratorFree( query_itr );
3865 // More sanity checks
3866 if( ! query_array ) {
3868 osrfAppSessionStatus(
3870 OSRF_STATUS_INTERNALSERVERERROR,
3871 "osrfMethodException",
3873 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3877 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3880 return NULL; // should be impossible...
3881 } else if( op_count > 1 ) {
3883 osrfAppSessionStatus(
3885 OSRF_STATUS_INTERNALSERVERERROR,
3886 "osrfMethodException",
3888 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3892 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3896 } if( query_array->type != JSON_ARRAY ) {
3898 osrfAppSessionStatus(
3900 OSRF_STATUS_INTERNALSERVERERROR,
3901 "osrfMethodException",
3903 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3907 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3910 json_type( query_array->type )
3913 } if( query_array->size < 2 ) {
3915 osrfAppSessionStatus(
3917 OSRF_STATUS_INTERNALSERVERERROR,
3918 "osrfMethodException",
3920 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3924 "%s:%srequires multiple queries as operands",
3929 } else if( excepting && query_array->size > 2 ) {
3931 osrfAppSessionStatus(
3933 OSRF_STATUS_INTERNALSERVERERROR,
3934 "osrfMethodException",
3936 "EXCEPT operator has too many queries as operands"
3940 "%s:EXCEPT operator has too many queries as operands",
3944 } else if( order_obj && ! alias ) {
3946 osrfAppSessionStatus(
3948 OSRF_STATUS_INTERNALSERVERERROR,
3949 "osrfMethodException",
3951 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3955 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3961 // So far so good. Now build the SQL.
3962 growing_buffer* sql = buffer_init( 256 );
3964 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3965 // Add a layer of parentheses
3966 if( flags & SUBCOMBO )
3967 OSRF_BUFFER_ADD( sql, "( " );
3969 // Traverse the query array. Each entry should be a hash.
3970 int first = 1; // boolean
3972 jsonObject* query = NULL;
3973 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3974 if( query->type != JSON_HASH ) {
3976 osrfAppSessionStatus(
3978 OSRF_STATUS_INTERNALSERVERERROR,
3979 "osrfMethodException",
3981 "Malformed query under UNION, INTERSECT or EXCEPT"
3985 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3988 json_type( query->type )
3997 OSRF_BUFFER_ADD( sql, op );
3999 OSRF_BUFFER_ADD( sql, "ALL " );
4002 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
4006 "%s: Error building query under%s",
4014 OSRF_BUFFER_ADD( sql, query_str );
4017 if( flags & SUBCOMBO )
4018 OSRF_BUFFER_ADD_CHAR( sql, ')' );
4020 if( !(flags & SUBSELECT) )
4021 OSRF_BUFFER_ADD_CHAR( sql, ';' );
4023 return buffer_release( sql );
4026 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
4027 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
4028 // or "except" to indicate the type of query.
4029 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
4033 osrfAppSessionStatus(
4035 OSRF_STATUS_INTERNALSERVERERROR,
4036 "osrfMethodException",
4038 "Malformed query; no query object"
4040 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
4042 } else if( query->type != JSON_HASH ) {
4044 osrfAppSessionStatus(
4046 OSRF_STATUS_INTERNALSERVERERROR,
4047 "osrfMethodException",
4049 "Malformed query object"
4053 "%s: Query object is %s instead of JSON_HASH",
4055 json_type( query->type )
4060 // Determine what kind of query it purports to be, and dispatch accordingly.
4061 if( jsonObjectGetKeyConst( query, "union" ) ||
4062 jsonObjectGetKeyConst( query, "intersect" ) ||
4063 jsonObjectGetKeyConst( query, "except" )) {
4064 return doCombo( ctx, query, flags );
4066 // It is presumably a SELECT query
4068 // Push a node onto the stack for the current query. Every level of
4069 // subquery gets its own QueryFrame on the Stack.
4072 // Build an SQL SELECT statement
4075 jsonObjectGetKey( query, "select" ),
4076 jsonObjectGetKeyConst( query, "from" ),
4077 jsonObjectGetKeyConst( query, "where" ),
4078 jsonObjectGetKeyConst( query, "having" ),
4079 jsonObjectGetKeyConst( query, "order_by" ),
4080 jsonObjectGetKeyConst( query, "limit" ),
4081 jsonObjectGetKeyConst( query, "offset" ),
4090 /* method context */ osrfMethodContext* ctx,
4092 /* SELECT */ jsonObject* selhash,
4093 /* FROM */ const jsonObject* join_hash,
4094 /* WHERE */ const jsonObject* search_hash,
4095 /* HAVING */ const jsonObject* having_hash,
4096 /* ORDER BY */ const jsonObject* order_hash,
4097 /* LIMIT */ const jsonObject* limit,
4098 /* OFFSET */ const jsonObject* offset,
4099 /* flags */ int flags
4101 const char* locale = osrf_message_get_last_locale();
4103 // general tmp objects
4104 const jsonObject* tmp_const;
4105 jsonObject* selclass = NULL;
4106 jsonObject* snode = NULL;
4107 jsonObject* onode = NULL;
4109 char* string = NULL;
4110 int from_function = 0;
4115 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
4117 // punt if there's no FROM clause
4118 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
4121 "%s: FROM clause is missing or empty",
4125 osrfAppSessionStatus(
4127 OSRF_STATUS_INTERNALSERVERERROR,
4128 "osrfMethodException",
4130 "FROM clause is missing or empty in JSON query"
4135 // the core search class
4136 const char* core_class = NULL;
4138 // get the core class -- the only key of the top level FROM clause, or a string
4139 if( join_hash->type == JSON_HASH ) {
4140 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
4141 snode = jsonIteratorNext( tmp_itr );
4143 // Populate the current QueryFrame with information
4144 // about the core class
4145 if( add_query_core( NULL, tmp_itr->key ) ) {
4147 osrfAppSessionStatus(
4149 OSRF_STATUS_INTERNALSERVERERROR,
4150 "osrfMethodException",
4152 "Unable to look up core class"
4156 core_class = curr_query->core.class_name;
4159 jsonObject* extra = jsonIteratorNext( tmp_itr );
4161 jsonIteratorFree( tmp_itr );
4164 // There shouldn't be more than one entry in join_hash
4168 "%s: Malformed FROM clause: extra entry in JSON_HASH",
4172 osrfAppSessionStatus(
4174 OSRF_STATUS_INTERNALSERVERERROR,
4175 "osrfMethodException",
4177 "Malformed FROM clause in JSON query"
4179 return NULL; // Malformed join_hash; extra entry
4181 } else if( join_hash->type == JSON_ARRAY ) {
4182 // We're selecting from a function, not from a table
4184 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
4187 } else if( join_hash->type == JSON_STRING ) {
4188 // Populate the current QueryFrame with information
4189 // about the core class
4190 core_class = jsonObjectGetString( join_hash );
4192 if( add_query_core( NULL, core_class ) ) {
4194 osrfAppSessionStatus(
4196 OSRF_STATUS_INTERNALSERVERERROR,
4197 "osrfMethodException",
4199 "Unable to look up core class"
4207 "%s: FROM clause is unexpected JSON type: %s",
4209 json_type( join_hash->type )
4212 osrfAppSessionStatus(
4214 OSRF_STATUS_INTERNALSERVERERROR,
4215 "osrfMethodException",
4217 "Ill-formed FROM clause in JSON query"
4222 // Build the join clause, if any, while filling out the list
4223 // of joined classes in the current QueryFrame.
4224 char* join_clause = NULL;
4225 if( join_hash && ! from_function ) {
4227 join_clause = searchJOIN( join_hash, &curr_query->core );
4228 if( ! join_clause ) {
4230 osrfAppSessionStatus(
4232 OSRF_STATUS_INTERNALSERVERERROR,
4233 "osrfMethodException",
4235 "Unable to construct JOIN clause(s)"
4241 // For in case we don't get a select list
4242 jsonObject* defaultselhash = NULL;
4244 // if there is no select list, build a default select list ...
4245 if( !selhash && !from_function ) {
4246 jsonObject* default_list = defaultSelectList( core_class );
4247 if( ! default_list ) {
4249 osrfAppSessionStatus(
4251 OSRF_STATUS_INTERNALSERVERERROR,
4252 "osrfMethodException",
4254 "Unable to build default SELECT clause in JSON query"
4256 free( join_clause );
4261 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4262 jsonObjectSetKey( selhash, core_class, default_list );
4265 // The SELECT clause can be encoded only by a hash
4266 if( !from_function && selhash->type != JSON_HASH ) {
4269 "%s: Expected JSON_HASH for SELECT clause; found %s",
4271 json_type( selhash->type )
4275 osrfAppSessionStatus(
4277 OSRF_STATUS_INTERNALSERVERERROR,
4278 "osrfMethodException",
4280 "Malformed SELECT clause in JSON query"
4282 free( join_clause );
4286 // If you see a null or wild card specifier for the core class, or an
4287 // empty array, replace it with a default SELECT list
4288 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4290 int default_needed = 0; // boolean
4291 if( JSON_STRING == tmp_const->type
4292 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4294 else if( JSON_NULL == tmp_const->type )
4297 if( default_needed ) {
4298 // Build a default SELECT list
4299 jsonObject* default_list = defaultSelectList( core_class );
4300 if( ! default_list ) {
4302 osrfAppSessionStatus(
4304 OSRF_STATUS_INTERNALSERVERERROR,
4305 "osrfMethodException",
4307 "Can't build default SELECT clause in JSON query"
4309 free( join_clause );
4314 jsonObjectSetKey( selhash, core_class, default_list );
4318 // temp buffers for the SELECT list and GROUP BY clause
4319 growing_buffer* select_buf = buffer_init( 128 );
4320 growing_buffer* group_buf = buffer_init( 128 );
4322 int aggregate_found = 0; // boolean
4324 // Build a select list
4325 if( from_function ) // From a function we select everything
4326 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4329 // Build the SELECT list as SQL
4333 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4334 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4336 const char* cname = selclass_itr->key;
4338 // Make sure the target relation is in the FROM clause.
4340 // At this point join_hash is a step down from the join_hash we
4341 // received as a parameter. If the original was a JSON_STRING,
4342 // then json_hash is now NULL. If the original was a JSON_HASH,
4343 // then json_hash is now the first (and only) entry in it,
4344 // denoting the core class. We've already excluded the
4345 // possibility that the original was a JSON_ARRAY, because in
4346 // that case from_function would be non-NULL, and we wouldn't
4349 // If the current table alias isn't in scope, bail out
4350 ClassInfo* class_info = search_alias( cname );
4351 if( ! class_info ) {
4354 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4359 osrfAppSessionStatus(
4361 OSRF_STATUS_INTERNALSERVERERROR,
4362 "osrfMethodException",
4364 "Selected class not in FROM clause in JSON query"
4366 jsonIteratorFree( selclass_itr );
4367 buffer_free( select_buf );
4368 buffer_free( group_buf );
4369 if( defaultselhash )
4370 jsonObjectFree( defaultselhash );
4371 free( join_clause );
4375 if( selclass->type != JSON_ARRAY ) {
4378 "%s: Malformed SELECT list for class \"%s\"; not an array",
4383 osrfAppSessionStatus(
4385 OSRF_STATUS_INTERNALSERVERERROR,
4386 "osrfMethodException",
4388 "Selected class not in FROM clause in JSON query"
4391 jsonIteratorFree( selclass_itr );
4392 buffer_free( select_buf );
4393 buffer_free( group_buf );
4394 if( defaultselhash )
4395 jsonObjectFree( defaultselhash );
4396 free( join_clause );
4400 // Look up some attributes of the current class
4401 osrfHash* idlClass = class_info->class_def;
4402 osrfHash* class_field_set = class_info->fields;
4403 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4404 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4406 if( 0 == selclass->size ) {
4409 "%s: No columns selected from \"%s\"",
4415 // stitch together the column list for the current table alias...
4416 unsigned long field_idx = 0;
4417 jsonObject* selfield = NULL;
4418 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4420 // If we need a separator comma, add one
4424 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4427 // if the field specification is a string, add it to the list
4428 if( selfield->type == JSON_STRING ) {
4430 // Look up the field in the IDL
4431 const char* col_name = jsonObjectGetString( selfield );
4432 osrfHash* field_def = NULL;
4434 if (!osrfStringArrayContains(
4436 osrfHashGet( class_field_set, col_name ),
4437 "suppress_controller"),
4440 field_def = osrfHashGet( class_field_set, col_name );
4443 // No such field in current class
4446 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4452 osrfAppSessionStatus(
4454 OSRF_STATUS_INTERNALSERVERERROR,
4455 "osrfMethodException",
4457 "Selected column not defined in JSON query"
4459 jsonIteratorFree( selclass_itr );
4460 buffer_free( select_buf );
4461 buffer_free( group_buf );
4462 if( defaultselhash )
4463 jsonObjectFree( defaultselhash );
4464 free( join_clause );
4466 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4467 // Virtual field not allowed
4470 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4476 osrfAppSessionStatus(
4478 OSRF_STATUS_INTERNALSERVERERROR,
4479 "osrfMethodException",
4481 "Selected column may not be virtual in JSON query"
4483 jsonIteratorFree( selclass_itr );
4484 buffer_free( select_buf );
4485 buffer_free( group_buf );
4486 if( defaultselhash )
4487 jsonObjectFree( defaultselhash );
4488 free( join_clause );
4494 if( flags & DISABLE_I18N )
4497 i18n = osrfHashGet( field_def, "i18n" );
4499 if( str_is_true( i18n ) ) {
4500 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4501 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4502 class_tname, cname, col_name, class_pkey,
4503 cname, class_pkey, locale, col_name );
4505 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4506 cname, col_name, col_name );
4509 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4510 cname, col_name, col_name );
4513 // ... but it could be an object, in which case we check for a Field Transform
4514 } else if( selfield->type == JSON_HASH ) {
4516 const char* col_name = jsonObjectGetString(
4517 jsonObjectGetKeyConst( selfield, "column" ) );
4519 // Get the field definition from the IDL
4520 osrfHash* field_def = NULL;
4521 if (!osrfStringArrayContains(
4523 osrfHashGet( class_field_set, col_name ),
4524 "suppress_controller"),
4527 field_def = osrfHashGet( class_field_set, col_name );
4531 // No such field in current class
4534 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4540 osrfAppSessionStatus(
4542 OSRF_STATUS_INTERNALSERVERERROR,
4543 "osrfMethodException",
4545 "Selected column is not defined in JSON query"
4547 jsonIteratorFree( selclass_itr );
4548 buffer_free( select_buf );
4549 buffer_free( group_buf );
4550 if( defaultselhash )
4551 jsonObjectFree( defaultselhash );
4552 free( join_clause );
4554 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4555 // No such field in current class
4558 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4564 osrfAppSessionStatus(
4566 OSRF_STATUS_INTERNALSERVERERROR,
4567 "osrfMethodException",
4569 "Selected column is virtual in JSON query"
4571 jsonIteratorFree( selclass_itr );
4572 buffer_free( select_buf );
4573 buffer_free( group_buf );
4574 if( defaultselhash )
4575 jsonObjectFree( defaultselhash );
4576 free( join_clause );
4580 // Decide what to use as a column alias
4582 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4583 _alias = jsonObjectGetString( tmp_const );
4584 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4585 _alias = jsonObjectGetString( tmp_const );
4586 } else { // Use field name as the alias
4590 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4591 char* transform_str = searchFieldTransform(
4592 class_info->alias, field_def, selfield );
4593 if( transform_str ) {
4594 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4595 free( transform_str );
4598 osrfAppSessionStatus(
4600 OSRF_STATUS_INTERNALSERVERERROR,
4601 "osrfMethodException",
4603 "Unable to generate transform function in JSON query"
4605 jsonIteratorFree( selclass_itr );
4606 buffer_free( select_buf );
4607 buffer_free( group_buf );
4608 if( defaultselhash )
4609 jsonObjectFree( defaultselhash );
4610 free( join_clause );
4617 if( flags & DISABLE_I18N )
4620 i18n = osrfHashGet( field_def, "i18n" );
4622 if( str_is_true( i18n ) ) {
4623 buffer_fadd( select_buf,
4624 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4625 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4626 class_tname, cname, col_name, class_pkey, cname,
4627 class_pkey, locale, _alias );
4629 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4630 cname, col_name, _alias );
4633 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4634 cname, col_name, _alias );
4641 "%s: Selected item is unexpected JSON type: %s",
4643 json_type( selfield->type )
4646 osrfAppSessionStatus(
4648 OSRF_STATUS_INTERNALSERVERERROR,
4649 "osrfMethodException",
4651 "Ill-formed SELECT item in JSON query"
4653 jsonIteratorFree( selclass_itr );
4654 buffer_free( select_buf );
4655 buffer_free( group_buf );
4656 if( defaultselhash )
4657 jsonObjectFree( defaultselhash );
4658 free( join_clause );
4662 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4663 if( obj_is_true( agg_obj ) )
4664 aggregate_found = 1;
4666 // Append a comma (except for the first one)
4667 // and add the column to a GROUP BY clause
4671 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4673 buffer_fadd( group_buf, " %d", sel_pos );
4677 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4679 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( selfield, "aggregate");
4680 if ( ! obj_is_true( aggregate_obj ) ) {
4684 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4687 buffer_fadd(group_buf, " %d", sel_pos);
4690 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4694 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4697 _column = searchFieldTransform(class_info->alias, field, selfield);
4698 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4699 OSRF_BUFFER_ADD(group_buf, _column);
4700 _column = searchFieldTransform(class_info->alias, field, selfield);
4707 } // end while -- iterating across SELECT columns
4709 } // end while -- iterating across classes
4711 jsonIteratorFree( selclass_itr );
4714 char* col_list = buffer_release( select_buf );
4716 // Make sure the SELECT list isn't empty. This can happen, for example,
4717 // if we try to build a default SELECT clause from a non-core table.
4720 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4722 osrfAppSessionStatus(
4724 OSRF_STATUS_INTERNALSERVERERROR,
4725 "osrfMethodException",
4727 "SELECT list is empty"
4730 buffer_free( group_buf );
4731 if( defaultselhash )
4732 jsonObjectFree( defaultselhash );
4733 free( join_clause );
4739 table = searchValueTransform( join_hash );
4741 table = strdup( curr_query->core.source_def );
4745 osrfAppSessionStatus(
4747 OSRF_STATUS_INTERNALSERVERERROR,
4748 "osrfMethodException",
4750 "Unable to identify table for core class"
4753 buffer_free( group_buf );
4754 if( defaultselhash )
4755 jsonObjectFree( defaultselhash );
4756 free( join_clause );
4760 // Put it all together
4761 growing_buffer* sql_buf = buffer_init( 128 );
4762 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4766 // Append the join clause, if any
4768 buffer_add(sql_buf, join_clause );
4769 free( join_clause );
4772 char* order_by_list = NULL;
4773 char* having_buf = NULL;
4775 if( !from_function ) {
4777 // Build a WHERE clause, if there is one
4779 buffer_add( sql_buf, " WHERE " );
4781 // and it's on the WHERE clause
4782 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4785 osrfAppSessionStatus(
4787 OSRF_STATUS_INTERNALSERVERERROR,
4788 "osrfMethodException",
4790 "Severe query error in WHERE predicate -- see error log for more details"
4793 buffer_free( group_buf );
4794 buffer_free( sql_buf );
4795 if( defaultselhash )
4796 jsonObjectFree( defaultselhash );
4800 buffer_add( sql_buf, pred );
4804 // Build a HAVING clause, if there is one
4807 // and it's on the the WHERE clause
4808 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4810 if( ! having_buf ) {
4812 osrfAppSessionStatus(
4814 OSRF_STATUS_INTERNALSERVERERROR,
4815 "osrfMethodException",
4817 "Severe query error in HAVING predicate -- see error log for more details"
4820 buffer_free( group_buf );
4821 buffer_free( sql_buf );
4822 if( defaultselhash )
4823 jsonObjectFree( defaultselhash );
4828 // Build an ORDER BY clause, if there is one
4829 if( NULL == order_hash )
4830 ; // No ORDER BY? do nothing
4831 else if( JSON_ARRAY == order_hash->type ) {
4832 order_by_list = buildOrderByFromArray( ctx, order_hash );
4833 if( !order_by_list ) {
4835 buffer_free( group_buf );
4836 buffer_free( sql_buf );
4837 if( defaultselhash )
4838 jsonObjectFree( defaultselhash );
4841 } else if( JSON_HASH == order_hash->type ) {
4842 // This hash is keyed on class alias. Each class has either
4843 // an array of field names or a hash keyed on field name.
4844 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4845 jsonIterator* class_itr = jsonNewIterator( order_hash );
4846 while( (snode = jsonIteratorNext( class_itr )) ) {
4848 ClassInfo* order_class_info = search_alias( class_itr->key );
4849 if( ! order_class_info ) {
4850 osrfLogError( OSRF_LOG_MARK,
4851 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4852 modulename, class_itr->key );
4854 osrfAppSessionStatus(
4856 OSRF_STATUS_INTERNALSERVERERROR,
4857 "osrfMethodException",
4859 "Invalid class referenced in ORDER BY clause -- "
4860 "see error log for more details"
4862 jsonIteratorFree( class_itr );
4863 buffer_free( order_buf );
4865 buffer_free( group_buf );
4866 buffer_free( sql_buf );
4867 if( defaultselhash )
4868 jsonObjectFree( defaultselhash );
4872 osrfHash* field_list_def = order_class_info->fields;
4874 if( snode->type == JSON_HASH ) {
4876 // Hash is keyed on field names from the current class. For each field
4877 // there is another layer of hash to define the sorting details, if any,
4878 // or a string to indicate direction of sorting.
4879 jsonIterator* order_itr = jsonNewIterator( snode );
4880 while( (onode = jsonIteratorNext( order_itr )) ) {
4882 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4884 osrfLogError( OSRF_LOG_MARK,
4885 "%s: Invalid field \"%s\" in ORDER BY clause",
4886 modulename, order_itr->key );
4888 osrfAppSessionStatus(
4890 OSRF_STATUS_INTERNALSERVERERROR,
4891 "osrfMethodException",
4893 "Invalid field in ORDER BY clause -- "
4894 "see error log for more details"
4896 jsonIteratorFree( order_itr );
4897 jsonIteratorFree( class_itr );
4898 buffer_free( order_buf );
4900 buffer_free( group_buf );
4901 buffer_free( sql_buf );
4902 if( defaultselhash )
4903 jsonObjectFree( defaultselhash );
4905 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4906 osrfLogError( OSRF_LOG_MARK,
4907 "%s: Virtual field \"%s\" in ORDER BY clause",
4908 modulename, order_itr->key );
4910 osrfAppSessionStatus(
4912 OSRF_STATUS_INTERNALSERVERERROR,
4913 "osrfMethodException",
4915 "Virtual field in ORDER BY clause -- "
4916 "see error log for more details"
4918 jsonIteratorFree( order_itr );
4919 jsonIteratorFree( class_itr );
4920 buffer_free( order_buf );
4922 buffer_free( group_buf );
4923 buffer_free( sql_buf );
4924 if( defaultselhash )
4925 jsonObjectFree( defaultselhash );
4929 const char* direction = NULL;
4930 if( onode->type == JSON_HASH ) {
4931 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4932 string = searchFieldTransform(
4934 osrfHashGet( field_list_def, order_itr->key ),
4938 if( ctx ) osrfAppSessionStatus(
4940 OSRF_STATUS_INTERNALSERVERERROR,
4941 "osrfMethodException",
4943 "Severe query error in ORDER BY clause -- "
4944 "see error log for more details"
4946 jsonIteratorFree( order_itr );
4947 jsonIteratorFree( class_itr );
4949 buffer_free( group_buf );
4950 buffer_free( order_buf);
4951 buffer_free( sql_buf );
4952 if( defaultselhash )
4953 jsonObjectFree( defaultselhash );
4957 growing_buffer* field_buf = buffer_init( 16 );
4958 buffer_fadd( field_buf, "\"%s\".%s",
4959 class_itr->key, order_itr->key );
4960 string = buffer_release( field_buf );
4963 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4964 const char* dir = jsonObjectGetString( tmp_const );
4965 if(!strncasecmp( dir, "d", 1 )) {
4966 direction = " DESC";
4972 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4973 osrfLogError( OSRF_LOG_MARK,
4974 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4975 modulename, json_type( onode->type ) );
4977 osrfAppSessionStatus(
4979 OSRF_STATUS_INTERNALSERVERERROR,
4980 "osrfMethodException",
4982 "Malformed ORDER BY clause -- see error log for more details"
4984 jsonIteratorFree( order_itr );
4985 jsonIteratorFree( class_itr );
4987 buffer_free( group_buf );
4988 buffer_free( order_buf );
4989 buffer_free( sql_buf );
4990 if( defaultselhash )
4991 jsonObjectFree( defaultselhash );
4995 string = strdup( order_itr->key );
4996 const char* dir = jsonObjectGetString( onode );
4997 if( !strncasecmp( dir, "d", 1 )) {
4998 direction = " DESC";
5005 OSRF_BUFFER_ADD( order_buf, ", " );
5007 order_buf = buffer_init( 128 );
5009 OSRF_BUFFER_ADD( order_buf, string );
5013 OSRF_BUFFER_ADD( order_buf, direction );
5017 jsonIteratorFree( order_itr );
5019 } else if( snode->type == JSON_ARRAY ) {
5021 // Array is a list of fields from the current class
5022 unsigned long order_idx = 0;
5023 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
5025 const char* _f = jsonObjectGetString( onode );
5027 osrfHash* field_def = osrfHashGet( field_list_def, _f );
5029 osrfLogError( OSRF_LOG_MARK,
5030 "%s: Invalid field \"%s\" in ORDER BY clause",
5033 osrfAppSessionStatus(
5035 OSRF_STATUS_INTERNALSERVERERROR,
5036 "osrfMethodException",
5038 "Invalid field in ORDER BY clause -- "
5039 "see error log for more details"
5041 jsonIteratorFree( class_itr );
5042 buffer_free( order_buf );
5044 buffer_free( group_buf );
5045 buffer_free( sql_buf );
5046 if( defaultselhash )
5047 jsonObjectFree( defaultselhash );
5049 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5050 osrfLogError( OSRF_LOG_MARK,
5051 "%s: Virtual field \"%s\" in ORDER BY clause",
5054 osrfAppSessionStatus(
5056 OSRF_STATUS_INTERNALSERVERERROR,
5057 "osrfMethodException",
5059 "Virtual field in ORDER BY clause -- "
5060 "see error log for more details"
5062 jsonIteratorFree( class_itr );
5063 buffer_free( order_buf );
5065 buffer_free( group_buf );
5066 buffer_free( sql_buf );
5067 if( defaultselhash )
5068 jsonObjectFree( defaultselhash );
5073 OSRF_BUFFER_ADD( order_buf, ", " );
5075 order_buf = buffer_init( 128 );
5077 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
5081 // IT'S THE OOOOOOOOOOOLD STYLE!
5083 osrfLogError( OSRF_LOG_MARK,
5084 "%s: Possible SQL injection attempt; direct order by is not allowed",
5087 osrfAppSessionStatus(
5089 OSRF_STATUS_INTERNALSERVERERROR,
5090 "osrfMethodException",
5092 "Severe query error -- see error log for more details"
5097 buffer_free( group_buf );
5098 buffer_free( order_buf );
5099 buffer_free( sql_buf );
5100 if( defaultselhash )
5101 jsonObjectFree( defaultselhash );
5102 jsonIteratorFree( class_itr );
5106 jsonIteratorFree( class_itr );
5108 order_by_list = buffer_release( order_buf );
5110 osrfLogError( OSRF_LOG_MARK,
5111 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
5112 modulename, json_type( order_hash->type ) );
5114 osrfAppSessionStatus(
5116 OSRF_STATUS_INTERNALSERVERERROR,
5117 "osrfMethodException",
5119 "Malformed ORDER BY clause -- see error log for more details"
5122 buffer_free( group_buf );
5123 buffer_free( sql_buf );
5124 if( defaultselhash )
5125 jsonObjectFree( defaultselhash );
5130 string = buffer_release( group_buf );
5132 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
5133 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
5134 OSRF_BUFFER_ADD( sql_buf, string );
5139 if( having_buf && *having_buf ) {
5140 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
5141 OSRF_BUFFER_ADD( sql_buf, having_buf );
5145 if( order_by_list ) {
5147 if( *order_by_list ) {
5148 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5149 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5152 free( order_by_list );
5156 const char* str = jsonObjectGetString( limit );
5157 if (str) { // limit could be JSON_NULL, etc.
5158 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
5163 const char* str = jsonObjectGetString( offset );
5165 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
5169 if( !(flags & SUBSELECT) )
5170 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5172 if( defaultselhash )
5173 jsonObjectFree( defaultselhash );
5175 return buffer_release( sql_buf );
5177 } // end of SELECT()
5180 @brief Build a list of ORDER BY expressions.
5181 @param ctx Pointer to the method context.
5182 @param order_array Pointer to a JSON_ARRAY of field specifications.
5183 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
5184 Each expression may be either a column reference or a function call whose first parameter
5185 is a column reference.
5187 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
5188 It may optionally include entries for "direction" and/or "transform".
5190 The calling code is responsible for freeing the returned string.
5192 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
5193 if( ! order_array ) {
5194 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
5197 osrfAppSessionStatus(
5199 OSRF_STATUS_INTERNALSERVERERROR,
5200 "osrfMethodException",
5202 "Logic error: ORDER BY clause expected, not found; "
5203 "see error log for more details"
5206 } else if( order_array->type != JSON_ARRAY ) {
5207 osrfLogError( OSRF_LOG_MARK,
5208 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
5210 osrfAppSessionStatus(
5212 OSRF_STATUS_INTERNALSERVERERROR,
5213 "osrfMethodException",
5215 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
5219 growing_buffer* order_buf = buffer_init( 128 );
5220 int first = 1; // boolean
5222 jsonObject* order_spec;
5223 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
5225 if( JSON_HASH != order_spec->type ) {
5226 osrfLogError( OSRF_LOG_MARK,
5227 "%s: Malformed field specification in ORDER BY clause; "
5228 "expected JSON_HASH, found %s",
5229 modulename, json_type( order_spec->type ) );
5231 osrfAppSessionStatus(
5233 OSRF_STATUS_INTERNALSERVERERROR,
5234 "osrfMethodException",
5236 "Malformed ORDER BY clause -- see error log for more details"
5238 buffer_free( order_buf );
5242 const char* class_alias =
5243 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5245 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5247 jsonObject* compare_to = jsonObjectGetKey( order_spec, "compare" );
5249 if( !field || !class_alias ) {
5250 osrfLogError( OSRF_LOG_MARK,
5251 "%s: Missing class or field name in field specification of ORDER BY clause",
5254 osrfAppSessionStatus(
5256 OSRF_STATUS_INTERNALSERVERERROR,
5257 "osrfMethodException",
5259 "Malformed ORDER BY clause -- see error log for more details"
5261 buffer_free( order_buf );
5265 const ClassInfo* order_class_info = search_alias( class_alias );
5266 if( ! order_class_info ) {
5267 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5268 "not in FROM clause, skipping it", modulename, class_alias );
5272 // Add a separating comma, except at the beginning
5276 OSRF_BUFFER_ADD( order_buf, ", " );
5278 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5280 osrfLogError( OSRF_LOG_MARK,
5281 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5282 modulename, class_alias, field );
5284 osrfAppSessionStatus(
5286 OSRF_STATUS_INTERNALSERVERERROR,
5287 "osrfMethodException",
5289 "Invalid field referenced in ORDER BY clause -- "
5290 "see error log for more details"
5294 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5295 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5296 modulename, field );
5298 osrfAppSessionStatus(
5300 OSRF_STATUS_INTERNALSERVERERROR,
5301 "osrfMethodException",
5303 "Virtual field in ORDER BY clause -- see error log for more details"
5305 buffer_free( order_buf );
5309 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5310 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5311 if( ! transform_str ) {
5313 osrfAppSessionStatus(
5315 OSRF_STATUS_INTERNALSERVERERROR,
5316 "osrfMethodException",
5318 "Severe query error in ORDER BY clause -- "
5319 "see error log for more details"
5321 buffer_free( order_buf );
5325 OSRF_BUFFER_ADD( order_buf, transform_str );
5326 free( transform_str );
5327 } else if( compare_to ) {
5328 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5329 if( ! compare_str ) {
5331 osrfAppSessionStatus(
5333 OSRF_STATUS_INTERNALSERVERERROR,
5334 "osrfMethodException",
5336 "Severe query error in ORDER BY clause -- "
5337 "see error log for more details"
5339 buffer_free( order_buf );
5343 buffer_fadd( order_buf, "(%s)", compare_str );
5344 free( compare_str );
5347 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5349 const char* direction =
5350 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5352 if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5353 OSRF_BUFFER_ADD( order_buf, " DESC" );
5355 OSRF_BUFFER_ADD( order_buf, " ASC" );
5359 return buffer_release( order_buf );
5363 @brief Build a SELECT statement.
5364 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5365 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5366 @param meta Pointer to the class metadata for the core class.
5367 @param ctx Pointer to the method context.
5368 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5370 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5371 "order_by", "limit", and "offset".
5373 The SELECT statements built here are distinct from those built for the json_query method.
5375 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5376 osrfHash* meta, osrfMethodContext* ctx ) {
5378 const char* locale = osrf_message_get_last_locale();
5380 osrfHash* fields = osrfHashGet( meta, "fields" );
5381 const char* core_class = osrfHashGet( meta, "classname" );
5383 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5385 jsonObject* selhash = NULL;
5386 jsonObject* defaultselhash = NULL;
5388 growing_buffer* sql_buf = buffer_init( 128 );
5389 growing_buffer* select_buf = buffer_init( 128 );
5391 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5392 defaultselhash = jsonNewObjectType( JSON_HASH );
5393 selhash = defaultselhash;
5396 // If there's no SELECT list for the core class, build one
5397 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5398 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5400 // Add every non-virtual field to the field list
5401 osrfHash* field_def = NULL;
5402 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5403 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5404 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5405 const char* field = osrfHashIteratorKey( field_itr );
5406 jsonObjectPush( field_list, jsonNewObject( field ) );
5409 osrfHashIteratorFree( field_itr );
5410 jsonObjectSetKey( selhash, core_class, field_list );
5413 // Build a list of columns for the SELECT clause
5415 const jsonObject* snode = NULL;
5416 jsonIterator* class_itr = jsonNewIterator( selhash );
5417 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5419 // If the class isn't in the IDL, ignore it
5420 const char* cname = class_itr->key;
5421 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5425 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5426 if( strcmp( core_class, class_itr->key )) {
5430 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5431 if( !found->size ) {
5432 jsonObjectFree( found );
5436 jsonObjectFree( found );
5439 const jsonObject* node = NULL;
5440 jsonIterator* select_itr = jsonNewIterator( snode );
5441 while( (node = jsonIteratorNext( select_itr )) ) {
5442 const char* item_str = jsonObjectGetString( node );
5443 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5444 char* fname = osrfHashGet( field, "name" );
5449 if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5455 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5460 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5461 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5464 i18n = osrfHashGet( field, "i18n" );
5466 if( str_is_true( i18n ) ) {
5467 char* pkey = osrfHashGet( idlClass, "primarykey" );
5468 char* tname = osrfHashGet( idlClass, "tablename" );
5470 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5471 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5472 tname, cname, fname, pkey, cname, pkey, locale, fname );
5474 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5477 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5481 jsonIteratorFree( select_itr );
5484 jsonIteratorFree( class_itr );
5486 char* col_list = buffer_release( select_buf );
5487 char* table = oilsGetRelation( meta );
5489 table = strdup( "(null)" );
5491 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5495 // Clear the query stack (as a fail-safe precaution against possible
5496 // leftover garbage); then push the first query frame onto the stack.
5497 clear_query_stack();
5499 if( add_query_core( NULL, core_class ) ) {
5501 osrfAppSessionStatus(
5503 OSRF_STATUS_INTERNALSERVERERROR,
5504 "osrfMethodException",
5506 "Unable to build query frame for core class"
5508 buffer_free( sql_buf );
5509 if( defaultselhash )
5510 jsonObjectFree( defaultselhash );
5514 // Add the JOIN clauses, if any
5516 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5517 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5518 OSRF_BUFFER_ADD( sql_buf, join_clause );
5519 free( join_clause );
5522 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5523 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5525 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5527 // Add the conditions in the WHERE clause
5528 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5530 osrfAppSessionStatus(
5532 OSRF_STATUS_INTERNALSERVERERROR,
5533 "osrfMethodException",
5535 "Severe query error -- see error log for more details"
5537 buffer_free( sql_buf );
5538 if( defaultselhash )
5539 jsonObjectFree( defaultselhash );
5540 clear_query_stack();
5543 buffer_add( sql_buf, pred );
5547 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5548 if( rest_of_query ) {
5549 const jsonObject* order_by = NULL;
5550 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5552 char* order_by_list = NULL;
5554 if( JSON_ARRAY == order_by->type ) {
5555 order_by_list = buildOrderByFromArray( ctx, order_by );
5556 if( !order_by_list ) {
5557 buffer_free( sql_buf );
5558 if( defaultselhash )
5559 jsonObjectFree( defaultselhash );
5560 clear_query_stack();
5563 } else if( JSON_HASH == order_by->type ) {
5564 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5565 // and build a list of ORDER BY expressions.
5566 growing_buffer* order_buf = buffer_init( 128 );
5568 jsonIterator* class_itr = jsonNewIterator( order_by );
5569 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5571 ClassInfo* order_class_info = search_alias( class_itr->key );
5572 if( ! order_class_info )
5573 continue; // class not referenced by FROM clause? Ignore it.
5575 if( JSON_HASH == snode->type ) {
5577 // If the data for the current class is a JSON_HASH, then it is
5578 // keyed on field name.
5580 const jsonObject* onode = NULL;
5581 jsonIterator* order_itr = jsonNewIterator( snode );
5582 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5584 osrfHash* field_def = osrfHashGet(
5585 order_class_info->fields, order_itr->key );
5587 continue; // Field not defined in IDL? Ignore it.
5588 if( str_is_true( osrfHashGet( field_def, "virtual")))
5589 continue; // Field is virtual? Ignore it.
5591 char* field_str = NULL;
5592 char* direction = NULL;
5593 if( onode->type == JSON_HASH ) {
5594 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5595 field_str = searchFieldTransform(
5596 class_itr->key, field_def, onode );
5598 osrfAppSessionStatus(
5600 OSRF_STATUS_INTERNALSERVERERROR,
5601 "osrfMethodException",
5603 "Severe query error in ORDER BY clause -- "
5604 "see error log for more details"
5606 jsonIteratorFree( order_itr );
5607 jsonIteratorFree( class_itr );
5608 buffer_free( order_buf );
5609 buffer_free( sql_buf );
5610 if( defaultselhash )
5611 jsonObjectFree( defaultselhash );
5612 clear_query_stack();
5616 growing_buffer* field_buf = buffer_init( 16 );
5617 buffer_fadd( field_buf, "\"%s\".%s",
5618 class_itr->key, order_itr->key );
5619 field_str = buffer_release( field_buf );
5622 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5623 const char* dir = jsonObjectGetString( order_by );
5624 if(!strncasecmp( dir, "d", 1 )) {
5625 direction = " DESC";
5629 field_str = strdup( order_itr->key );
5630 const char* dir = jsonObjectGetString( onode );
5631 if( !strncasecmp( dir, "d", 1 )) {
5632 direction = " DESC";
5641 buffer_add( order_buf, ", " );
5644 buffer_add( order_buf, field_str );
5648 buffer_add( order_buf, direction );
5650 } // end while; looping over ORDER BY expressions
5652 jsonIteratorFree( order_itr );
5654 } else if( JSON_STRING == snode->type ) {
5655 // We expect a comma-separated list of sort fields.
5656 const char* str = jsonObjectGetString( snode );
5657 if( strchr( str, ';' )) {
5658 // No semicolons allowed. It is theoretically possible for a
5659 // legitimate semicolon to occur within quotes, but it's not likely
5660 // to occur in practice in the context of an ORDER BY list.
5661 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5662 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5664 osrfAppSessionStatus(
5666 OSRF_STATUS_INTERNALSERVERERROR,
5667 "osrfMethodException",
5669 "Possible attempt at SOL injection -- "
5670 "semicolon found in ORDER BY list"
5673 jsonIteratorFree( class_itr );
5674 buffer_free( order_buf );
5675 buffer_free( sql_buf );
5676 if( defaultselhash )
5677 jsonObjectFree( defaultselhash );
5678 clear_query_stack();
5681 buffer_add( order_buf, str );
5685 } // end while; looping over order_by classes
5687 jsonIteratorFree( class_itr );
5688 order_by_list = buffer_release( order_buf );
5691 osrfLogWarning( OSRF_LOG_MARK,
5692 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5693 "no ORDER BY generated" );
5696 if( order_by_list && *order_by_list ) {
5697 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5698 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5701 free( order_by_list );
5704 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5706 const char* str = jsonObjectGetString( limit );
5716 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5718 const char* str = jsonObjectGetString( offset );
5729 if( defaultselhash )
5730 jsonObjectFree( defaultselhash );
5731 clear_query_stack();
5733 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5734 return buffer_release( sql_buf );
5737 int doJSONSearch ( osrfMethodContext* ctx ) {
5738 if(osrfMethodVerifyContext( ctx )) {
5739 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5743 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5747 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5751 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5752 flags |= SELECT_DISTINCT;
5754 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5755 flags |= DISABLE_I18N;
5757 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5758 clear_query_stack(); // a possibly needless precaution
5759 char* sql = buildQuery( ctx, hash, flags );
5760 clear_query_stack();
5767 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5770 dbhandle = writehandle;
5772 dbi_result result = dbi_conn_query( dbhandle, sql );
5775 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5777 if( dbi_result_first_row( result )) {
5778 /* JSONify the result */
5779 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5782 jsonObject* return_val = oilsMakeJSONFromResult( result );
5783 osrfAppRespond( ctx, return_val );
5784 jsonObjectFree( return_val );
5785 } while( dbi_result_next_row( result ));
5788 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5791 osrfAppRespondComplete( ctx, NULL );
5793 /* clean up the query */
5794 dbi_result_free( result );
5799 int errnum = dbi_conn_error( dbhandle, &msg );
5800 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5801 modulename, sql, errnum, msg ? msg : "(No description available)" );
5802 osrfAppSessionStatus(
5804 OSRF_STATUS_INTERNALSERVERERROR,
5805 "osrfMethodException",
5807 "Severe query error -- see error log for more details"
5809 if( !oilsIsDBConnected( dbhandle ))
5810 osrfAppSessionPanic( ctx->session );
5817 // The last parameter, err, is used to report an error condition by updating an int owned by
5818 // the calling code.
5820 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5821 // It is the responsibility of the calling code to initialize *err before the
5822 // call, so that it will be able to make sense of the result.
5824 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5825 // redundant anyway.
5826 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5827 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5830 dbhandle = writehandle;
5832 char* core_class = osrfHashGet( class_meta, "classname" );
5833 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5835 char* pkey = osrfHashGet( class_meta, "primarykey" );
5837 if (!ctx->session->userData)
5838 (void) initSessionCache( ctx );
5840 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5841 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5842 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5844 int i_respond_directly = 0;
5845 int flesh_depth = 0;
5847 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5849 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5854 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5856 dbi_result result = dbi_conn_query( dbhandle, sql );
5857 if( NULL == result ) {
5859 int errnum = dbi_conn_error( dbhandle, &msg );
5860 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5861 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5862 msg ? msg : "(No description available)" );
5863 if( !oilsIsDBConnected( dbhandle ))
5864 osrfAppSessionPanic( ctx->session );
5865 osrfAppSessionStatus(
5867 OSRF_STATUS_INTERNALSERVERERROR,
5868 "osrfMethodException",
5870 "Severe query error -- see error log for more details"
5877 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5880 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5881 jsonObject* row_obj = NULL;
5883 // The following two steps are for verifyObjectPCRUD()'s benefit.
5884 // 1. get the flesh depth
5885 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5887 flesh_depth = (int) jsonObjectGetNumber( _tmp );
5888 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5889 flesh_depth = max_flesh_depth;
5892 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
5893 // over the whole life of this request. This means if we've already set
5894 // up a rs_size_req_%d, do nothing.
5895 // a. Incidentally, we can also use this opportunity to set i_respond_directly
5896 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
5897 if( !rs_size ) { // pointer null, so value not set in hash
5898 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
5899 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
5901 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
5902 unsigned long long result_count = dbi_result_get_numrows( result );
5903 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
5904 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
5907 if( dbi_result_first_row( result )) {
5909 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5910 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5911 // eliminate the duplicates.
5912 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5913 osrfHash* dedup = osrfNewHash();
5915 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5916 char* pkey_val = oilsFMGetString( row_obj, pkey );
5917 if( osrfHashGet( dedup, pkey_val ) ) {
5918 jsonObjectFree( row_obj );
5921 if( !enforce_pcrud || !need_to_verify ||
5922 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
5923 osrfHashSet( dedup, pkey_val, pkey_val );
5924 jsonObjectPush( res_list, row_obj );
5927 } while( dbi_result_next_row( result ));
5928 osrfHashFree( dedup );
5931 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5935 /* clean up the query */
5936 dbi_result_free( result );
5939 // If we're asked to flesh, and there's anything to flesh, then flesh it
5940 // (formerly we would skip fleshing if in pcrud mode, but now we support
5941 // fleshing even in PCRUD).
5942 if( res_list->size ) {
5943 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
5944 jsonObject* flesh_fields;
5945 jsonObject* flesh_blob = NULL;
5946 osrfStringArray* link_fields = NULL;
5947 osrfHash* links = NULL;
5951 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
5952 if( temp_blob && flesh_depth > 0 ) {
5954 flesh_blob = jsonObjectClone( temp_blob );
5955 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
5957 links = osrfHashGet( class_meta, "links" );
5959 // Make an osrfStringArray of the names of fields to be fleshed
5960 if( flesh_fields ) {
5961 if( flesh_fields->size == 1 ) {
5962 const char* _t = jsonObjectGetString(
5963 jsonObjectGetIndex( flesh_fields, 0 ) );
5964 if( !strcmp( _t, "*" ))
5965 link_fields = osrfHashKeys( links );
5968 if( !link_fields ) {
5970 link_fields = osrfNewStringArray( 1 );
5971 jsonIterator* _i = jsonNewIterator( flesh_fields );
5972 while ((_f = jsonIteratorNext( _i ))) {
5973 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5975 jsonIteratorFree( _i );
5978 want_flesh = link_fields ? 1 : 0;
5982 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5984 // Iterate over the JSON_ARRAY of rows
5986 unsigned long res_idx = 0;
5987 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5990 const char* link_field;
5992 // Iterate over the list of fleshable fields
5994 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5996 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5998 osrfHash* kid_link = osrfHashGet( links, link_field );
6000 continue; // Not a link field; skip it
6002 osrfHash* field = osrfHashGet( fields, link_field );
6004 continue; // Not a field at all; skip it (IDL is ill-formed)
6006 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
6007 osrfHashGet( kid_link, "class" ));
6009 continue; // The class it links to doesn't exist; skip it
6011 const char* reltype = osrfHashGet( kid_link, "reltype" );
6013 continue; // No reltype; skip it (IDL is ill-formed)
6015 osrfHash* value_field = field;
6017 if( !strcmp( reltype, "has_many" )
6018 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
6019 value_field = osrfHashGet(
6020 fields, osrfHashGet( class_meta, "primarykey" ) );
6023 int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
6024 // fleshing pcrud case: we require the controller in need_to_verify mode
6025 if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
6026 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
6030 (unsigned long) atoi( osrfHashGet(field, "array_position") ),
6032 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
6038 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
6040 if( link_map->size > 0 ) {
6041 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
6044 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
6049 osrfHashGet( kid_link, "class" ),
6056 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
6057 osrfHashGet( kid_link, "field" ),
6058 osrfHashGet( kid_link, "class" ),
6059 osrfHashGet( kid_link, "key" ),
6060 osrfHashGet( kid_link, "reltype" )
6063 const char* search_key = jsonObjectGetString(
6064 jsonObjectGetIndex( cur,
6065 atoi( osrfHashGet( value_field, "array_position" ) )
6070 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
6074 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
6076 // construct WHERE clause
6077 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
6080 osrfHashGet( kid_link, "key" ),
6081 jsonNewObject( search_key )
6084 // construct the rest of the query, mostly
6085 // by copying pieces of the previous level of query
6086 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
6087 jsonObjectSetKey( rest_of_query, "flesh",
6088 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
6092 jsonObjectSetKey( rest_of_query, "flesh_fields",
6093 jsonObjectClone( flesh_blob ));
6095 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
6096 jsonObjectSetKey( rest_of_query, "order_by",
6097 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
6101 if( jsonObjectGetKeyConst( query_hash, "select" )) {
6102 jsonObjectSetKey( rest_of_query, "select",
6103 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
6107 // do the query, recursively, to expand the fleshable field
6108 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
6109 where_clause, rest_of_query, err );
6111 jsonObjectFree( where_clause );
6112 jsonObjectFree( rest_of_query );
6115 osrfStringArrayFree( link_fields );
6116 jsonObjectFree( res_list );
6117 jsonObjectFree( flesh_blob );
6121 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
6122 osrfHashGet( kid_link, "class" ), kids->size );
6124 // Traverse the result set
6125 jsonObject* X = NULL;
6126 if( link_map->size > 0 && kids->size > 0 ) {
6128 kids = jsonNewObjectType( JSON_ARRAY );
6130 jsonObject* _k_node;
6131 unsigned long res_idx = 0;
6132 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
6138 (unsigned long) atoi(
6144 osrfHashGet( kid_link, "class" )
6148 osrfStringArrayGetString( link_map, 0 )
6156 } // end while loop traversing X
6159 if (kids->size > 0) {
6161 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
6162 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
6164 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
6165 osrfHashGet( kid_link, "field" ));
6168 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6169 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
6174 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
6176 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
6177 osrfHashGet( kid_link, "field" ) );
6180 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6181 jsonObjectClone( kids )
6186 jsonObjectFree( kids );
6190 jsonObjectFree( kids );
6192 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
6193 osrfHashGet( kid_link, "field" ) );
6194 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
6196 } // end while loop traversing list of fleshable fields
6199 if( i_respond_directly ) {
6200 if ( *methodtype == 'i' ) {
6201 osrfAppRespond( ctx,
6202 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
6204 osrfAppRespond( ctx, cur );
6207 } // end while loop traversing res_list
6208 jsonObjectFree( flesh_blob );
6209 osrfStringArrayFree( link_fields );
6212 if( i_respond_directly ) {
6213 jsonObjectFree( res_list );
6214 return jsonNewObjectType( JSON_ARRAY );
6221 int doUpdate( osrfMethodContext* ctx ) {
6222 if( osrfMethodVerifyContext( ctx )) {
6223 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6228 timeout_needs_resetting = 1;
6230 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6232 jsonObject* target = NULL;
6234 target = jsonObjectGetIndex( ctx->params, 1 );
6236 target = jsonObjectGetIndex( ctx->params, 0 );
6238 if(!verifyObjectClass( ctx, target )) {
6239 osrfAppRespondComplete( ctx, NULL );
6243 if( getXactId( ctx ) == NULL ) {
6244 osrfAppSessionStatus(
6246 OSRF_STATUS_BADREQUEST,
6247 "osrfMethodException",
6249 "No active transaction -- required for UPDATE"
6251 osrfAppRespondComplete( ctx, NULL );
6255 // The following test is harmless but redundant. If a class is
6256 // readonly, we don't register an update method for it.
6257 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6258 osrfAppSessionStatus(
6260 OSRF_STATUS_BADREQUEST,
6261 "osrfMethodException",
6263 "Cannot UPDATE readonly class"
6265 osrfAppRespondComplete( ctx, NULL );
6269 const char* trans_id = getXactId( ctx );
6271 // Set the last_xact_id
6272 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6274 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6275 trans_id, target->classname, index );
6276 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6279 char* pkey = osrfHashGet( meta, "primarykey" );
6280 osrfHash* fields = osrfHashGet( meta, "fields" );
6282 char* id = oilsFMGetString( target, pkey );
6286 "%s updating %s object with %s = %s",
6288 osrfHashGet( meta, "fieldmapper" ),
6293 dbhandle = writehandle;
6294 growing_buffer* sql = buffer_init( 128 );
6295 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6298 osrfHash* field_def = NULL;
6299 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6300 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6302 // Skip virtual fields, and the primary key
6303 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6306 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6310 const char* field_name = osrfHashIteratorKey( field_itr );
6311 if( ! strcmp( field_name, pkey ) )
6314 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6316 int value_is_numeric = 0; // boolean
6318 if( field_object && field_object->classname ) {
6319 value = oilsFMGetString(
6321 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6323 } else if( field_object && JSON_BOOL == field_object->type ) {
6324 if( jsonBoolIsTrue( field_object ) )
6325 value = strdup( "t" );
6327 value = strdup( "f" );
6329 value = jsonObjectToSimpleString( field_object );
6330 if( field_object && JSON_NUMBER == field_object->type )
6331 value_is_numeric = 1;
6334 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6335 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6337 if( !field_object || field_object->type == JSON_NULL ) {
6338 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6339 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6343 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6344 buffer_fadd( sql, " %s = NULL", field_name );
6347 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6351 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6353 const char* numtype = get_datatype( field_def );
6354 if( !strncmp( numtype, "INT", 3 ) ) {
6355 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6356 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6357 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6359 // Must really be intended as a string, so quote it
6360 if( dbi_conn_quote_string( dbhandle, &value )) {
6361 buffer_fadd( sql, " %s = %s", field_name, value );
6363 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6364 modulename, value );
6365 osrfAppSessionStatus(
6367 OSRF_STATUS_INTERNALSERVERERROR,
6368 "osrfMethodException",
6370 "Error quoting string -- please see the error log for more details"
6374 osrfHashIteratorFree( field_itr );
6376 osrfAppRespondComplete( ctx, NULL );
6381 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6384 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6388 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6389 buffer_fadd( sql, " %s = %s", field_name, value );
6391 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6392 osrfAppSessionStatus(
6394 OSRF_STATUS_INTERNALSERVERERROR,
6395 "osrfMethodException",
6397 "Error quoting string -- please see the error log for more details"
6401 osrfHashIteratorFree( field_itr );
6403 osrfAppRespondComplete( ctx, NULL );
6412 osrfHashIteratorFree( field_itr );
6414 jsonObject* obj = jsonNewObject( id );
6416 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6417 dbi_conn_quote_string( dbhandle, &id );
6419 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6421 char* query = buffer_release( sql );
6422 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6424 dbi_result result = dbi_conn_query( dbhandle, query );
6429 jsonObjectFree( obj );
6430 obj = jsonNewObject( NULL );
6432 int errnum = dbi_conn_error( dbhandle, &msg );
6435 "%s ERROR updating %s object with %s = %s: %d %s",
6437 osrfHashGet( meta, "fieldmapper" ),
6441 msg ? msg : "(No description available)"
6443 osrfAppSessionStatus(
6445 OSRF_STATUS_INTERNALSERVERERROR,
6446 "osrfMethodException",
6448 "Error in updating a row -- please see the error log for more details"
6450 if( !oilsIsDBConnected( dbhandle ))
6451 osrfAppSessionPanic( ctx->session );
6454 dbi_result_free( result );
6457 osrfAppRespondComplete( ctx, obj );
6458 jsonObjectFree( obj );
6462 int doDelete( osrfMethodContext* ctx ) {
6463 if( osrfMethodVerifyContext( ctx )) {
6464 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6469 timeout_needs_resetting = 1;
6471 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6473 if( getXactId( ctx ) == NULL ) {
6474 osrfAppSessionStatus(
6476 OSRF_STATUS_BADREQUEST,
6477 "osrfMethodException",
6479 "No active transaction -- required for DELETE"
6481 osrfAppRespondComplete( ctx, NULL );
6485 // The following test is harmless but redundant. If a class is
6486 // readonly, we don't register a delete method for it.
6487 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6488 osrfAppSessionStatus(
6490 OSRF_STATUS_BADREQUEST,
6491 "osrfMethodException",
6493 "Cannot DELETE readonly class"
6495 osrfAppRespondComplete( ctx, NULL );
6499 dbhandle = writehandle;
6501 char* pkey = osrfHashGet( meta, "primarykey" );
6508 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6509 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6510 osrfAppRespondComplete( ctx, NULL );
6514 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6516 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6517 osrfAppRespondComplete( ctx, NULL );
6520 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6525 "%s deleting %s object with %s = %s",
6527 osrfHashGet( meta, "fieldmapper" ),
6532 jsonObject* obj = jsonNewObject( id );
6534 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6535 dbi_conn_quote_string( writehandle, &id );
6537 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6538 osrfHashGet( meta, "tablename" ), pkey, id );
6543 jsonObjectFree( obj );
6544 obj = jsonNewObject( NULL );
6546 int errnum = dbi_conn_error( writehandle, &msg );
6549 "%s ERROR deleting %s object with %s = %s: %d %s",
6551 osrfHashGet( meta, "fieldmapper" ),
6555 msg ? msg : "(No description available)"
6557 osrfAppSessionStatus(
6559 OSRF_STATUS_INTERNALSERVERERROR,
6560 "osrfMethodException",
6562 "Error in deleting a row -- please see the error log for more details"
6564 if( !oilsIsDBConnected( writehandle ))
6565 osrfAppSessionPanic( ctx->session );
6567 dbi_result_free( result );
6571 osrfAppRespondComplete( ctx, obj );
6572 jsonObjectFree( obj );
6577 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6578 @param result An iterator for a result set; we only look at the current row.
6579 @param @meta Pointer to the class metadata for the core class.
6580 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6582 If a column is not defined in the IDL, or if it has no array_position defined for it in
6583 the IDL, or if it is defined as virtual, ignore it.
6585 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6586 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6587 array_position in the IDL.
6589 A field defined in the IDL but not represented in the returned row will leave a hole
6590 in the JSON_ARRAY. In effect it will be treated as a null value.
6592 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6593 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6594 classname corresponding to the @a meta argument.
6596 The calling code is responsible for freeing the the resulting jsonObject by calling
6599 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6600 if( !( result && meta )) return NULL;
6602 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6603 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6604 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6606 osrfHash* fields = osrfHashGet( meta, "fields" );
6608 int columnIndex = 1;
6609 const char* columnName;
6611 /* cycle through the columns in the row returned from the database */
6612 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6614 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6616 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6618 /* determine the field type and storage attributes */
6619 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6620 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6622 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6623 // or if it has no sequence number there, or if it's virtual, skip it.
6624 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6627 if( str_is_true( osrfHashGet( _f, "virtual" )))
6628 continue; // skip this column: IDL says it's virtual
6630 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6631 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6632 continue; // since we assign sequence numbers dynamically as we load the IDL.
6634 fmIndex = atoi( pos );
6635 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6637 continue; // This field is not defined in the IDL
6640 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6641 // sequence number from the IDL (which is likely to be different from the sequence
6642 // of columns in the SELECT clause).
6643 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6644 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6649 case DBI_TYPE_INTEGER :
6651 if( attr & DBI_INTEGER_SIZE8 )
6652 jsonObjectSetIndex( object, fmIndex,
6653 jsonNewNumberObject(
6654 dbi_result_get_longlong_idx( result, columnIndex )));
6656 jsonObjectSetIndex( object, fmIndex,
6657 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6661 case DBI_TYPE_DECIMAL :
6662 jsonObjectSetIndex( object, fmIndex,
6663 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6666 case DBI_TYPE_STRING :
6671 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6676 case DBI_TYPE_DATETIME : {
6678 char dt_string[ 256 ] = "";
6681 // Fetch the date column as a time_t
6682 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6684 // Translate the time_t to a human-readable string
6685 if( !( attr & DBI_DATETIME_DATE )) {
6686 gmtime_r( &_tmp_dt, &gmdt );
6687 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6688 } else if( !( attr & DBI_DATETIME_TIME )) {
6689 gmtime_r( &_tmp_dt, &gmdt );
6690 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6692 localtime_r( &_tmp_dt, &gmdt );
6693 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6696 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6700 case DBI_TYPE_BINARY :
6701 osrfLogError( OSRF_LOG_MARK,
6702 "Can't do binary at column %s : index %d", columnName, columnIndex );
6711 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6712 if( !result ) return NULL;
6714 jsonObject* object = jsonNewObject( NULL );
6717 char dt_string[ 256 ];
6721 int columnIndex = 1;
6723 unsigned short type;
6724 const char* columnName;
6726 /* cycle through the column list */
6727 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6729 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6731 fmIndex = -1; // reset the position
6733 /* determine the field type and storage attributes */
6734 type = dbi_result_get_field_type_idx( result, columnIndex );
6735 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6737 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6738 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6743 case DBI_TYPE_INTEGER :
6745 if( attr & DBI_INTEGER_SIZE8 )
6746 jsonObjectSetKey( object, columnName,
6747 jsonNewNumberObject( dbi_result_get_longlong_idx(
6748 result, columnIndex )) );
6750 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6751 dbi_result_get_int_idx( result, columnIndex )) );
6754 case DBI_TYPE_DECIMAL :
6755 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6756 dbi_result_get_double_idx( result, columnIndex )) );
6759 case DBI_TYPE_STRING :
6760 jsonObjectSetKey( object, columnName,
6761 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6764 case DBI_TYPE_DATETIME :
6766 memset( dt_string, '\0', sizeof( dt_string ));
6767 memset( &gmdt, '\0', sizeof( gmdt ));
6769 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6771 if( !( attr & DBI_DATETIME_DATE )) {
6772 gmtime_r( &_tmp_dt, &gmdt );
6773 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6774 } else if( !( attr & DBI_DATETIME_TIME )) {
6775 gmtime_r( &_tmp_dt, &gmdt );
6776 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6778 localtime_r( &_tmp_dt, &gmdt );
6779 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6782 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6785 case DBI_TYPE_BINARY :
6786 osrfLogError( OSRF_LOG_MARK,
6787 "Can't do binary at column %s : index %d", columnName, columnIndex );
6791 } // end while loop traversing result
6796 // Interpret a string as true or false
6797 int str_is_true( const char* str ) {
6798 if( NULL == str || strcasecmp( str, "true" ) )
6804 // Interpret a jsonObject as true or false
6805 static int obj_is_true( const jsonObject* obj ) {
6808 else switch( obj->type )
6816 if( strcasecmp( obj->value.s, "true" ) )
6820 case JSON_NUMBER : // Support 1/0 for perl's sake
6821 if( jsonObjectGetNumber( obj ) == 1.0 )
6830 // Translate a numeric code into a text string identifying a type of
6831 // jsonObject. To be used for building error messages.
6832 static const char* json_type( int code ) {
6838 return "JSON_ARRAY";
6840 return "JSON_STRING";
6842 return "JSON_NUMBER";
6848 return "(unrecognized)";
6852 // Extract the "primitive" attribute from an IDL field definition.
6853 // If we haven't initialized the app, then we must be running in
6854 // some kind of testbed. In that case, default to "string".
6855 static const char* get_primitive( osrfHash* field ) {
6856 const char* s = osrfHashGet( field, "primitive" );
6858 if( child_initialized )
6861 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6863 osrfHashGet( field, "name" )
6871 // Extract the "datatype" attribute from an IDL field definition.
6872 // If we haven't initialized the app, then we must be running in
6873 // some kind of testbed. In that case, default to to NUMERIC,
6874 // since we look at the datatype only for numbers.
6875 static const char* get_datatype( osrfHash* field ) {
6876 const char* s = osrfHashGet( field, "datatype" );
6878 if( child_initialized )
6881 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6883 osrfHashGet( field, "name" )
6892 @brief Determine whether a string is potentially a valid SQL identifier.
6893 @param s The identifier to be tested.
6894 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6896 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6897 need to follow all the rules exactly, such as requiring that the first character not
6900 We allow leading and trailing white space. In between, we do not allow punctuation
6901 (except for underscores and dollar signs), control characters, or embedded white space.
6903 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6904 for the foreseeable future such quoted identifiers are not likely to be an issue.
6906 int is_identifier( const char* s) {
6910 // Skip leading white space
6911 while( isspace( (unsigned char) *s ) )
6915 return 0; // Nothing but white space? Not okay.
6917 // Check each character until we reach white space or
6918 // end-of-string. Letters, digits, underscores, and
6919 // dollar signs are okay. With the exception of periods
6920 // (as in schema.identifier), control characters and other
6921 // punctuation characters are not okay. Anything else
6922 // is okay -- it could for example be part of a multibyte
6923 // UTF8 character such as a letter with diacritical marks,
6924 // and those are allowed.
6926 if( isalnum( (unsigned char) *s )
6930 ; // Fine; keep going
6931 else if( ispunct( (unsigned char) *s )
6932 || iscntrl( (unsigned char) *s ) )
6935 } while( *s && ! isspace( (unsigned char) *s ) );
6937 // If we found any white space in the above loop,
6938 // the rest had better be all white space.
6940 while( isspace( (unsigned char) *s ) )
6944 return 0; // White space was embedded within non-white space
6950 @brief Determine whether to accept a character string as a comparison operator.
6951 @param op The candidate comparison operator.
6952 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6954 We don't validate the operator for real. We just make sure that it doesn't contain
6955 any semicolons or white space (with special exceptions for a few specific operators).
6956 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6957 space but it's still not a valid operator, then the database will complain.
6959 Another approach would be to compare the string against a short list of approved operators.
6960 We don't do that because we want to allow custom operators like ">100*", which at this
6961 writing would be difficult or impossible to express otherwise in a JSON query.
6963 int is_good_operator( const char* op ) {
6964 if( !op ) return 0; // Sanity check
6968 if( isspace( (unsigned char) *s ) ) {
6969 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6970 // and IS NOT DISTINCT FROM.
6971 if( !strcasecmp( op, "similar to" ) )
6973 else if( !strcasecmp( op, "is distinct from" ) )
6975 else if( !strcasecmp( op, "is not distinct from" ) )
6980 else if( ';' == *s )
6988 @name Query Frame Management
6990 The following machinery supports a stack of query frames for use by SELECT().
6992 A query frame caches information about one level of a SELECT query. When we enter
6993 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6995 The query frame stores information about the core class, and about any joined classes
6998 The main purpose is to map table aliases to classes and tables, so that a query can
6999 join to the same table more than once. A secondary goal is to reduce the number of
7000 lookups in the IDL by caching the results.
7004 #define STATIC_CLASS_INFO_COUNT 3
7006 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
7009 @brief Allocate a ClassInfo as raw memory.
7010 @return Pointer to the newly allocated ClassInfo.
7012 Except for the in_use flag, which is used only by the allocation and deallocation
7013 logic, we don't initialize the ClassInfo here.
7015 static ClassInfo* allocate_class_info( void ) {
7016 // In order to reduce the number of mallocs and frees, we return a static
7017 // instance of ClassInfo, if we can find one that we're not already using.
7018 // We rely on the fact that the compiler will implicitly initialize the
7019 // static instances so that in_use == 0.
7022 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7023 if( ! static_class_info[ i ].in_use ) {
7024 static_class_info[ i ].in_use = 1;
7025 return static_class_info + i;
7029 // The static ones are all in use. Malloc one.
7031 return safe_malloc( sizeof( ClassInfo ) );
7035 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
7036 @param info Pointer to the ClassInfo to be cleared.
7038 static void clear_class_info( ClassInfo* info ) {
7043 // Free any malloc'd strings
7045 if( info->alias != info->alias_store )
7046 free( info->alias );
7048 if( info->class_name != info->class_name_store )
7049 free( info->class_name );
7051 free( info->source_def );
7053 info->alias = info->class_name = info->source_def = NULL;
7058 @brief Free a ClassInfo and everything it owns.
7059 @param info Pointer to the ClassInfo to be freed.
7061 static void free_class_info( ClassInfo* info ) {
7066 clear_class_info( info );
7068 // If it's one of the static instances, just mark it as not in use
7071 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7072 if( info == static_class_info + i ) {
7073 static_class_info[ i ].in_use = 0;
7078 // Otherwise it must have been malloc'd, so free it
7084 @brief Populate an already-allocated ClassInfo.
7085 @param info Pointer to the ClassInfo to be populated.
7086 @param alias Alias for the class. If it is NULL, or an empty string, use the class
7088 @param class Name of the class.
7089 @return Zero if successful, or 1 if not.
7091 Populate the ClassInfo with copies of the alias and class name, and with pointers to
7092 the relevant portions of the IDL for the specified class.
7094 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
7097 osrfLogError( OSRF_LOG_MARK,
7098 "%s ERROR: No ClassInfo available to populate", modulename );
7099 info->alias = info->class_name = info->source_def = NULL;
7100 info->class_def = info->fields = info->links = NULL;
7105 osrfLogError( OSRF_LOG_MARK,
7106 "%s ERROR: No class name provided for lookup", modulename );
7107 info->alias = info->class_name = info->source_def = NULL;
7108 info->class_def = info->fields = info->links = NULL;
7112 // Alias defaults to class name if not supplied
7113 if( ! alias || ! alias[ 0 ] )
7116 // Look up class info in the IDL
7117 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
7119 osrfLogError( OSRF_LOG_MARK,
7120 "%s ERROR: Class %s not defined in IDL", modulename, class );
7121 info->alias = info->class_name = info->source_def = NULL;
7122 info->class_def = info->fields = info->links = NULL;
7124 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
7125 osrfLogError( OSRF_LOG_MARK,
7126 "%s ERROR: Class %s is defined as virtual", modulename, class );
7127 info->alias = info->class_name = info->source_def = NULL;
7128 info->class_def = info->fields = info->links = NULL;
7132 osrfHash* links = osrfHashGet( class_def, "links" );
7134 osrfLogError( OSRF_LOG_MARK,
7135 "%s ERROR: No links defined in IDL for class %s", modulename, class );
7136 info->alias = info->class_name = info->source_def = NULL;
7137 info->class_def = info->fields = info->links = NULL;
7141 osrfHash* fields = osrfHashGet( class_def, "fields" );
7143 osrfLogError( OSRF_LOG_MARK,
7144 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
7145 info->alias = info->class_name = info->source_def = NULL;
7146 info->class_def = info->fields = info->links = NULL;
7150 char* source_def = oilsGetRelation( class_def );
7154 // We got everything we need, so populate the ClassInfo
7155 if( strlen( alias ) > ALIAS_STORE_SIZE )
7156 info->alias = strdup( alias );
7158 strcpy( info->alias_store, alias );
7159 info->alias = info->alias_store;
7162 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
7163 info->class_name = strdup( class );
7165 strcpy( info->class_name_store, class );
7166 info->class_name = info->class_name_store;
7169 info->source_def = source_def;
7171 info->class_def = class_def;
7172 info->links = links;
7173 info->fields = fields;
7178 #define STATIC_FRAME_COUNT 3
7180 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
7183 @brief Allocate a QueryFrame as raw memory.
7184 @return Pointer to the newly allocated QueryFrame.
7186 Except for the in_use flag, which is used only by the allocation and deallocation
7187 logic, we don't initialize the QueryFrame here.
7189 static QueryFrame* allocate_frame( void ) {
7190 // In order to reduce the number of mallocs and frees, we return a static
7191 // instance of QueryFrame, if we can find one that we're not already using.
7192 // We rely on the fact that the compiler will implicitly initialize the
7193 // static instances so that in_use == 0.
7196 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7197 if( ! static_frame[ i ].in_use ) {
7198 static_frame[ i ].in_use = 1;
7199 return static_frame + i;
7203 // The static ones are all in use. Malloc one.
7205 return safe_malloc( sizeof( QueryFrame ) );
7209 @brief Free a QueryFrame, and all the memory it owns.
7210 @param frame Pointer to the QueryFrame to be freed.
7212 static void free_query_frame( QueryFrame* frame ) {
7217 clear_class_info( &frame->core );
7219 // Free the join list
7221 ClassInfo* info = frame->join_list;
7224 free_class_info( info );
7228 frame->join_list = NULL;
7231 // If the frame is a static instance, just mark it as unused
7233 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7234 if( frame == static_frame + i ) {
7235 static_frame[ i ].in_use = 0;
7240 // Otherwise it must have been malloc'd, so free it
7246 @brief Search a given QueryFrame for a specified alias.
7247 @param frame Pointer to the QueryFrame to be searched.
7248 @param target The alias for which to search.
7249 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7251 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7252 if( ! frame || ! target ) {
7256 ClassInfo* found_class = NULL;
7258 if( !strcmp( target, frame->core.alias ) )
7259 return &(frame->core);
7261 ClassInfo* curr_class = frame->join_list;
7262 while( curr_class ) {
7263 if( strcmp( target, curr_class->alias ) )
7264 curr_class = curr_class->next;
7266 found_class = curr_class;
7276 @brief Push a new (blank) QueryFrame onto the stack.
7278 static void push_query_frame( void ) {
7279 QueryFrame* frame = allocate_frame();
7280 frame->join_list = NULL;
7281 frame->next = curr_query;
7283 // Initialize the ClassInfo for the core class
7284 ClassInfo* core = &frame->core;
7285 core->alias = core->class_name = core->source_def = NULL;
7286 core->class_def = core->fields = core->links = NULL;
7292 @brief Pop a QueryFrame off the stack and destroy it.
7294 static void pop_query_frame( void ) {
7299 QueryFrame* popped = curr_query;
7300 curr_query = popped->next;
7302 free_query_frame( popped );
7306 @brief Populate the ClassInfo for the core class.
7307 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7308 class name as an alias.
7309 @param class_name Name of the core class.
7310 @return Zero if successful, or 1 if not.
7312 Populate the ClassInfo of the core class with copies of the alias and class name, and
7313 with pointers to the relevant portions of the IDL for the core class.
7315 static int add_query_core( const char* alias, const char* class_name ) {
7318 if( ! curr_query ) {
7319 osrfLogError( OSRF_LOG_MARK,
7320 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7322 } else if( curr_query->core.alias ) {
7323 osrfLogError( OSRF_LOG_MARK,
7324 "%s ERROR: Core class %s already populated as %s",
7325 modulename, curr_query->core.class_name, curr_query->core.alias );
7329 build_class_info( &curr_query->core, alias, class_name );
7330 if( curr_query->core.alias )
7333 osrfLogError( OSRF_LOG_MARK,
7334 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7340 @brief Search the current QueryFrame for a specified alias.
7341 @param target The alias for which to search.
7342 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7344 static inline ClassInfo* search_alias( const char* target ) {
7345 return search_alias_in_frame( curr_query, target );
7349 @brief Search all levels of query for a specified alias, starting with the current query.
7350 @param target The alias for which to search.
7351 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7353 static ClassInfo* search_all_alias( const char* target ) {
7354 ClassInfo* found_class = NULL;
7355 QueryFrame* curr_frame = curr_query;
7357 while( curr_frame ) {
7358 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7361 curr_frame = curr_frame->next;
7368 @brief Add a class to the list of classes joined to the current query.
7369 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7370 the class name as an alias.
7371 @param classname The name of the class to be added.
7372 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7374 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7376 if( ! classname || ! *classname ) { // sanity check
7377 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7384 const ClassInfo* conflict = search_alias( alias );
7386 osrfLogError( OSRF_LOG_MARK,
7387 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7388 modulename, alias, conflict->class_name );
7392 ClassInfo* info = allocate_class_info();
7394 if( build_class_info( info, alias, classname ) ) {
7395 free_class_info( info );
7399 // Add the new ClassInfo to the join list of the current QueryFrame
7400 info->next = curr_query->join_list;
7401 curr_query->join_list = info;
7407 @brief Destroy all nodes on the query stack.
7409 static void clear_query_stack( void ) {
7415 @brief Implement the set_audit_info method.
7416 @param ctx Pointer to the method context.
7417 @return Zero if successful, or -1 if not.
7419 Issue a SAVEPOINT to the database server.
7424 - workstation id (int)
7426 If user id is not provided the authkey will be used.
7427 For PCRUD the authkey is always used, even if a user is provided.
7429 int setAuditInfo( osrfMethodContext* ctx ) {
7430 if(osrfMethodVerifyContext( ctx )) {
7431 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
7435 // Get the user id from the parameters
7436 const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7438 if( enforce_pcrud || !user_id ) {
7439 timeout_needs_resetting = 1;
7440 const jsonObject* user = verifyUserPCRUD( ctx );
7443 osrfAppRespondComplete( ctx, NULL );
7447 // Not PCRUD and have a user_id?
7448 int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7449 osrfAppRespondComplete( ctx, NULL );
7454 @brief Save a audit info
7455 @param ctx Pointer to the method context.
7456 @param user_id User ID to write as a string
7457 @param ws_id Workstation ID to write as a string
7459 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7460 if( ctx && ctx->session ) {
7461 osrfAppSession* session = ctx->session;
7463 osrfHash* cache = session->userData;
7465 // If the session doesn't already have a hash, create one. Make sure
7466 // that the application session frees the hash when it terminates.
7467 if( NULL == cache ) {
7468 session->userData = cache = osrfNewHash();
7469 osrfHashSetCallback( cache, &sessionDataFree );
7470 ctx->session->userDataFree = &userDataFree;
7473 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7475 osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7477 int errnum = dbi_conn_error( writehandle, &msg );
7480 "%s: Error setting auditor information: %d %s",
7483 msg ? msg : "(No description available)"
7485 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7486 "osrfMethodException", ctx->request, "Error setting auditor info" );
7487 if( !oilsIsDBConnected( writehandle ))
7488 osrfAppSessionPanic( ctx->session );
7491 dbi_result_free( result );
7498 @brief Remove all but safe character from savepoint name
7499 @param sp User-supplied savepoint name
7500 @return sanitized savepoint name, or NULL
7502 The caller is expected to free the returned string. Note that
7503 this function exists only because we can't use PQescapeLiteral
7504 without either forking libdbi or abandoning it.
7506 static char* _sanitize_savepoint_name( const char* sp ) {
7508 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_";
7510 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7511 // and the default value of NAMEDATALEN is 64; that should be long enough
7512 // for our purposes, and it's unlikely that anyone is going to recompile
7513 // PostgreSQL to have a smaller value, so cap the identifier name
7514 // accordingly to avoid the remote chance that someone manages to pass in a
7515 // 12GB savepoint name
7516 const int MAX_LITERAL_NAMELEN = 63;
7519 if (len > MAX_LITERAL_NAMELEN) {
7520 len = MAX_LITERAL_NAMELEN;
7523 char* safeSpName = safe_malloc( len + 1 );
7527 for (j = 0; j < len; j++) {
7528 found = strchr(safe_chars, sp[j]);
7530 safeSpName[ i++ ] = found[0];
7533 safeSpName[ i ] = '\0';