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* searchBETWEENRange( osrfHash* field, const jsonObject* node );
89 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
90 static char* searchINList( osrfHash* field, jsonObject* node, const char* op, osrfMethodContext* ctx );
91 static char* searchINPredicate ( const char*, osrfHash*,
92 jsonObject*, const char*, osrfMethodContext* );
93 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
94 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
95 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo*, int, osrfMethodContext* );
96 static char* buildSELECT( const jsonObject*, jsonObject* rest_of_query,
97 osrfHash* meta, osrfMethodContext* ctx );
98 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array );
100 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
102 char* SELECT ( osrfMethodContext*, jsonObject*, const jsonObject*, const jsonObject*,
103 const jsonObject*, const jsonObject*, const jsonObject*, const jsonObject*, int );
105 static osrfStringArray* getPermLocationCache( osrfMethodContext*, const char* );
106 static void setPermLocationCache( osrfMethodContext*, const char*, osrfStringArray* );
108 void userDataFree( void* );
109 static void sessionDataFree( char*, void* );
110 static void pcacheFree( char*, void* );
111 static int obj_is_true( const jsonObject* obj );
112 static const char* json_type( int code );
113 static const char* get_primitive( osrfHash* field );
114 static const char* get_datatype( osrfHash* field );
115 static void pop_query_frame( void );
116 static void push_query_frame( void );
117 static int add_query_core( const char* alias, const char* class_name );
118 static inline ClassInfo* search_alias( const char* target );
119 static ClassInfo* search_all_alias( const char* target );
120 static ClassInfo* add_joined_class( const char* alias, const char* classname );
121 static void clear_query_stack( void );
123 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
124 static const jsonObject* verifyUserPCRUDfull( osrfMethodContext*, int );
125 static int verifyObjectPCRUD( osrfMethodContext*, osrfHash*, const jsonObject*, int );
126 static const char* org_tree_root( osrfMethodContext* ctx );
127 static jsonObject* single_hash( const char* key, const char* value );
129 static int child_initialized = 0; /* boolean */
131 static dbi_conn writehandle; /* our MASTER db connection */
132 static dbi_conn dbhandle; /* our CURRENT db connection */
133 //static osrfHash * readHandles;
135 // The following points to the top of a stack of QueryFrames. It's a little
136 // confusing because the top level of the query is at the bottom of the stack.
137 static QueryFrame* curr_query = NULL;
139 static dbi_conn writehandle; /* our MASTER db connection */
140 static dbi_conn dbhandle; /* our CURRENT db connection */
141 //static osrfHash * readHandles;
143 static int max_flesh_depth = 100;
145 static int perm_at_threshold = 5;
146 static int enforce_pcrud = 0; // Boolean
147 static char* modulename = NULL;
149 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id);
151 static char* _sanitize_tz_name( const char* tz );
152 static char* _sanitize_savepoint_name( const char* sp );
155 @brief Connect to the database.
156 @return A database connection if successful, or NULL if not.
158 dbi_conn oilsConnectDB( const char* mod_name ) {
160 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
161 if( dbi_initialize( NULL ) == -1 ) {
162 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
165 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
167 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
168 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
169 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
170 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
171 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
172 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
173 char* pg_app = osrf_settings_host_value( "/apps/%s/app_settings/database/application_name", mod_name );
175 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
176 dbi_conn handle = dbi_conn_new( driver );
179 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
182 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
184 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
185 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
187 if( host ) dbi_conn_set_option( handle, "host", host );
188 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
189 if( user ) dbi_conn_set_option( handle, "username", user );
190 if( pw ) dbi_conn_set_option( handle, "password", pw );
191 if( db ) dbi_conn_set_option( handle, "dbname", db );
192 if( pg_app ) dbi_conn_set_option( handle, "pgsql_application_name", pg_app );
201 if( dbi_conn_connect( handle ) < 0 ) {
203 if( dbi_conn_connect( handle ) < 0 ) {
205 dbi_conn_error( handle, &msg );
206 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
207 msg ? msg : "(No description available)" );
212 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
218 @brief Select some options.
219 @param module_name: Name of the server.
220 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
222 This source file is used (at this writing) to implement three different servers:
223 - open-ils.reporter-store
227 These servers behave mostly the same, but they implement different combinations of
228 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
230 Here we use the server name in messages to identify which kind of server issued them.
231 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
233 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
235 module_name = "open-ils.cstore"; // bulletproofing with a default
240 modulename = strdup( module_name );
241 enforce_pcrud = do_pcrud;
242 max_flesh_depth = flesh_depth;
246 @brief Install a database connection.
247 @param conn Pointer to a database connection.
249 In some contexts, @a conn may merely provide a driver so that we can process strings
250 properly, without providing an open database connection.
252 void oilsSetDBConnection( dbi_conn conn ) {
253 dbhandle = writehandle = conn;
257 @brief Determine whether a database connection is alive.
258 @param handle Handle for a database connection.
259 @return 1 if the connection is alive, or zero if it isn't.
261 int oilsIsDBConnected( dbi_conn handle ) {
262 // Do an innocuous SELECT. If it succeeds, the database connection is still good.
263 dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
265 dbi_result_free( result );
268 // This is a terrible, horrible, no good, very bad kludge.
269 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
270 // but because (due to a previous error) the database is ignoring all commands,
271 // even innocuous SELECTs, until the current transaction is rolled back. The only
272 // known way to detect this condition via the dbi library is by looking at the error
273 // message. This approach will break if the language or wording of the message ever
275 // Note: the dbi_conn_ping function purports to determine whether the database
276 // connection is live, but at this writing this function is unreliable and useless.
277 static const char* ok_msg = "ERROR: current transaction is aborted, commands "
278 "ignored until end of transaction block\n";
280 dbi_conn_error( handle, &msg );
281 // Newer versions of dbi_conn_error return codes within the error msg.
282 // E.g. 3624914: ERROR: current transaction is aborted, commands ignored until end of transaction block
283 // Substring test should work regardless.
284 const char* substr = strstr(msg, ok_msg);
285 if( substr == NULL ) {
286 osrfLogError( OSRF_LOG_MARK, "Database connection isn't working : %s", msg );
289 return 1; // ignoring SELECT due to previous error; that's okay
294 @brief Get a table name, view name, or subquery for use in a FROM clause.
295 @param class Pointer to the IDL class entry.
296 @return A table name, a view name, or a subquery in parentheses.
298 In some cases the IDL defines a class, not with a table name or a view name, but with
299 a SELECT statement, which may be used as a subquery.
301 char* oilsGetRelation( osrfHash* classdef ) {
303 char* source_def = NULL;
304 const char* tabledef = osrfHashGet( classdef, "tablename" );
307 source_def = strdup( tabledef ); // Return the name of a table or view
309 tabledef = osrfHashGet( classdef, "source_definition" );
311 // Return a subquery, enclosed in parentheses
312 source_def = safe_malloc( strlen( tabledef ) + 3 );
313 source_def[ 0 ] = '(';
314 strcpy( source_def + 1, tabledef );
315 strcat( source_def, ")" );
317 // Not found: return an error
318 const char* classname = osrfHashGet( classdef, "classname" );
323 "%s ERROR No tablename or source_definition for class \"%s\"",
334 @brief Add datatypes from the database to the fields in the IDL.
335 @param handle Handle for a database connection
336 @return Zero if successful, or 1 upon error.
338 For each relevant class in the IDL: ask the database for the datatype of every field.
339 In particular, determine which fields are text fields and which fields are numeric
340 fields, so that we know whether to enclose their values in quotes.
342 int oilsExtendIDL( dbi_conn handle ) {
343 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
344 osrfHash* class = NULL;
345 growing_buffer* query_buf = buffer_init( 64 );
346 int results_found = 0; // boolean
348 // For each class in the IDL...
349 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
350 const char* classname = osrfHashIteratorKey( class_itr );
351 osrfHash* fields = osrfHashGet( class, "fields" );
353 // If the class is virtual, ignore it
354 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
355 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
359 char* tabledef = oilsGetRelation( class );
361 continue; // No such relation -- a query of it would be doomed to failure
363 buffer_reset( query_buf );
364 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
368 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
369 modulename, OSRF_BUFFER_C_STR( query_buf ) );
371 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
376 const char* columnName;
377 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
379 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
382 /* fetch the fieldmapper index */
383 osrfHash* _f = osrfHashGet(fields, columnName);
386 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
388 /* determine the field type and storage attributes */
390 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
392 case DBI_TYPE_INTEGER : {
394 if( !osrfHashGet(_f, "primitive") )
395 osrfHashSet(_f, "number", "primitive");
397 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
398 if( attr & DBI_INTEGER_SIZE8 )
399 osrfHashSet( _f, "INT8", "datatype" );
401 osrfHashSet( _f, "INT", "datatype" );
404 case DBI_TYPE_DECIMAL :
405 if( !osrfHashGet( _f, "primitive" ))
406 osrfHashSet( _f, "number", "primitive" );
408 osrfHashSet( _f, "NUMERIC", "datatype" );
411 case DBI_TYPE_STRING :
412 if( !osrfHashGet( _f, "primitive" ))
413 osrfHashSet( _f, "string", "primitive" );
415 osrfHashSet( _f,"TEXT", "datatype" );
418 case DBI_TYPE_DATETIME :
419 if( !osrfHashGet( _f, "primitive" ))
420 osrfHashSet( _f, "string", "primitive" );
422 osrfHashSet( _f, "TIMESTAMP", "datatype" );
425 case DBI_TYPE_BINARY :
426 if( !osrfHashGet( _f, "primitive" ))
427 osrfHashSet( _f, "string", "primitive" );
429 osrfHashSet( _f, "BYTEA", "datatype" );
434 "Setting [%s] to primitive [%s] and datatype [%s]...",
436 osrfHashGet( _f, "primitive" ),
437 osrfHashGet( _f, "datatype" )
441 } // end while loop for traversing columns of result
442 dbi_result_free( result );
445 int errnum = dbi_conn_error( handle, &msg );
446 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
447 errnum, msg ? msg : "(No description available)" );
448 // We don't check the database connection here. It's routine to get failures at
449 // this point; we routinely try to query tables that don't exist, because they
450 // are defined in the IDL but not in the database.
452 } // end for each class in IDL
454 buffer_free( query_buf );
455 osrfHashIteratorFree( class_itr );
456 child_initialized = 1;
458 if( !results_found ) {
459 osrfLogError( OSRF_LOG_MARK,
460 "No results found for any class -- bad database connection?" );
462 } else if( ! oilsIsDBConnected( handle )) {
463 osrfLogError( OSRF_LOG_MARK,
464 "Unable to extend IDL: database connection isn't working" );
472 @brief Free an osrfHash that stores a transaction ID.
473 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
475 This function is a callback, to be called by the application session when it ends.
476 The application session stores the osrfHash via an opaque pointer.
478 If the osrfHash contains an entry for the key "xact_id", it means that an
479 uncommitted transaction is pending. Roll it back.
481 void userDataFree( void* blob ) {
482 osrfHash* hash = (osrfHash*) blob;
483 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
484 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
486 int errnum = dbi_conn_error( writehandle, &msg );
487 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
488 errnum, msg ? msg : "(No description available)" );
492 if( !dbi_conn_query( writehandle, "SELECT auditor.clear_audit_info();" ) ) {
494 int errnum = dbi_conn_error( writehandle, &msg );
495 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform audit info clearing: %d %s",
496 errnum, msg ? msg : "(No description available)" );
500 osrfHashFree( hash );
504 @name Managing session data
505 @brief Maintain data stored via the userData pointer of the application session.
507 Currently, session-level data is stored in an osrfHash. Other arrangements are
508 possible, and some would be more efficient. The application session calls a
509 callback function to free userData before terminating.
511 Currently, the only data we store at the session level is the transaction id. By this
512 means we can ensure that any pending transactions are rolled back before the application
518 @brief Free an item in the application session's userData.
519 @param key The name of a key for an osrfHash.
520 @param item An opaque pointer to the item associated with the key.
522 We store an osrfHash as userData with the application session, and arrange (by
523 installing userDataFree() as a different callback) for the session to free that
524 osrfHash before terminating.
526 This function is a callback for freeing items in the osrfHash. Currently we store
528 - Transaction id of a pending transaction; a character string. Key: "xact_id".
529 - Authkey; a character string. Key: "authkey".
530 - User object from the authentication server; a jsonObject. Key: "user_login".
532 If we ever store anything else in userData, we will need to revisit this function so
533 that it will free whatever else needs freeing.
535 static void sessionDataFree( char* key, void* item ) {
536 if( !strcmp( key, "xact_id" ) || !strcmp( key, "authkey" ) || !strncmp( key, "rs_size_", 8) )
538 else if( !strcmp( key, "user_login" ) )
539 jsonObjectFree( (jsonObject*) item );
540 else if( !strcmp( key, "pcache" ) )
541 osrfHashFree( (osrfHash*) item );
544 static void pcacheFree( char* key, void* item ) {
545 osrfStringArrayFree( (osrfStringArray*) item );
549 @brief Initialize session cache.
550 @param ctx Pointer to the method context.
552 Create a cache for the session by making the session's userData member point
553 to an osrfHash instance.
555 static osrfHash* initSessionCache( osrfMethodContext* ctx ) {
556 ctx->session->userData = osrfNewHash();
557 osrfHashSetCallback( (osrfHash*) ctx->session->userData, &sessionDataFree );
558 ctx->session->userDataFree = &userDataFree;
559 return ctx->session->userData;
563 @brief Save a transaction id.
564 @param ctx Pointer to the method context.
566 Save the session_id of the current application session as a transaction id.
568 static void setXactId( osrfMethodContext* ctx ) {
569 if( ctx && ctx->session ) {
570 osrfAppSession* session = ctx->session;
572 osrfHash* cache = session->userData;
574 // If the session doesn't already have a hash, create one. Make sure
575 // that the application session frees the hash when it terminates.
577 cache = initSessionCache( ctx );
579 // Save the transaction id in the hash, with the key "xact_id"
580 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
585 @brief Get the transaction ID for the current transaction, if any.
586 @param ctx Pointer to the method context.
587 @return Pointer to the transaction ID.
589 The return value points to an internal buffer, and will become invalid upon issuing
590 a commit or rollback.
592 static inline const char* getXactId( osrfMethodContext* ctx ) {
593 if( ctx && ctx->session && ctx->session->userData )
594 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
600 @brief Clear the current transaction id.
601 @param ctx Pointer to the method context.
603 static inline void clearXactId( osrfMethodContext* ctx ) {
604 if( ctx && ctx->session && ctx->session->userData )
605 osrfHashRemove( ctx->session->userData, "xact_id" );
610 @brief Stash the location for a particular perm in the sessionData cache
611 @param ctx Pointer to the method context.
612 @param perm Name of the permission we're looking at
613 @param array StringArray of perm location ids
615 static void setPermLocationCache( osrfMethodContext* ctx, const char* perm, osrfStringArray* locations ) {
616 if( ctx && ctx->session ) {
617 osrfAppSession* session = ctx->session;
619 osrfHash* cache = session->userData;
621 // If the session doesn't already have a hash, create one. Make sure
622 // that the application session frees the hash when it terminates.
624 cache = initSessionCache( ctx );
626 osrfHash* pcache = osrfHashGet(cache, "pcache");
628 if( NULL == pcache ) {
629 pcache = osrfNewHash();
630 osrfHashSetCallback( pcache, &pcacheFree );
631 osrfHashSet( cache, pcache, "pcache" );
634 if( perm && locations )
635 osrfHashSet( pcache, locations, strdup(perm) );
640 @brief Grab stashed location for a particular perm in the sessionData cache
641 @param ctx Pointer to the method context.
642 @param perm Name of the permission we're looking at
644 static osrfStringArray* getPermLocationCache( osrfMethodContext* ctx, const char* perm ) {
645 if( ctx && ctx->session ) {
646 osrfAppSession* session = ctx->session;
647 osrfHash* cache = session->userData;
649 osrfHash* pcache = osrfHashGet(cache, "pcache");
651 return osrfHashGet( pcache, perm );
660 @brief Save the user's login in the userData for the current application session.
661 @param ctx Pointer to the method context.
662 @param user_login Pointer to the user login object to be cached (we cache the original,
665 If @a user_login is NULL, remove the user login if one is already cached.
667 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
668 if( ctx && ctx->session ) {
669 osrfAppSession* session = ctx->session;
671 osrfHash* cache = session->userData;
673 // If the session doesn't already have a hash, create one. Make sure
674 // that the application session frees the hash when it terminates.
676 cache = initSessionCache( ctx );
679 osrfHashSet( cache, user_login, "user_login" );
681 osrfHashRemove( cache, "user_login" );
686 @brief Get the user login object for the current application session, if any.
687 @param ctx Pointer to the method context.
688 @return Pointer to the user login object if found; otherwise NULL.
690 The user login object was returned from the authentication server, and then cached so
691 we don't have to call the authentication server again for the same user.
693 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
694 if( ctx && ctx->session && ctx->session->userData )
695 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
701 @brief Save a copy of an authkey in the userData of the current application session.
702 @param ctx Pointer to the method context.
703 @param authkey The authkey to be saved.
705 If @a authkey is NULL, remove the authkey if one is already cached.
707 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
708 if( ctx && ctx->session && authkey ) {
709 osrfAppSession* session = ctx->session;
710 osrfHash* cache = session->userData;
712 // If the session doesn't already have a hash, create one. Make sure
713 // that the application session frees the hash when it terminates.
715 cache = initSessionCache( ctx );
717 // Save the transaction id in the hash, with the key "xact_id"
718 if( authkey && *authkey )
719 osrfHashSet( cache, strdup( authkey ), "authkey" );
721 osrfHashRemove( cache, "authkey" );
726 @brief Reset the login timeout.
727 @param authkey The authentication key for the current login session.
728 @param now The current time.
729 @return Zero if successful, or 1 if not.
731 Tell the authentication server to reset the timeout so that the login session won't
732 expire for a while longer.
734 We could dispense with the @a now parameter by calling time(). But we just called
735 time() in order to decide whether to reset the timeout, so we might as well reuse
736 the result instead of calling time() again.
738 static int reset_timeout( const char* authkey, time_t now ) {
739 jsonObject* auth_object = jsonNewObject( authkey );
741 // Ask the authentication server to reset the timeout. It returns an event
742 // indicating success or failure.
743 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
744 "open-ils.auth.session.reset_timeout", auth_object );
745 jsonObjectFree( auth_object );
747 if( !result || result->type != JSON_HASH ) {
748 osrfLogError( OSRF_LOG_MARK,
749 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
750 jsonObjectFree( result );
751 return 1; // Not the right sort of object returned
754 const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
755 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
756 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
757 jsonObjectFree( result );
758 return 1; // Return code from method not available
761 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
762 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
764 desc = "(No reason available)"; // failsafe; shouldn't happen
765 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
766 jsonObjectFree( result );
770 // Revise our local proxy for the timeout deadline
771 // by a smallish fraction of the timeout interval
772 const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
774 timeout = "1"; // failsafe; shouldn't happen
775 time_next_reset = now + atoi( timeout ) / 15;
777 jsonObjectFree( result );
778 return 0; // Successfully reset timeout
782 @brief Get the authkey string for the current application session, if any.
783 @param ctx Pointer to the method context.
784 @return Pointer to the cached authkey if found; otherwise NULL.
786 If present, the authkey string was cached from a previous method call.
788 static const char* getAuthkey( osrfMethodContext* ctx ) {
789 if( ctx && ctx->session && ctx->session->userData ) {
790 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
791 // LFW recent changes mean the userData hash gets set up earlier, but
792 // doesn't necessarily have an authkey yet
796 // Possibly reset the authentication timeout to keep the login alive. We do so
797 // no more than once per method call, and not at all if it has been only a short
798 // time since the last reset.
800 // Here we reset explicitly, if at all. We also implicitly reset the timeout
801 // whenever we call the "open-ils.auth.session.retrieve" method.
802 if( timeout_needs_resetting ) {
803 time_t now = time( NULL );
804 if( now >= time_next_reset && reset_timeout( authkey, now ) )
805 authkey = NULL; // timeout has apparently expired already
808 timeout_needs_resetting = 0;
816 @brief Implement the transaction.begin method.
817 @param ctx Pointer to the method context.
818 @return Zero if successful, or -1 upon error.
820 Start a transaction. Save a transaction ID for future reference.
823 - authkey (PCRUD only)
825 Return to client: Transaction ID
827 int beginTransaction( osrfMethodContext* ctx ) {
828 if(osrfMethodVerifyContext( ctx )) {
829 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
833 const char* tz = _sanitize_tz_name(ctx->session->session_tz);
835 if( enforce_pcrud ) {
836 timeout_needs_resetting = 1;
837 const jsonObject* user = verifyUserPCRUD( ctx );
842 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
845 int errnum = dbi_conn_error( writehandle, &msg );
846 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
847 modulename, errnum, msg ? msg : "(No description available)" );
848 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
849 "osrfMethodException", ctx->request, "Error starting transaction" );
850 if( !oilsIsDBConnected( writehandle ))
851 osrfAppSessionPanic( ctx->session );
854 dbi_result_free( result );
856 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
857 osrfAppRespondComplete( ctx, ret );
858 jsonObjectFree( ret );
865 dbi_result tz_res = dbi_conn_queryf( writehandle, "SET LOCAL timezone TO '%s'; -- cstore", tz );
867 osrfLogError( OSRF_LOG_MARK, "%s: Error setting timezone %s", modulename, tz);
868 if( !oilsIsDBConnected( writehandle )) {
869 osrfAppSessionPanic( ctx->session );
873 dbi_result_free( tz_res );
878 dbi_result res = dbi_conn_queryf( writehandle, "SET timezone TO DEFAULT; -- no tz" );
880 osrfLogError( OSRF_LOG_MARK, "%s: Error resetting timezone", modulename);
881 if( !oilsIsDBConnected( writehandle )) {
882 osrfAppSessionPanic( ctx->session );
886 dbi_result_free( res );
894 @brief Implement the savepoint.set method.
895 @param ctx Pointer to the method context.
896 @return Zero if successful, or -1 if not.
898 Issue a SAVEPOINT to the database server.
901 - authkey (PCRUD only)
904 Return to client: Savepoint name
906 int setSavepoint( osrfMethodContext* ctx ) {
907 if(osrfMethodVerifyContext( ctx )) {
908 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
913 if( enforce_pcrud ) {
915 timeout_needs_resetting = 1;
916 const jsonObject* user = verifyUserPCRUD( ctx );
921 // Verify that a transaction is pending
922 const char* trans_id = getXactId( ctx );
923 if( NULL == trans_id ) {
924 osrfAppSessionStatus(
926 OSRF_STATUS_INTERNALSERVERERROR,
927 "osrfMethodException",
929 "No active transaction -- required for savepoints"
934 // Get the savepoint name from the method params
935 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
938 osrfLogWarning(OSRF_LOG_MARK, "savepoint.set called with no name");
942 char *safeSpName = _sanitize_savepoint_name( spName );
944 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", safeSpName );
948 int errnum = dbi_conn_error( writehandle, &msg );
951 "%s: Error creating savepoint %s in transaction %s: %d %s",
956 msg ? msg : "(No description available)"
958 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
959 "osrfMethodException", ctx->request, "Error creating savepoint" );
960 if( !oilsIsDBConnected( writehandle ))
961 osrfAppSessionPanic( ctx->session );
964 dbi_result_free( result );
965 jsonObject* ret = jsonNewObject( spName );
966 osrfAppRespondComplete( ctx, ret );
967 jsonObjectFree( ret );
973 @brief Implement the savepoint.release method.
974 @param ctx Pointer to the method context.
975 @return Zero if successful, or -1 if not.
977 Issue a RELEASE SAVEPOINT to the database server.
980 - authkey (PCRUD only)
983 Return to client: Savepoint name
985 int releaseSavepoint( osrfMethodContext* ctx ) {
986 if(osrfMethodVerifyContext( ctx )) {
987 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
992 if( enforce_pcrud ) {
994 timeout_needs_resetting = 1;
995 const jsonObject* user = verifyUserPCRUD( ctx );
1000 // Verify that a transaction is pending
1001 const char* trans_id = getXactId( ctx );
1002 if( NULL == trans_id ) {
1003 osrfAppSessionStatus(
1005 OSRF_STATUS_INTERNALSERVERERROR,
1006 "osrfMethodException",
1008 "No active transaction -- required for savepoints"
1013 // Get the savepoint name from the method params
1014 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1017 osrfLogWarning(OSRF_LOG_MARK, "savepoint.release called with no name");
1021 char *safeSpName = _sanitize_savepoint_name( spName );
1023 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", safeSpName );
1027 int errnum = dbi_conn_error( writehandle, &msg );
1030 "%s: Error releasing savepoint %s in transaction %s: %d %s",
1035 msg ? msg : "(No description available)"
1037 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1038 "osrfMethodException", ctx->request, "Error releasing savepoint" );
1039 if( !oilsIsDBConnected( writehandle ))
1040 osrfAppSessionPanic( ctx->session );
1043 dbi_result_free( result );
1044 jsonObject* ret = jsonNewObject( spName );
1045 osrfAppRespondComplete( ctx, ret );
1046 jsonObjectFree( ret );
1052 @brief Implement the savepoint.rollback method.
1053 @param ctx Pointer to the method context.
1054 @return Zero if successful, or -1 if not.
1056 Issue a ROLLBACK TO SAVEPOINT to the database server.
1059 - authkey (PCRUD only)
1062 Return to client: Savepoint name
1064 int rollbackSavepoint( osrfMethodContext* ctx ) {
1065 if(osrfMethodVerifyContext( ctx )) {
1066 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1071 if( enforce_pcrud ) {
1073 timeout_needs_resetting = 1;
1074 const jsonObject* user = verifyUserPCRUD( ctx );
1079 // Verify that a transaction is pending
1080 const char* trans_id = getXactId( ctx );
1081 if( NULL == trans_id ) {
1082 osrfAppSessionStatus(
1084 OSRF_STATUS_INTERNALSERVERERROR,
1085 "osrfMethodException",
1087 "No active transaction -- required for savepoints"
1092 // Get the savepoint name from the method params
1093 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1096 osrfLogWarning(OSRF_LOG_MARK, "savepoint.rollback called with no name");
1100 char *safeSpName = _sanitize_savepoint_name( spName );
1102 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", safeSpName );
1106 int errnum = dbi_conn_error( writehandle, &msg );
1109 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
1114 msg ? msg : "(No description available)"
1116 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1117 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1118 if( !oilsIsDBConnected( writehandle ))
1119 osrfAppSessionPanic( ctx->session );
1122 dbi_result_free( result );
1123 jsonObject* ret = jsonNewObject( spName );
1124 osrfAppRespondComplete( ctx, ret );
1125 jsonObjectFree( ret );
1131 @brief Implement the transaction.commit method.
1132 @param ctx Pointer to the method context.
1133 @return Zero if successful, or -1 if not.
1135 Issue a COMMIT to the database server.
1138 - authkey (PCRUD only)
1140 Return to client: Transaction ID.
1142 int commitTransaction( osrfMethodContext* ctx ) {
1143 if(osrfMethodVerifyContext( ctx )) {
1144 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1148 if( enforce_pcrud ) {
1149 timeout_needs_resetting = 1;
1150 const jsonObject* user = verifyUserPCRUD( ctx );
1155 // Verify that a transaction is pending
1156 const char* trans_id = getXactId( ctx );
1157 if( NULL == trans_id ) {
1158 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1159 "osrfMethodException", ctx->request, "No active transaction to commit" );
1163 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1166 int errnum = dbi_conn_error( writehandle, &msg );
1167 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1168 modulename, errnum, msg ? msg : "(No description available)" );
1169 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1170 "osrfMethodException", ctx->request, "Error committing transaction" );
1171 if( !oilsIsDBConnected( writehandle ))
1172 osrfAppSessionPanic( ctx->session );
1175 dbi_result_free( result );
1176 jsonObject* ret = jsonNewObject( trans_id );
1177 osrfAppRespondComplete( ctx, ret );
1178 jsonObjectFree( ret );
1185 @brief Implement the transaction.rollback method.
1186 @param ctx Pointer to the method context.
1187 @return Zero if successful, or -1 if not.
1189 Issue a ROLLBACK to the database server.
1192 - authkey (PCRUD only)
1194 Return to client: Transaction ID
1196 int rollbackTransaction( osrfMethodContext* ctx ) {
1197 if( osrfMethodVerifyContext( ctx )) {
1198 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1202 if( enforce_pcrud ) {
1203 timeout_needs_resetting = 1;
1204 const jsonObject* user = verifyUserPCRUD( ctx );
1209 // Verify that a transaction is pending
1210 const char* trans_id = getXactId( ctx );
1211 if( NULL == trans_id ) {
1212 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1213 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1217 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1220 int errnum = dbi_conn_error( writehandle, &msg );
1221 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1222 modulename, errnum, msg ? msg : "(No description available)" );
1223 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1224 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1225 if( !oilsIsDBConnected( writehandle ))
1226 osrfAppSessionPanic( ctx->session );
1229 dbi_result_free( result );
1230 jsonObject* ret = jsonNewObject( trans_id );
1231 osrfAppRespondComplete( ctx, ret );
1232 jsonObjectFree( ret );
1239 @brief Implement the "search" method.
1240 @param ctx Pointer to the method context.
1241 @return Zero if successful, or -1 if not.
1244 - authkey (PCRUD only)
1245 - WHERE clause, as jsonObject
1246 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1248 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1249 Optionally flesh linked fields.
1251 int doSearch( osrfMethodContext* ctx ) {
1252 if( osrfMethodVerifyContext( ctx )) {
1253 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1258 timeout_needs_resetting = 1;
1260 jsonObject* where_clause;
1261 jsonObject* rest_of_query;
1263 if( enforce_pcrud ) {
1264 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1265 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1267 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1268 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1271 if( !where_clause ) {
1272 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1276 // Get the class metadata
1277 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1278 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1282 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1284 osrfAppRespondComplete( ctx, NULL );
1288 // doFieldmapperSearch() now takes care of our responding for us
1289 // // Return each row to the client
1290 // jsonObject* cur = 0;
1291 // unsigned long res_idx = 0;
1293 // while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1294 // // We used to discard based on perms here, but now that's
1295 // // inside doFieldmapperSearch()
1296 // osrfAppRespond( ctx, cur );
1299 jsonObjectFree( obj );
1301 osrfAppRespondComplete( ctx, NULL );
1306 @brief Implement the "id_list" method.
1307 @param ctx Pointer to the method context.
1308 @param err Pointer through which to return an error code.
1309 @return Zero if successful, or -1 if not.
1312 - authkey (PCRUD only)
1313 - WHERE clause, as jsonObject
1314 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1316 Return to client: The primary key values for all rows of the relevant class that
1317 satisfy a specified WHERE clause.
1319 This method relies on the assumption that every class has a primary key consisting of
1322 int doIdList( osrfMethodContext* ctx ) {
1323 if( osrfMethodVerifyContext( ctx )) {
1324 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1329 timeout_needs_resetting = 1;
1331 jsonObject* where_clause;
1332 jsonObject* rest_of_query;
1334 // We use the where clause without change. But we need to massage the rest of the
1335 // query, so we work with a copy of it instead of modifying the original.
1337 if( enforce_pcrud ) {
1338 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1339 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1341 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1342 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1345 if( !where_clause ) {
1346 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1350 // Eliminate certain SQL clauses, if present.
1351 if( rest_of_query ) {
1352 jsonObjectRemoveKey( rest_of_query, "select" );
1353 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1354 jsonObjectRemoveKey( rest_of_query, "flesh" );
1355 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1357 rest_of_query = jsonNewObjectType( JSON_HASH );
1360 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1362 // Get the class metadata
1363 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1364 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1366 // Build a SELECT list containing just the primary key,
1367 // i.e. like { "classname":["keyname"] }
1368 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1370 // Load array with name of primary key
1371 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1372 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1373 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1375 jsonObjectSetKey( rest_of_query, "select", select_clause );
1380 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1382 jsonObjectFree( rest_of_query );
1384 osrfAppRespondComplete( ctx, NULL );
1388 // Return each primary key value to the client
1390 unsigned long res_idx = 0;
1391 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1392 // We used to discard based on perms here, but now that's
1393 // inside doFieldmapperSearch()
1394 osrfAppRespond( ctx,
1395 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1398 jsonObjectFree( obj );
1399 osrfAppRespondComplete( ctx, NULL );
1404 @brief Verify that we have a valid class reference.
1405 @param ctx Pointer to the method context.
1406 @param param Pointer to the method parameters.
1407 @return 1 if the class reference is valid, or zero if it isn't.
1409 The class of the method params must match the class to which the method id devoted.
1410 For PCRUD there are additional restrictions.
1412 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1414 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1415 osrfHash* class = osrfHashGet( method_meta, "class" );
1417 // Compare the method's class to the parameters' class
1418 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1420 // Oops -- they don't match. Complain.
1421 growing_buffer* msg = buffer_init( 128 );
1424 "%s: %s method for type %s was passed a %s",
1426 osrfHashGet( method_meta, "methodtype" ),
1427 osrfHashGet( class, "classname" ),
1428 param->classname ? param->classname : "(null)"
1431 char* m = buffer_release( msg );
1432 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1440 return verifyObjectPCRUD( ctx, class, param, 1 );
1446 @brief (PCRUD only) Verify that the user is properly logged in.
1447 @param ctx Pointer to the method context.
1448 @return If the user is logged in, a pointer to the user object from the authentication
1449 server; otherwise NULL.
1451 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1452 return verifyUserPCRUDfull( ctx, 0 );
1455 static const jsonObject* verifyUserPCRUDfull( osrfMethodContext* ctx, int anon_ok ) {
1457 // Get the authkey (the first method parameter)
1458 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1460 jsonObject* user = NULL;
1461 int notify_failed_verify = 1; // assume failure
1464 // auth token is null, fail by default
1465 } else if( strcmp( "ANONYMOUS", auth ) ) {
1466 // If we are /not/ in anonymous mode
1467 // See if we have the same authkey, and a user object,
1468 // locally cached from a previous call
1469 const char* cached_authkey = getAuthkey( ctx );
1470 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1471 const jsonObject* cached_user = getUserLogin( ctx );
1476 // We have no matching authentication data in the cache. Authenticate from scratch.
1477 jsonObject* auth_object = jsonNewObject( auth );
1479 // Fetch the user object from the authentication server
1480 user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve", auth_object );
1481 jsonObjectFree( auth_object );
1483 if( !user->classname || strcmp(user->classname, "au" )) {
1484 jsonObjectFree( user );
1486 } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
1487 // Failed to set audit information - But note that write_audit_info already set error information.
1488 notify_failed_verify = 0;
1489 jsonObjectFree( user );
1491 } else { // succeeded
1492 notify_failed_verify = 0;
1495 } else if ( anon_ok ) { // we /are/ (attempting to be) anonymous
1496 user = jsonNewObjectType(JSON_ARRAY);
1497 jsonObjectSetClass( user, "aou" );
1498 oilsFMSetString(user, "id", "-1");
1499 notify_failed_verify = 0;
1502 if (notify_failed_verify) {
1503 growing_buffer* msg = buffer_init( 128 );
1506 "%s: permacrud received a bad auth token: %s",
1511 char* m = buffer_release( msg );
1512 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1517 setUserLogin( ctx, user );
1518 setAuthkey( ctx, auth );
1520 // Allow ourselves up to a second before we have to reset the login timeout.
1521 // It would be nice to use some fraction of the timeout interval enforced by the
1522 // authentication server, but that value is not readily available at this point.
1523 // Instead, we use a conservative default interval.
1524 time_next_reset = time( NULL ) + 1;
1530 @brief For PCRUD: Determine whether the current user may access the current row.
1531 @param ctx Pointer to the method context.
1532 @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1533 @param obj Pointer to the row being potentially accessed.
1534 @return 1 if access is permitted, or 0 if it isn't.
1536 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1538 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1540 dbhandle = writehandle;
1542 // Figure out what class and method are involved
1543 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1544 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1547 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1548 if (rs_size_from_hash) {
1549 rs_size = *rs_size_from_hash;
1550 osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1554 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1555 // contexts we will do another lookup of the current row, even if we already have a
1556 // previously fetched row image, because the row image in hand may not include the
1557 // foreign key(s) that we need.
1559 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1560 // but they aren't implemented yet.
1563 if( *method_type == 's' || *method_type == 'i' ) {
1564 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1566 } else if( *method_type == 'u' || *method_type == 'd' ) {
1567 fetch = 1; // MUST go to the db for the object for update and delete
1570 // In retrieve or search ONLY we allow anon. Later perm checks will fail as they should,
1571 // in the face of a fake user but required permissions.
1573 if( *method_type == 'r' )
1576 // Get the appropriate permacrud entry from the IDL, depending on method type
1577 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1579 // No permacrud for this method type on this class
1581 growing_buffer* msg = buffer_init( 128 );
1584 "%s: %s on class %s has no permacrud IDL entry",
1586 osrfHashGet( method_metadata, "methodtype" ),
1587 osrfHashGet( class, "classname" )
1590 char* m = buffer_release( msg );
1591 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1592 "osrfMethodException", ctx->request, m );
1599 // Get the user id, and make sure the user is logged in
1600 const jsonObject* user = verifyUserPCRUDfull( ctx, anon_ok );
1602 return 0; // Not logged in or anon? No access.
1604 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1606 // Get a list of permissions from the permacrud entry.
1607 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1608 if( permission->size == 0 ) {
1611 "No permissions required for this action (class %s), passing through",
1612 osrfHashGet(class, "classname")
1617 // But, if there are perms and the user is anonymous ... FAIL
1621 // Build a list of org units that own the row. This is fairly convoluted because there
1622 // are several different ways that an org unit may own the row, as defined by the
1625 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1626 // identifying an owning org_unit..
1627 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1629 // Foreign context adds a layer of indirection. The row points to some other row that
1630 // an org unit may own. The "jump" attribute, if present, adds another layer of
1632 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1634 // The following string array stores the list of org units. (We don't have a thingie
1635 // for storing lists of integers, so we fake it with a list of strings.)
1636 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1638 const char* context_org = NULL;
1639 const char* pkey = NULL;
1640 jsonObject *param = NULL;
1641 const char* perm = NULL;
1645 const char* pkey_value = NULL;
1646 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1647 // If the global_required attribute is present and true, then the only owning
1648 // org unit is the root org unit, i.e. the one with no parent.
1649 osrfLogDebug( OSRF_LOG_MARK,
1650 "global-level permissions required, fetching top of the org tree" );
1652 // no need to check perms for org tree root retrieval
1653 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1654 // check for perm at top of org tree
1655 const char* org_tree_root_id = org_tree_root( ctx );
1656 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1658 if( org_tree_root_id ) {
1659 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1660 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1662 osrfStringArrayFree( context_org_array );
1667 // If the global_required attribute is absent or false, then we look for
1668 // local and/or foreign context. In order to find the relevant foreign
1669 // keys, we must either read the relevant row from the database, or look at
1670 // the image of the row that we already have in memory.
1672 // Even if we have an image of the row in memory, that image may not include the
1673 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1674 // of the row to make sure that we have what we need.
1676 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1677 "fetching context org ids" );
1679 pkey = osrfHashGet( class, "primarykey" );
1682 // There is no primary key, so we can't do a fresh lookup. Use the row
1683 // image that we already have. If it doesn't have everything we need, too bad.
1685 param = jsonObjectClone( obj );
1686 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1687 } else if( obj->classname ) {
1688 pkey_value = oilsFMGetStringConst( obj, pkey );
1690 param = jsonObjectClone( obj );
1691 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1694 pkey_value = jsonObjectGetString( obj );
1696 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1697 "of %s and retrieving from the database", pkey_value );
1701 // Fetch the row so that we can look at the foreign key(s)
1702 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1703 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1704 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1705 jsonObjectFree( _tmp_params );
1706 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1708 param = jsonObjectExtractIndex( _list, 0 );
1709 jsonObjectFree( _list );
1715 // The row doesn't exist. Complain, and deny access.
1716 osrfLogDebug( OSRF_LOG_MARK,
1717 "Object not found in the database with primary key %s of %s",
1720 growing_buffer* msg = buffer_init( 128 );
1723 "%s: no object found with primary key %s of %s",
1729 char* m = buffer_release( msg );
1730 osrfAppSessionStatus(
1732 OSRF_STATUS_INTERNALSERVERERROR,
1733 "osrfMethodException",
1742 if( local_context && local_context->size > 0 ) {
1743 // The IDL provides a list of column names for the foreign keys denoting
1744 // local context, i.e. columns identifying owing org units directly. Look up
1745 // the value of each one, and if it isn't null, add it to the list of org units.
1746 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1747 local_context->size );
1749 const char* lcontext = NULL;
1750 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1751 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1752 if( fkey_value ) { // if not null
1753 osrfStringArrayAdd( context_org_array, fkey_value );
1756 "adding class-local field %s (value: %s) to the context org list",
1758 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1764 if( foreign_context ) {
1765 unsigned long class_count = osrfHashGetCount( foreign_context );
1766 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1768 if( class_count > 0 ) {
1770 // The IDL provides a list of foreign key columns pointing to rows that
1771 // an org unit may own. Follow each link, identify the owning org unit,
1772 // and add it to the list.
1773 osrfHash* fcontext = NULL;
1774 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1775 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1776 // For each class to which a foreign key points:
1777 const char* class_name = osrfHashIteratorKey( class_itr );
1778 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1782 "%d foreign context fields(s) specified for class %s",
1783 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1787 // Get the name of the key field in the foreign table
1788 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1790 // Get the value of the foreign key pointing to the foreign table
1791 char* foreign_pkey_value =
1792 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1793 if( !foreign_pkey_value )
1794 continue; // Foreign key value is null; skip it
1796 // Look up the row to which the foreign key points
1797 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1799 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1800 jsonObject* _list = doFieldmapperSearch(
1801 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1802 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1804 jsonObject* _fparam = NULL;
1805 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1806 _fparam = jsonObjectExtractIndex( _list, 0 );
1808 jsonObjectFree( _tmp_params );
1809 jsonObjectFree( _list );
1811 // At this point _fparam either points to the row identified by the
1812 // foreign key, or it's NULL (no such row found).
1814 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1816 const char* bad_class = NULL; // For noting failed lookups
1818 bad_class = class_name; // Referenced row not found
1819 else if( jump_list ) {
1820 // Follow a chain of rows, linked by foreign keys, to find an owner
1821 const char* flink = NULL;
1823 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1824 // For each entry in the jump list. Each entry (i.e. flink) is
1825 // the name of a foreign key column in the current row.
1827 // From the IDL, get the linkage information for the next jump
1828 osrfHash* foreign_link_hash =
1829 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1831 // Get the class metadata for the class
1832 // to which the foreign key points
1833 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1834 osrfHashGet( foreign_link_hash, "class" ));
1836 // Get the name of the referenced key of that class
1837 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1839 // Get the value of the foreign key pointing to that class
1840 free( foreign_pkey_value );
1841 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1842 if( !foreign_pkey_value )
1843 break; // Foreign key is null; quit looking
1845 // Build a WHERE clause for the lookup
1846 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1849 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1850 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1851 _tmp_params, NULL, &err );
1852 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1854 // Get the resulting row
1855 jsonObjectFree( _fparam );
1856 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1857 _fparam = jsonObjectExtractIndex( _list, 0 );
1859 // Referenced row not found
1861 bad_class = osrfHashGet( foreign_link_hash, "class" );
1864 jsonObjectFree( _tmp_params );
1865 jsonObjectFree( _list );
1871 // We had a foreign key pointing to such-and-such a row, but then
1872 // we couldn't fetch that row. The data in the database are in an
1873 // inconsistent state; the database itself may even be corrupted.
1874 growing_buffer* msg = buffer_init( 128 );
1877 "%s: no object of class %s found with primary key %s of %s",
1881 foreign_pkey_value ? foreign_pkey_value : "(null)"
1884 char* m = buffer_release( msg );
1885 osrfAppSessionStatus(
1887 OSRF_STATUS_INTERNALSERVERERROR,
1888 "osrfMethodException",
1894 osrfHashIteratorFree( class_itr );
1895 free( foreign_pkey_value );
1896 jsonObjectFree( param );
1901 free( foreign_pkey_value );
1904 // Examine each context column of the foreign row,
1905 // and add its value to the list of org units.
1907 const char* foreign_field = NULL;
1908 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1909 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1910 osrfStringArrayAdd( context_org_array,
1911 oilsFMGetStringConst( _fparam, foreign_field ));
1912 osrfLogDebug( OSRF_LOG_MARK,
1913 "adding foreign class %s field %s (value: %s) "
1914 "to the context org list",
1917 osrfStringArrayGetString(
1918 context_org_array, context_org_array->size - 1 )
1922 jsonObjectFree( _fparam );
1926 osrfHashIteratorFree( class_itr );
1931 // If there is an owning_user attached to the action, we allow that user and users with
1932 // object perms on the object. CREATE can't use this. We only do this when we're not
1933 // ignoring object perms.
1934 char* owning_user_field = osrfHashGet( pcrud, "owning_user" );
1936 *method_type != 'c' &&
1937 (!str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) || // Always honor
1940 if (owning_user_field) { // see if we can short-cut by comparing the owner to the requestor
1942 if (!param) { // We didn't get it during the context lookup
1943 pkey = osrfHashGet( class, "primarykey" );
1946 // There is no primary key, so we can't do a fresh lookup. Use the row
1947 // image that we already have. If it doesn't have everything we need, too bad.
1949 param = jsonObjectClone( obj );
1950 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1951 } else if( obj->classname ) {
1952 pkey_value = oilsFMGetStringConst( obj, pkey );
1954 param = jsonObjectClone( obj );
1955 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1958 pkey_value = jsonObjectGetString( obj );
1960 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1961 "of %s and retrieving from the database", pkey_value );
1965 // Fetch the row so that we can look at the foreign key(s)
1966 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1967 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1968 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1969 jsonObjectFree( _tmp_params );
1970 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1972 param = jsonObjectExtractIndex( _list, 0 );
1973 jsonObjectFree( _list );
1978 // The row doesn't exist. Complain, and deny access.
1979 osrfLogDebug( OSRF_LOG_MARK,
1980 "Object not found in the database with primary key %s of %s",
1983 growing_buffer* msg = buffer_init( 128 );
1986 "%s: no object found with primary key %s of %s",
1992 char* m = buffer_release( msg );
1993 osrfAppSessionStatus(
1995 OSRF_STATUS_INTERNALSERVERERROR,
1996 "osrfMethodException",
2005 int ownerid = atoi( oilsFMGetStringConst( param, owning_user_field ) );
2007 // Allow the owner to do whatever
2008 if (ownerid == userid)
2015 (perm = osrfStringArrayGetString(permission, i++)) &&
2016 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms"))
2022 "Checking object permission [%s] for user %d "
2023 "on object %s (class %s)",
2027 osrfHashGet( class, "classname" )
2030 result = dbi_conn_queryf(
2032 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s') AS has_perm;",
2035 osrfHashGet( class, "classname" ),
2042 "Received a result for object permission [%s] "
2043 "for user %d on object %s (class %s)",
2047 osrfHashGet( class, "classname" )
2050 if( dbi_result_first_row( result )) {
2051 jsonObject* return_val = oilsMakeJSONFromResult( result );
2052 const char* has_perm = jsonObjectGetString(
2053 jsonObjectGetKeyConst( return_val, "has_perm" ));
2057 "Status of object permission [%s] for user %d "
2058 "on object %s (class %s) is %s",
2062 osrfHashGet(class, "classname"),
2066 if( *has_perm == 't' )
2068 jsonObjectFree( return_val );
2071 dbi_result_free( result );
2076 int errnum = dbi_conn_error( writehandle, &msg );
2077 osrfLogWarning( OSRF_LOG_MARK,
2078 "Unable to call check object permissions: %d, %s",
2079 errnum, msg ? msg : "(No description available)" );
2080 if( !oilsIsDBConnected( writehandle ))
2081 osrfAppSessionPanic( ctx->session );
2086 // For every combination of permission and context org unit: call a stored procedure
2087 // to determine if the user has this permission in the context of this org unit.
2088 // If the answer is yes at any point, then we're done, and the user has permission.
2089 // In other words permissions are additive.
2091 while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
2094 osrfStringArray* pcache = NULL;
2095 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
2096 pcache = getPermLocationCache(ctx, perm);
2099 pcache = osrfNewStringArray(0);
2101 result = dbi_conn_queryf(
2103 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
2111 "Received a result for permission [%s] for user %d",
2116 if( dbi_result_first_row( result )) {
2118 jsonObject* return_val = oilsMakeJSONFromResult( result );
2119 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
2120 jsonObjectFree( return_val );
2121 } while( dbi_result_next_row( result ));
2123 setPermLocationCache(ctx, perm, pcache);
2126 dbi_result_free( result );
2132 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
2134 if (rs_size > perm_at_threshold) {
2135 if (osrfStringArrayContains( pcache, context_org )) {
2143 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
2145 !str_is_true( osrfHashGet(pcrud, "global_required") ) ||
2146 osrfHashGet(pcrud, "owning_user")
2151 "Checking object permission [%s] for user %d "
2152 "on object %s (class %s) at org %d",
2156 osrfHashGet( class, "classname" ),
2160 result = dbi_conn_queryf(
2162 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
2165 osrfHashGet( class, "classname" ),
2173 "Received a result for object permission [%s] "
2174 "for user %d on object %s (class %s) at org %d",
2178 osrfHashGet( class, "classname" ),
2182 if( dbi_result_first_row( result )) {
2183 jsonObject* return_val = oilsMakeJSONFromResult( result );
2184 const char* has_perm = jsonObjectGetString(
2185 jsonObjectGetKeyConst( return_val, "has_perm" ));
2189 "Status of object permission [%s] for user %d "
2190 "on object %s (class %s) at org %d is %s",
2194 osrfHashGet(class, "classname"),
2199 if( *has_perm == 't' )
2201 jsonObjectFree( return_val );
2204 dbi_result_free( result );
2209 int errnum = dbi_conn_error( writehandle, &msg );
2210 osrfLogWarning( OSRF_LOG_MARK,
2211 "Unable to call check object permissions: %d, %s",
2212 errnum, msg ? msg : "(No description available)" );
2213 if( !oilsIsDBConnected( writehandle ))
2214 osrfAppSessionPanic( ctx->session );
2218 if (rs_size > perm_at_threshold) break;
2220 osrfLogDebug( OSRF_LOG_MARK,
2221 "Checking non-object permission [%s] for user %d at org %d",
2222 perm, userid, atoi(context_org) );
2223 result = dbi_conn_queryf(
2225 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
2232 osrfLogDebug( OSRF_LOG_MARK,
2233 "Received a result for permission [%s] for user %d at org %d",
2234 perm, userid, atoi( context_org ));
2235 if( dbi_result_first_row( result )) {
2236 jsonObject* return_val = oilsMakeJSONFromResult( result );
2237 const char* has_perm = jsonObjectGetString(
2238 jsonObjectGetKeyConst( return_val, "has_perm" ));
2239 osrfLogDebug( OSRF_LOG_MARK,
2240 "Status of permission [%s] for user %d at org %d is [%s]",
2241 perm, userid, atoi( context_org ), has_perm );
2242 if( *has_perm == 't' )
2244 jsonObjectFree( return_val );
2247 dbi_result_free( result );
2252 int errnum = dbi_conn_error( writehandle, &msg );
2253 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
2254 errnum, msg ? msg : "(No description available)" );
2255 if( !oilsIsDBConnected( writehandle ))
2256 osrfAppSessionPanic( ctx->session );
2265 osrfStringArrayFree( context_org_array );
2271 @brief Look up the root of the org_unit tree.
2272 @param ctx Pointer to the method context.
2273 @return The id of the root org unit, as a character string.
2275 Query actor.org_unit where parent_ou is null, and return the id as a string.
2277 This function assumes that there is only one root org unit, i.e. that we
2278 have a single tree, not a forest.
2280 The calling code is responsible for freeing the returned string.
2282 static const char* org_tree_root( osrfMethodContext* ctx ) {
2284 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
2285 static time_t last_lookup_time = 0;
2286 time_t current_time = time( NULL );
2288 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2289 // We successfully looked this up less than an hour ago.
2290 // It's not likely to have changed since then.
2291 return strdup( cached_root_id );
2293 last_lookup_time = current_time;
2296 jsonObject* where_clause = single_hash( "parent_ou", NULL );
2297 jsonObject* result = doFieldmapperSearch(
2298 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2299 jsonObjectFree( where_clause );
2301 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2304 jsonObjectFree( result );
2306 growing_buffer* msg = buffer_init( 128 );
2307 OSRF_BUFFER_ADD( msg, modulename );
2308 OSRF_BUFFER_ADD( msg,
2309 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2311 char* m = buffer_release( msg );
2312 osrfAppSessionStatus( ctx->session,
2313 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2316 cached_root_id[ 0 ] = '\0';
2320 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2321 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2323 strcpy( cached_root_id, root_org_unit_id );
2324 jsonObjectFree( result );
2325 return cached_root_id;
2329 @brief Create a JSON_HASH with a single key/value pair.
2330 @param key The key of the key/value pair.
2331 @param value the value of the key/value pair.
2332 @return Pointer to a newly created jsonObject of type JSON_HASH.
2334 The value of the key/value is either a string or (if @a value is NULL) a null.
2336 static jsonObject* single_hash( const char* key, const char* value ) {
2338 if( ! key ) key = "";
2340 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2341 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2346 int doCreate( osrfMethodContext* ctx ) {
2347 if(osrfMethodVerifyContext( ctx )) {
2348 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2353 timeout_needs_resetting = 1;
2355 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2356 jsonObject* target = NULL;
2357 jsonObject* options = NULL;
2359 if( enforce_pcrud ) {
2360 target = jsonObjectGetIndex( ctx->params, 1 );
2361 options = jsonObjectGetIndex( ctx->params, 2 );
2363 target = jsonObjectGetIndex( ctx->params, 0 );
2364 options = jsonObjectGetIndex( ctx->params, 1 );
2367 if( !verifyObjectClass( ctx, target )) {
2368 osrfAppRespondComplete( ctx, NULL );
2372 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2374 const char* trans_id = getXactId( ctx );
2376 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2378 osrfAppSessionStatus(
2380 OSRF_STATUS_BADREQUEST,
2381 "osrfMethodException",
2383 "No active transaction -- required for CREATE"
2385 osrfAppRespondComplete( ctx, NULL );
2389 // The following test is harmless but redundant. If a class is
2390 // readonly, we don't register a create method for it.
2391 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2392 osrfAppSessionStatus(
2394 OSRF_STATUS_BADREQUEST,
2395 "osrfMethodException",
2397 "Cannot INSERT readonly class"
2399 osrfAppRespondComplete( ctx, NULL );
2403 // Set the last_xact_id
2404 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2406 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2407 trans_id, target->classname, index);
2408 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2411 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2413 dbhandle = writehandle;
2415 osrfHash* fields = osrfHashGet( meta, "fields" );
2416 char* pkey = osrfHashGet( meta, "primarykey" );
2417 char* seq = osrfHashGet( meta, "sequence" );
2419 growing_buffer* table_buf = buffer_init( 128 );
2420 growing_buffer* col_buf = buffer_init( 128 );
2421 growing_buffer* val_buf = buffer_init( 128 );
2423 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2424 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2425 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2426 buffer_add( val_buf,"VALUES (" );
2430 osrfHash* field = NULL;
2431 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2432 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2434 const char* field_name = osrfHashIteratorKey( field_itr );
2436 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2439 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2442 if( field_object && field_object->classname ) {
2443 value = oilsFMGetString(
2445 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2447 } else if( field_object && JSON_BOOL == field_object->type ) {
2448 if( jsonBoolIsTrue( field_object ) )
2449 value = strdup( "t" );
2451 value = strdup( "f" );
2453 value = jsonObjectToSimpleString( field_object );
2459 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2460 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2463 buffer_add( col_buf, field_name );
2465 if( !field_object || field_object->type == JSON_NULL ) {
2466 buffer_add( val_buf, "DEFAULT" );
2468 } else if( !strcmp( get_primitive( field ), "number" )) {
2469 const char* numtype = get_datatype( field );
2470 if( !strcmp( numtype, "INT8" )) {
2471 buffer_fadd( val_buf, "%lld", atoll( value ));
2473 } else if( !strcmp( numtype, "INT" )) {
2474 buffer_fadd( val_buf, "%d", atoi( value ));
2476 } else if( !strcmp( numtype, "NUMERIC" )) {
2477 buffer_fadd( val_buf, "%f", atof( value ));
2480 if( dbi_conn_quote_string( writehandle, &value )) {
2481 OSRF_BUFFER_ADD( val_buf, value );
2484 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2485 osrfAppSessionStatus(
2487 OSRF_STATUS_INTERNALSERVERERROR,
2488 "osrfMethodException",
2490 "Error quoting string -- please see the error log for more details"
2493 buffer_free( table_buf );
2494 buffer_free( col_buf );
2495 buffer_free( val_buf );
2496 osrfAppRespondComplete( ctx, NULL );
2504 osrfHashIteratorFree( field_itr );
2506 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2507 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2509 char* table_str = buffer_release( table_buf );
2510 char* col_str = buffer_release( col_buf );
2511 char* val_str = buffer_release( val_buf );
2512 growing_buffer* sql = buffer_init( 128 );
2513 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2518 char* query = buffer_release( sql );
2520 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2522 jsonObject* obj = NULL;
2525 dbi_result result = dbi_conn_query( writehandle, query );
2527 obj = jsonNewObject( NULL );
2529 int errnum = dbi_conn_error( writehandle, &msg );
2532 "%s ERROR inserting %s object using query [%s]: %d %s",
2534 osrfHashGet(meta, "fieldmapper"),
2537 msg ? msg : "(No description available)"
2539 osrfAppSessionStatus(
2541 OSRF_STATUS_INTERNALSERVERERROR,
2542 "osrfMethodException",
2544 "INSERT error -- please see the error log for more details"
2546 if( !oilsIsDBConnected( writehandle ))
2547 osrfAppSessionPanic( ctx->session );
2550 dbi_result_free( result );
2552 char* id = oilsFMGetString( target, pkey );
2554 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2555 growing_buffer* _id = buffer_init( 10 );
2556 buffer_fadd( _id, "%lld", new_id );
2557 id = buffer_release( _id );
2560 // Find quietness specification, if present
2561 const char* quiet_str = NULL;
2563 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2565 quiet_str = jsonObjectGetString( quiet_obj );
2568 if( str_is_true( quiet_str )) { // if quietness is specified
2569 obj = jsonNewObject( id );
2573 // Fetch the row that we just inserted, so that we can return it to the client
2574 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2575 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2578 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2582 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2584 jsonObjectFree( list );
2585 jsonObjectFree( where_clause );
2592 osrfAppRespondComplete( ctx, obj );
2593 jsonObjectFree( obj );
2598 @brief Implement the retrieve method.
2599 @param ctx Pointer to the method context.
2600 @param err Pointer through which to return an error code.
2601 @return If successful, a pointer to the result to be returned to the client;
2604 From the method's class, fetch a row with a specified value in the primary key. This
2605 method relies on the database design convention that a primary key consists of a single
2609 - authkey (PCRUD only)
2610 - value of the primary key for the desired row, for building the WHERE clause
2611 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2613 Return to client: One row from the query.
2615 int doRetrieve( osrfMethodContext* ctx ) {
2616 if(osrfMethodVerifyContext( ctx )) {
2617 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2622 timeout_needs_resetting = 1;
2627 if( enforce_pcrud ) {
2632 // Get the class metadata
2633 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2635 // Get the value of the primary key, from a method parameter
2636 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2640 "%s retrieving %s object with primary key value of %s",
2642 osrfHashGet( class_def, "fieldmapper" ),
2643 jsonObjectGetString( id_obj )
2646 // Build a WHERE clause based on the key value
2647 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2650 osrfHashGet( class_def, "primarykey" ), // name of key column
2651 jsonObjectClone( id_obj ) // value of key column
2654 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2658 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2660 jsonObjectFree( where_clause );
2662 osrfAppRespondComplete( ctx, NULL );
2666 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2667 jsonObjectFree( list );
2669 if( enforce_pcrud ) {
2670 // no result, skip this entirely
2671 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2672 jsonObjectFree( obj );
2674 growing_buffer* msg = buffer_init( 128 );
2675 OSRF_BUFFER_ADD( msg, modulename );
2676 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2678 char* m = buffer_release( msg );
2679 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2683 osrfAppRespondComplete( ctx, NULL );
2688 // doFieldmapperSearch() now does the responding for us
2689 //osrfAppRespondComplete( ctx, obj );
2690 osrfAppRespondComplete( ctx, NULL );
2692 jsonObjectFree( obj );
2697 @brief Translate a numeric value to a string representation for the database.
2698 @param field Pointer to the IDL field definition.
2699 @param value Pointer to a jsonObject holding the value of a field.
2700 @return Pointer to a newly allocated string.
2702 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2703 its contents are numeric. A non-numeric string is likely to result in invalid SQL.
2705 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2707 The calling code is responsible for freeing the resulting string by calling free().
2709 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2710 growing_buffer* val_buf = buffer_init( 32 );
2712 // If the value is a number and the DB field is numeric, no quotes needed
2713 if( value->type == JSON_NUMBER && !strcmp( get_primitive( field ), "number") ) {
2714 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2716 // Presumably this was really intended to be a string, so quote it
2717 char* str = jsonObjectToSimpleString( value );
2718 if( dbi_conn_quote_string( dbhandle, &str )) {
2719 OSRF_BUFFER_ADD( val_buf, str );
2722 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2724 buffer_free( val_buf );
2729 return buffer_release( val_buf );
2732 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2733 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2735 char* field_transform = searchFieldTransform( class_alias, field, node );
2736 if( ! field_transform )
2740 char* in_list = searchINList(field, node, op, ctx);
2744 growing_buffer* sql_buf = buffer_init( 32 );
2745 buffer_add( sql_buf, field_transform );
2748 buffer_add( sql_buf, " IN (" );
2749 } else if( !strcasecmp( op,"not in" )) {
2750 buffer_add( sql_buf, " NOT IN (" );
2752 buffer_add( sql_buf, " IN (" );
2755 buffer_add( sql_buf, in_list);
2756 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2758 free(field_transform);
2761 return buffer_release( sql_buf );
2764 static char* searchINList( osrfHash* field,
2765 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2766 growing_buffer* sql_buf = buffer_init( 32 );
2768 const jsonObject* local_node = node;
2769 if( local_node->type == JSON_HASH ) { // may be the case that the node tranforms the field
2770 // if so, grab the "value" property
2771 local_node = jsonObjectGetKeyConst( node, "value" );
2772 if (!local_node) local_node = node;
2775 if( local_node->type == JSON_HASH ) {
2776 // subquery predicate
2778 char* subpred = buildQuery( ctx, (jsonObject*) local_node, SUBSELECT );
2780 buffer_free( sql_buf );
2784 buffer_add( sql_buf, subpred );
2787 } else if( local_node->type == JSON_ARRAY ) {
2788 // literal value list
2789 int in_item_index = 0;
2790 int in_item_first = 1;
2791 const jsonObject* in_item;
2792 while( (in_item = jsonObjectGetIndex( local_node, in_item_index++ )) ) {
2797 buffer_add( sql_buf, ", " );
2800 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2801 osrfLogError( OSRF_LOG_MARK,
2802 "%s: Expected string or number within IN list; found %s",
2803 modulename, json_type( in_item->type ) );
2804 buffer_free( sql_buf );
2808 // Append the literal value -- quoted if not a number
2809 if( JSON_NUMBER == in_item->type ) {
2810 char* val = jsonNumberToDBString( field, in_item );
2811 OSRF_BUFFER_ADD( sql_buf, val );
2814 } else if( !strcmp( get_primitive( field ), "number" )) {
2815 char* val = jsonNumberToDBString( field, in_item );
2816 OSRF_BUFFER_ADD( sql_buf, val );
2820 char* key_string = jsonObjectToSimpleString( in_item );
2821 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2822 OSRF_BUFFER_ADD( sql_buf, key_string );
2825 osrfLogError( OSRF_LOG_MARK,
2826 "%s: Error quoting key string [%s]", modulename, key_string );
2828 buffer_free( sql_buf );
2834 if( in_item_first ) {
2835 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2836 buffer_free( sql_buf );
2840 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2841 modulename, json_type( local_node->type ));
2842 buffer_free( sql_buf );
2846 return buffer_release( sql_buf );
2849 // Receive a JSON_ARRAY representing a function call. The first
2850 // entry in the array is the function name. The rest are parameters.
2851 static char* searchValueTransform( const jsonObject* array ) {
2853 if( array->size < 1 ) {
2854 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2858 // Get the function name
2859 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2860 if( func_item->type != JSON_STRING ) {
2861 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2862 modulename, json_type( func_item->type ));
2866 growing_buffer* sql_buf = buffer_init( 32 );
2868 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2869 OSRF_BUFFER_ADD( sql_buf, "( " );
2871 // Get the parameters
2872 int func_item_index = 1; // We already grabbed the zeroth entry
2873 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2875 // Add a separator comma, if we need one
2876 if( func_item_index > 2 )
2877 buffer_add( sql_buf, ", " );
2879 // Add the current parameter
2880 if( func_item->type == JSON_NULL ) {
2881 buffer_add( sql_buf, "NULL" );
2883 if( func_item->type == JSON_BOOL ) {
2884 if( jsonBoolIsTrue(func_item) ) {
2885 buffer_add( sql_buf, "TRUE" );
2887 buffer_add( sql_buf, "FALSE" );
2890 char* val = jsonObjectToSimpleString( func_item );
2891 if( dbi_conn_quote_string( dbhandle, &val )) {
2892 OSRF_BUFFER_ADD( sql_buf, val );
2895 osrfLogError( OSRF_LOG_MARK,
2896 "%s: Error quoting key string [%s]", modulename, val );
2897 buffer_free( sql_buf );
2905 buffer_add( sql_buf, " )" );
2907 return buffer_release( sql_buf );
2910 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2911 const jsonObject* node, const char* op ) {
2913 if( ! is_good_operator( op ) ) {
2914 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2918 char* val = searchValueTransform( node );
2922 const char* right_percent = "";
2923 const char* real_op = op;
2925 if( !strcasecmp( op, "startwith") ) {
2927 right_percent = "|| '%'";
2930 growing_buffer* sql_buf = buffer_init( 32 );
2933 "\"%s\".%s %s %s%s",
2935 osrfHashGet( field, "name" ),
2943 return buffer_release( sql_buf );
2946 // class_alias is a class name or other table alias
2947 // field is a field definition as stored in the IDL
2948 // node comes from the method parameter, and may represent an entry in the SELECT list
2949 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2950 const jsonObject* node ) {
2951 growing_buffer* sql_buf = buffer_init( 32 );
2953 if( node->type == JSON_HASH ) {
2954 const char* field_transform = jsonObjectGetString(
2955 jsonObjectGetKeyConst( node, "transform" ) );
2956 const char* transform_subcolumn = jsonObjectGetString(
2957 jsonObjectGetKeyConst( node, "result_field" ) );
2959 if( field_transform && transform_subcolumn ) {
2960 if( ! is_identifier( transform_subcolumn ) ) {
2961 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2962 modulename, transform_subcolumn );
2963 buffer_free( sql_buf );
2966 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2969 if( field_transform ) {
2971 if( ! is_identifier( field_transform ) ) {
2972 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2973 modulename, field_transform );
2974 buffer_free( sql_buf );
2978 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2979 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2980 field_transform, class_alias, osrfHashGet( field, "name" ));
2982 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2983 field_transform, class_alias, osrfHashGet( field, "name" ));
2986 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2989 if( array->type != JSON_ARRAY ) {
2990 osrfLogError( OSRF_LOG_MARK,
2991 "%s: Expected JSON_ARRAY for function params; found %s",
2992 modulename, json_type( array->type ) );
2993 buffer_free( sql_buf );
2996 int func_item_index = 0;
2997 jsonObject* func_item;
2998 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
3000 char* val = jsonObjectToSimpleString( func_item );
3003 buffer_add( sql_buf, ",NULL" );
3004 } else if( dbi_conn_quote_string( dbhandle, &val )) {
3005 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
3006 OSRF_BUFFER_ADD( sql_buf, val );
3008 osrfLogError( OSRF_LOG_MARK,
3009 "%s: Error quoting key string [%s]", modulename, val );
3011 buffer_free( sql_buf );
3018 buffer_add( sql_buf, ")" );
3020 if( transform_subcolumn )
3021 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
3024 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
3027 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
3031 return buffer_release( sql_buf );
3034 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
3035 const jsonObject* node, const char* op ) {
3037 if( ! is_good_operator( op ) ) {
3038 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
3042 char* field_transform = searchFieldTransform( class_info->alias, field, node );
3043 if( ! field_transform )
3046 int extra_parens = 0; // boolean
3048 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
3050 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
3052 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
3054 free( field_transform );
3058 } else if( value_obj->type == JSON_ARRAY ) {
3059 value = searchValueTransform( value_obj );
3061 osrfLogError( OSRF_LOG_MARK,
3062 "%s: Error building value transform for field transform", modulename );
3063 free( field_transform );
3066 } else if( value_obj->type == JSON_HASH ) {
3067 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
3069 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
3071 free( field_transform );
3075 } else if( value_obj->type == JSON_NUMBER ) {
3076 value = jsonNumberToDBString( field, value_obj );
3077 } else if( value_obj->type == JSON_NULL ) {
3078 osrfLogError( OSRF_LOG_MARK,
3079 "%s: Error building predicate for field transform: null value", modulename );
3080 free( field_transform );
3082 } else if( value_obj->type == JSON_BOOL ) {
3083 osrfLogError( OSRF_LOG_MARK,
3084 "%s: Error building predicate for field transform: boolean value", modulename );
3085 free( field_transform );
3088 if( !strcmp( get_primitive( field ), "number") ) {
3089 value = jsonNumberToDBString( field, value_obj );
3091 value = jsonObjectToSimpleString( value_obj );
3092 if( !dbi_conn_quote_string( dbhandle, &value )) {
3093 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3094 modulename, value );
3096 free( field_transform );
3102 const char* left_parens = "";
3103 const char* right_parens = "";
3105 if( extra_parens ) {
3110 const char* right_percent = "";
3111 const char* real_op = op;
3113 if( !strcasecmp( op, "startwith") ) {
3115 right_percent = "|| '%'";
3118 growing_buffer* sql_buf = buffer_init( 32 );
3122 "%s%s %s %s %s%s %s%s",
3134 free( field_transform );
3136 return buffer_release( sql_buf );
3139 static char* searchSimplePredicate( const char* op, const char* class_alias,
3140 osrfHash* field, const jsonObject* node ) {
3142 if( ! is_good_operator( op ) ) {
3143 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
3149 // Get the value to which we are comparing the specified column
3150 if( node->type != JSON_NULL ) {
3151 if( node->type == JSON_NUMBER ) {
3152 val = jsonNumberToDBString( field, node );
3153 } else if( !strcmp( get_primitive( field ), "number" ) ) {
3154 val = jsonNumberToDBString( field, node );
3156 val = jsonObjectToSimpleString( node );
3161 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
3162 // Value is not numeric; enclose it in quotes
3163 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
3164 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3171 // Compare to a null value
3172 val = strdup( "NULL" );
3173 if( strcmp( op, "=" ))
3179 const char* right_percent = "";
3180 const char* real_op = op;
3182 if( !strcasecmp( op, "startwith") ) {
3184 right_percent = "|| '%'";
3187 growing_buffer* sql_buf = buffer_init( 32 );
3188 buffer_fadd( sql_buf, "\"%s\".%s %s %s%s", class_alias, osrfHashGet(field, "name"), real_op, val, right_percent );
3189 char* pred = buffer_release( sql_buf );
3196 static char* searchBETWEENRange( osrfHash* field, const jsonObject* node ) {
3198 const jsonObject* local_node = node;
3199 if( node->type == JSON_HASH ) { // will be the case if the node tranforms the field
3200 local_node = jsonObjectGetKeyConst( node, "value" );
3201 if (!local_node) local_node = node;
3204 const jsonObject* x_node = jsonObjectGetIndex( local_node, 0 );
3205 const jsonObject* y_node = jsonObjectGetIndex( local_node, 1 );
3207 if( NULL == y_node ) {
3208 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
3210 } else if( NULL != jsonObjectGetIndex( local_node, 2 ) ) {
3211 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
3218 if( !strcmp( get_primitive( field ), "number") ) {
3219 x_string = jsonNumberToDBString( field, x_node );
3220 y_string = jsonNumberToDBString( field, y_node );
3223 x_string = jsonObjectToSimpleString( x_node );
3224 y_string = jsonObjectToSimpleString( y_node );
3225 if( !(dbi_conn_quote_string( dbhandle, &x_string )
3226 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
3227 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
3228 modulename, x_string, y_string );
3235 growing_buffer* sql_buf = buffer_init( 32 );
3236 buffer_fadd( sql_buf, "%s AND %s", x_string, y_string );
3240 return buffer_release( sql_buf );
3243 static char* searchBETWEENPredicate( const char* class_alias,
3244 osrfHash* field, const jsonObject* node ) {
3246 char* field_transform = searchFieldTransform( class_alias, field, node );
3247 if( ! field_transform )
3250 char* between_range = searchBETWEENRange(field, node);
3252 if( NULL == between_range )
3255 growing_buffer* sql_buf = buffer_init( 32 );
3256 buffer_fadd( sql_buf, "%s BETWEEN %s", field_transform, between_range);
3258 free(field_transform);
3259 free(between_range);
3261 return buffer_release( sql_buf );
3264 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
3265 jsonObject* node, osrfMethodContext* ctx ) {
3268 if( node->type == JSON_ARRAY ) { // equality IN search
3269 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
3270 } else if( node->type == JSON_HASH ) { // other search
3271 jsonIterator* pred_itr = jsonNewIterator( node );
3272 if( !jsonIteratorHasNext( pred_itr ) ) {
3273 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
3274 modulename, osrfHashGet(field, "name" ));
3276 jsonObject* pred_node = jsonIteratorNext( pred_itr );
3278 // Verify that there are no additional predicates
3279 if( jsonIteratorHasNext( pred_itr ) ) {
3280 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
3281 modulename, osrfHashGet(field, "name" ));
3282 } else if( !(strcasecmp( pred_itr->key,"between" )) ) {
3283 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
3284 } else if( !(strcasecmp( pred_itr->key,"in" ))
3285 || !(strcasecmp( pred_itr->key,"not in" )) ) {
3286 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
3287 } else if( pred_node->type == JSON_ARRAY ) {
3288 pred = searchFunctionPredicate(
3289 class_info->alias, field, pred_node, pred_itr->key );
3290 } else if( pred_node->type == JSON_HASH ) {
3291 pred = searchFieldTransformPredicate(
3292 class_info, field, pred_node, pred_itr->key );
3294 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
3297 jsonIteratorFree( pred_itr );
3299 } else if( node->type == JSON_NULL ) { // IS NULL search
3300 growing_buffer* _p = buffer_init( 64 );
3303 "\"%s\".%s IS NULL",
3305 osrfHashGet( field, "name" )
3307 pred = buffer_release( _p );
3308 } else { // equality search
3309 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3328 field : call_number,
3342 Or, to specify join order:
3345 {mrd:{field:'record', type:'inner'}},
3346 {acn:{field:'record', type:'left'}}
3351 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3353 jsonObject* working_hash;
3354 jsonObject* freeable_hash = NULL;
3356 jsonObject* working_array;
3357 jsonObject* freeable_array = NULL;
3359 if( join_hash->type == JSON_ARRAY ) {
3360 working_array = (jsonObject*)join_hash;
3362 working_array = jsonNewObjectType( JSON_ARRAY );
3364 if( join_hash->type == JSON_HASH ) {
3365 working_hash = (jsonObject*)join_hash;
3366 } else if( join_hash->type == JSON_STRING ) {
3367 freeable_array = working_array;
3368 // turn it into a JSON_HASH by creating a wrapper
3369 // around a copy of the original
3370 const char* _tmp = jsonObjectGetString( join_hash );
3371 freeable_hash = jsonNewObjectType( JSON_HASH );
3372 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3373 working_hash = freeable_hash;
3377 "%s: JOIN failed; expected JSON object type not found",
3383 jsonObjectPush( working_array, working_hash );
3386 growing_buffer* join_buf = buffer_init( 128 );
3387 const char* leftclass = left_info->class_name;
3389 unsigned long order_idx = 0;
3390 while(( working_hash = jsonObjectGetIndex( working_array, order_idx++ ) )) {
3392 jsonObject* freeable_subhash = NULL;
3393 if( working_hash->type == JSON_STRING ) {
3394 // turn it into a JSON_HASH by creating a wrapper
3395 // around a copy of the original
3396 const char* _inner_tmp = jsonObjectGetString( working_hash );
3397 freeable_subhash = jsonNewObjectType( JSON_HASH );
3398 jsonObjectSetKey( freeable_subhash, _inner_tmp, NULL );
3399 working_hash = freeable_subhash;
3402 jsonObject* snode = NULL;
3403 jsonIterator* search_itr = jsonNewIterator( working_hash );
3405 while ( (snode = jsonIteratorNext( search_itr )) ) {
3406 const char* right_alias = search_itr->key;
3408 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3410 class = right_alias;
3412 const ClassInfo* right_info = add_joined_class( right_alias, class );
3416 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3420 jsonIteratorFree( search_itr );
3421 buffer_free( join_buf );
3422 if( freeable_subhash )
3423 jsonObjectFree( freeable_subhash );
3425 jsonObjectFree( freeable_hash );
3426 if( freeable_array )
3427 jsonObjectFree( freeable_array );
3430 osrfHash* links = right_info->links;
3431 const char* table = right_info->source_def;
3433 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3434 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3436 if( field && !fkey ) {
3437 // Look up the corresponding join column in the IDL.
3438 // The link must be defined in the child table,
3439 // and point to the right parent table.
3440 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3441 const char* reltype = NULL;
3442 const char* other_class = NULL;
3443 reltype = osrfHashGet( idl_link, "reltype" );
3444 if( reltype && strcmp( reltype, "has_many" ) )
3445 other_class = osrfHashGet( idl_link, "class" );
3446 if( other_class && !strcmp( other_class, leftclass ) )
3447 fkey = osrfHashGet( idl_link, "key" );
3451 "%s: JOIN failed. No link defined from %s.%s to %s",
3457 buffer_free( join_buf );
3458 if( freeable_subhash )
3459 jsonObjectFree( freeable_subhash );
3461 jsonObjectFree( freeable_hash );
3462 if( freeable_array )
3463 jsonObjectFree( freeable_array );
3464 jsonIteratorFree( search_itr );
3468 } else if( !field && fkey ) {
3469 // Look up the corresponding join column in the IDL.
3470 // The link must be defined in the child table,
3471 // and point to the right parent table.
3472 osrfHash* left_links = left_info->links;
3473 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3474 const char* reltype = NULL;
3475 const char* other_class = NULL;
3476 reltype = osrfHashGet( idl_link, "reltype" );
3477 if( reltype && strcmp( reltype, "has_many" ) )
3478 other_class = osrfHashGet( idl_link, "class" );
3479 if( other_class && !strcmp( other_class, class ) )
3480 field = osrfHashGet( idl_link, "key" );
3484 "%s: JOIN failed. No link defined from %s.%s to %s",
3490 buffer_free( join_buf );
3491 if( freeable_subhash )
3492 jsonObjectFree( freeable_subhash );
3494 jsonObjectFree( freeable_hash );
3495 if( freeable_array )
3496 jsonObjectFree( freeable_array );
3497 jsonIteratorFree( search_itr );
3501 } else if( !field && !fkey ) {
3502 osrfHash* left_links = left_info->links;
3504 // For each link defined for the left class:
3505 // see if the link references the joined class
3506 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3507 osrfHash* curr_link = NULL;
3508 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3509 const char* other_class = osrfHashGet( curr_link, "class" );
3510 if( other_class && !strcmp( other_class, class ) ) {
3512 // In the IDL, the parent class doesn't always know then names of the child
3513 // columns that are pointing to it, so don't use that end of the link
3514 const char* reltype = osrfHashGet( curr_link, "reltype" );
3515 if( reltype && strcmp( reltype, "has_many" ) ) {
3516 // Found a link between the classes
3517 fkey = osrfHashIteratorKey( itr );
3518 field = osrfHashGet( curr_link, "key" );
3523 osrfHashIteratorFree( itr );
3525 if( !field || !fkey ) {
3526 // Do another such search, with the classes reversed
3528 // For each link defined for the joined class:
3529 // see if the link references the left class
3530 osrfHashIterator* itr = osrfNewHashIterator( links );
3531 osrfHash* curr_link = NULL;
3532 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3533 const char* other_class = osrfHashGet( curr_link, "class" );
3534 if( other_class && !strcmp( other_class, leftclass ) ) {
3536 // In the IDL, the parent class doesn't know then names of the child
3537 // columns that are pointing to it, so don't use that end of the link
3538 const char* reltype = osrfHashGet( curr_link, "reltype" );
3539 if( reltype && strcmp( reltype, "has_many" ) ) {
3540 // Found a link between the classes
3541 field = osrfHashIteratorKey( itr );
3542 fkey = osrfHashGet( curr_link, "key" );
3547 osrfHashIteratorFree( itr );
3550 if( !field || !fkey ) {
3553 "%s: JOIN failed. No link defined between %s and %s",
3558 buffer_free( join_buf );
3559 if( freeable_subhash )
3560 jsonObjectFree( freeable_subhash );
3562 jsonObjectFree( freeable_hash );
3563 if( freeable_array )
3564 jsonObjectFree( freeable_array );
3565 jsonIteratorFree( search_itr );
3570 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3572 if( !strcasecmp( type,"left" )) {
3573 buffer_add( join_buf, " LEFT JOIN" );
3574 } else if( !strcasecmp( type,"right" )) {
3575 buffer_add( join_buf, " RIGHT JOIN" );
3576 } else if( !strcasecmp( type,"full" )) {
3577 buffer_add( join_buf, " FULL JOIN" );
3579 buffer_add( join_buf, " INNER JOIN" );
3582 buffer_add( join_buf, " INNER JOIN" );
3585 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3586 table, right_alias, right_alias, field, left_info->alias, fkey );
3588 // Add any other join conditions as specified by "filter"
3589 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3591 const char* filter_op = jsonObjectGetString(
3592 jsonObjectGetKeyConst( snode, "filter_op" ) );
3593 if( filter_op && !strcasecmp( "or",filter_op )) {
3594 buffer_add( join_buf, " OR " );
3596 buffer_add( join_buf, " AND " );
3599 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3601 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3602 OSRF_BUFFER_ADD( join_buf, jpred );
3607 "%s: JOIN failed. Invalid conditional expression.",
3610 jsonIteratorFree( search_itr );
3611 buffer_free( join_buf );
3612 if( freeable_subhash )
3613 jsonObjectFree( freeable_subhash );
3615 jsonObjectFree( freeable_hash );
3616 if( freeable_array )
3617 jsonObjectFree( freeable_array );
3622 buffer_add( join_buf, " ) " );
3624 // Recursively add a nested join, if one is present
3625 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3627 char* jpred = searchJOIN( join_filter, right_info );
3629 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3630 OSRF_BUFFER_ADD( join_buf, jpred );
3633 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3634 jsonIteratorFree( search_itr );
3635 buffer_free( join_buf );
3636 if( freeable_subhash )
3637 jsonObjectFree( freeable_subhash );
3639 jsonObjectFree( freeable_hash );
3640 if( freeable_array )
3641 jsonObjectFree( freeable_array );
3647 if( freeable_subhash )
3648 jsonObjectFree( freeable_subhash );
3650 jsonIteratorFree( search_itr );
3654 jsonObjectFree( freeable_hash );
3656 if( freeable_array )
3657 jsonObjectFree( freeable_array );
3660 return buffer_release( join_buf );
3665 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3666 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3667 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3669 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3671 search_hash is the JSON expression of the conditions.
3672 meta is the class definition from the IDL, for the relevant table.
3673 opjoin_type indicates whether multiple conditions, if present, should be
3674 connected by AND or OR.
3675 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3676 to pass it to other functions -- and all they do with it is to use the session
3677 and request members to send error messages back to the client.
3681 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3682 int opjoin_type, osrfMethodContext* ctx ) {
3686 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3687 "opjoin_type = %d, ctx addr = %p",
3690 class_info->class_def,
3695 growing_buffer* sql_buf = buffer_init( 128 );
3697 jsonObject* node = NULL;
3700 if( search_hash->type == JSON_ARRAY ) {
3701 if( 0 == search_hash->size ) {
3704 "%s: Invalid predicate structure: empty JSON array",
3707 buffer_free( sql_buf );
3711 unsigned long i = 0;
3712 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3716 if( opjoin_type == OR_OP_JOIN )
3717 buffer_add( sql_buf, " OR " );
3719 buffer_add( sql_buf, " AND " );
3722 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3724 buffer_free( sql_buf );
3728 buffer_fadd( sql_buf, "( %s )", subpred );
3732 } else if( search_hash->type == JSON_HASH ) {
3733 osrfLogDebug( OSRF_LOG_MARK,
3734 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3735 jsonIterator* search_itr = jsonNewIterator( search_hash );
3736 if( !jsonIteratorHasNext( search_itr ) ) {
3739 "%s: Invalid predicate structure: empty JSON object",
3742 jsonIteratorFree( search_itr );
3743 buffer_free( sql_buf );
3747 while( (node = jsonIteratorNext( search_itr )) ) {
3752 if( opjoin_type == OR_OP_JOIN )
3753 buffer_add( sql_buf, " OR " );
3755 buffer_add( sql_buf, " AND " );
3758 if( '+' == search_itr->key[ 0 ] ) {
3760 // This plus sign prefixes a class name or other table alias;
3761 // make sure the table alias is in scope
3762 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3763 if( ! alias_info ) {
3766 "%s: Invalid table alias \"%s\" in WHERE clause",
3770 jsonIteratorFree( search_itr );
3771 buffer_free( sql_buf );
3775 if( node->type == JSON_STRING ) {
3776 // It's the name of a column; make sure it belongs to the class
3777 const char* fieldname = jsonObjectGetString( node );
3778 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3781 "%s: Invalid column name \"%s\" in WHERE clause "
3782 "for table alias \"%s\"",
3787 jsonIteratorFree( search_itr );
3788 buffer_free( sql_buf );
3792 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3794 // It's something more complicated
3795 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3797 jsonIteratorFree( search_itr );
3798 buffer_free( sql_buf );
3802 buffer_fadd( sql_buf, "( %s )", subpred );
3805 } else if( '-' == search_itr->key[ 0 ] ) {
3806 if( !strcasecmp( "-or", search_itr->key )) {
3807 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3809 jsonIteratorFree( search_itr );
3810 buffer_free( sql_buf );
3814 buffer_fadd( sql_buf, "( %s )", subpred );
3816 } else if( !strcasecmp( "-and", search_itr->key )) {
3817 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3819 jsonIteratorFree( search_itr );
3820 buffer_free( sql_buf );
3824 buffer_fadd( sql_buf, "( %s )", subpred );
3826 } else if( !strcasecmp("-not",search_itr->key) ) {
3827 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3829 jsonIteratorFree( search_itr );
3830 buffer_free( sql_buf );
3834 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3836 } else if( !strcasecmp( "-exists", search_itr->key )) {
3837 char* subpred = buildQuery( ctx, node, SUBSELECT );
3839 jsonIteratorFree( search_itr );
3840 buffer_free( sql_buf );
3844 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3846 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3847 char* subpred = buildQuery( ctx, node, SUBSELECT );
3849 jsonIteratorFree( search_itr );
3850 buffer_free( sql_buf );
3854 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3856 } else { // Invalid "minus" operator
3859 "%s: Invalid operator \"%s\" in WHERE clause",
3863 jsonIteratorFree( search_itr );
3864 buffer_free( sql_buf );
3870 const char* class = class_info->class_name;
3871 osrfHash* fields = class_info->fields;
3872 osrfHash* field = osrfHashGet( fields, search_itr->key );
3875 const char* table = class_info->source_def;
3878 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3881 table ? table : "?",
3884 jsonIteratorFree( search_itr );
3885 buffer_free( sql_buf );
3889 char* subpred = searchPredicate( class_info, field, node, ctx );
3891 buffer_free( sql_buf );
3892 jsonIteratorFree( search_itr );
3896 buffer_add( sql_buf, subpred );
3900 jsonIteratorFree( search_itr );
3903 // ERROR ... only hash and array allowed at this level
3904 char* predicate_string = jsonObjectToJSON( search_hash );
3907 "%s: Invalid predicate structure: %s",
3911 buffer_free( sql_buf );
3912 free( predicate_string );
3916 return buffer_release( sql_buf );
3919 /* Build a JSON_ARRAY of field names for a given table alias
3921 static jsonObject* defaultSelectList( const char* table_alias ) {
3926 ClassInfo* class_info = search_all_alias( table_alias );
3927 if( ! class_info ) {
3930 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3937 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3938 osrfHash* field_def = NULL;
3939 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3940 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3941 const char* field_name = osrfHashIteratorKey( field_itr );
3942 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3943 jsonObjectPush( array, jsonNewObject( field_name ) );
3946 osrfHashIteratorFree( field_itr );
3951 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3952 // The jsonObject must be a JSON_HASH with an single entry for "union",
3953 // "intersect", or "except". The data associated with this key must be an
3954 // array of hashes, each hash being a query.
3955 // Also allowed but currently ignored: entries for "order_by" and "alias".
3956 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3958 if( ! combo || combo->type != JSON_HASH )
3959 return NULL; // should be impossible; validated by caller
3961 const jsonObject* query_array = NULL; // array of subordinate queries
3962 const char* op = NULL; // name of operator, e.g. UNION
3963 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3964 int op_count = 0; // for detecting conflicting operators
3965 int excepting = 0; // boolean
3966 int all = 0; // boolean
3967 jsonObject* order_obj = NULL;
3969 // Identify the elements in the hash
3970 jsonIterator* query_itr = jsonNewIterator( combo );
3971 jsonObject* curr_obj = NULL;
3972 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3973 if( ! strcmp( "union", query_itr->key ) ) {
3976 query_array = curr_obj;
3977 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3980 query_array = curr_obj;
3981 } else if( ! strcmp( "except", query_itr->key ) ) {
3985 query_array = curr_obj;
3986 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3989 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3992 order_obj = curr_obj;
3993 } else if( ! strcmp( "alias", query_itr->key ) ) {
3994 if( curr_obj->type != JSON_STRING ) {
3995 jsonIteratorFree( query_itr );
3998 alias = jsonObjectGetString( curr_obj );
3999 } else if( ! strcmp( "all", query_itr->key ) ) {
4000 if( obj_is_true( curr_obj ) )
4004 osrfAppSessionStatus(
4006 OSRF_STATUS_INTERNALSERVERERROR,
4007 "osrfMethodException",
4009 "Malformed query; unexpected entry in query object"
4013 "%s: Unexpected entry for \"%s\" in%squery",
4018 jsonIteratorFree( query_itr );
4022 jsonIteratorFree( query_itr );
4024 // More sanity checks
4025 if( ! query_array ) {
4027 osrfAppSessionStatus(
4029 OSRF_STATUS_INTERNALSERVERERROR,
4030 "osrfMethodException",
4032 "Expected UNION, INTERSECT, or EXCEPT operator not found"
4036 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
4039 return NULL; // should be impossible...
4040 } else if( op_count > 1 ) {
4042 osrfAppSessionStatus(
4044 OSRF_STATUS_INTERNALSERVERERROR,
4045 "osrfMethodException",
4047 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
4051 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
4055 } if( query_array->type != JSON_ARRAY ) {
4057 osrfAppSessionStatus(
4059 OSRF_STATUS_INTERNALSERVERERROR,
4060 "osrfMethodException",
4062 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
4066 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
4069 json_type( query_array->type )
4072 } if( query_array->size < 2 ) {
4074 osrfAppSessionStatus(
4076 OSRF_STATUS_INTERNALSERVERERROR,
4077 "osrfMethodException",
4079 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
4083 "%s:%srequires multiple queries as operands",
4088 } else if( excepting && query_array->size > 2 ) {
4090 osrfAppSessionStatus(
4092 OSRF_STATUS_INTERNALSERVERERROR,
4093 "osrfMethodException",
4095 "EXCEPT operator has too many queries as operands"
4099 "%s:EXCEPT operator has too many queries as operands",
4103 } else if( order_obj && ! alias ) {
4105 osrfAppSessionStatus(
4107 OSRF_STATUS_INTERNALSERVERERROR,
4108 "osrfMethodException",
4110 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
4114 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
4120 // So far so good. Now build the SQL.
4121 growing_buffer* sql = buffer_init( 256 );
4123 // If we nested inside another UNION, INTERSECT, or EXCEPT,
4124 // Add a layer of parentheses
4125 if( flags & SUBCOMBO )
4126 OSRF_BUFFER_ADD( sql, "( " );
4128 // Traverse the query array. Each entry should be a hash.
4129 int first = 1; // boolean
4131 jsonObject* query = NULL;
4132 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
4133 if( query->type != JSON_HASH ) {
4135 osrfAppSessionStatus(
4137 OSRF_STATUS_INTERNALSERVERERROR,
4138 "osrfMethodException",
4140 "Malformed query under UNION, INTERSECT or EXCEPT"
4144 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
4147 json_type( query->type )
4156 OSRF_BUFFER_ADD( sql, op );
4158 OSRF_BUFFER_ADD( sql, "ALL " );
4161 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
4165 "%s: Error building query under%s",
4173 OSRF_BUFFER_ADD( sql, query_str );
4176 if( flags & SUBCOMBO )
4177 OSRF_BUFFER_ADD_CHAR( sql, ')' );
4179 if( !(flags & SUBSELECT) )
4180 OSRF_BUFFER_ADD_CHAR( sql, ';' );
4182 return buffer_release( sql );
4185 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
4186 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
4187 // or "except" to indicate the type of query.
4188 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
4192 osrfAppSessionStatus(
4194 OSRF_STATUS_INTERNALSERVERERROR,
4195 "osrfMethodException",
4197 "Malformed query; no query object"
4199 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
4201 } else if( query->type != JSON_HASH ) {
4203 osrfAppSessionStatus(
4205 OSRF_STATUS_INTERNALSERVERERROR,
4206 "osrfMethodException",
4208 "Malformed query object"
4212 "%s: Query object is %s instead of JSON_HASH",
4214 json_type( query->type )
4219 // Determine what kind of query it purports to be, and dispatch accordingly.
4220 if( jsonObjectGetKeyConst( query, "union" ) ||
4221 jsonObjectGetKeyConst( query, "intersect" ) ||
4222 jsonObjectGetKeyConst( query, "except" )) {
4223 return doCombo( ctx, query, flags );
4225 // It is presumably a SELECT query
4227 // Push a node onto the stack for the current query. Every level of
4228 // subquery gets its own QueryFrame on the Stack.
4231 // Build an SQL SELECT statement
4234 jsonObjectGetKey( query, "select" ),
4235 jsonObjectGetKeyConst( query, "from" ),
4236 jsonObjectGetKeyConst( query, "where" ),
4237 jsonObjectGetKeyConst( query, "having" ),
4238 jsonObjectGetKeyConst( query, "order_by" ),
4239 jsonObjectGetKeyConst( query, "limit" ),
4240 jsonObjectGetKeyConst( query, "offset" ),
4249 /* method context */ osrfMethodContext* ctx,
4251 /* SELECT */ jsonObject* selhash,
4252 /* FROM */ const jsonObject* join_hash,
4253 /* WHERE */ const jsonObject* search_hash,
4254 /* HAVING */ const jsonObject* having_hash,
4255 /* ORDER BY */ const jsonObject* order_hash,
4256 /* LIMIT */ const jsonObject* limit,
4257 /* OFFSET */ const jsonObject* offset,
4258 /* flags */ int flags
4260 const char* locale = osrf_message_get_last_locale();
4262 // general tmp objects
4263 const jsonObject* tmp_const;
4264 jsonObject* selclass = NULL;
4265 jsonObject* snode = NULL;
4266 jsonObject* onode = NULL;
4268 char* string = NULL;
4269 int from_function = 0;
4274 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
4276 // punt if there's no FROM clause
4277 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
4280 "%s: FROM clause is missing or empty",
4284 osrfAppSessionStatus(
4286 OSRF_STATUS_INTERNALSERVERERROR,
4287 "osrfMethodException",
4289 "FROM clause is missing or empty in JSON query"
4294 // the core search class
4295 const char* core_class = NULL;
4297 // get the core class -- the only key of the top level FROM clause, or a string
4298 if( join_hash->type == JSON_HASH ) {
4299 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
4300 snode = jsonIteratorNext( tmp_itr );
4302 // Populate the current QueryFrame with information
4303 // about the core class
4304 if( add_query_core( NULL, tmp_itr->key ) ) {
4306 osrfAppSessionStatus(
4308 OSRF_STATUS_INTERNALSERVERERROR,
4309 "osrfMethodException",
4311 "Unable to look up core class"
4315 core_class = curr_query->core.class_name;
4318 jsonObject* extra = jsonIteratorNext( tmp_itr );
4320 jsonIteratorFree( tmp_itr );
4323 // There shouldn't be more than one entry in join_hash
4327 "%s: Malformed FROM clause: extra entry in JSON_HASH",
4331 osrfAppSessionStatus(
4333 OSRF_STATUS_INTERNALSERVERERROR,
4334 "osrfMethodException",
4336 "Malformed FROM clause in JSON query"
4338 return NULL; // Malformed join_hash; extra entry
4340 } else if( join_hash->type == JSON_ARRAY ) {
4341 // We're selecting from a function, not from a table
4343 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
4346 } else if( join_hash->type == JSON_STRING ) {
4347 // Populate the current QueryFrame with information
4348 // about the core class
4349 core_class = jsonObjectGetString( join_hash );
4351 if( add_query_core( NULL, core_class ) ) {
4353 osrfAppSessionStatus(
4355 OSRF_STATUS_INTERNALSERVERERROR,
4356 "osrfMethodException",
4358 "Unable to look up core class"
4366 "%s: FROM clause is unexpected JSON type: %s",
4368 json_type( join_hash->type )
4371 osrfAppSessionStatus(
4373 OSRF_STATUS_INTERNALSERVERERROR,
4374 "osrfMethodException",
4376 "Ill-formed FROM clause in JSON query"
4381 // Build the join clause, if any, while filling out the list
4382 // of joined classes in the current QueryFrame.
4383 char* join_clause = NULL;
4384 if( join_hash && ! from_function ) {
4386 join_clause = searchJOIN( join_hash, &curr_query->core );
4387 if( ! join_clause ) {
4389 osrfAppSessionStatus(
4391 OSRF_STATUS_INTERNALSERVERERROR,
4392 "osrfMethodException",
4394 "Unable to construct JOIN clause(s)"
4400 // For in case we don't get a select list
4401 jsonObject* defaultselhash = NULL;
4403 // if there is no select list, build a default select list ...
4404 if( !selhash && !from_function ) {
4405 jsonObject* default_list = defaultSelectList( core_class );
4406 if( ! default_list ) {
4408 osrfAppSessionStatus(
4410 OSRF_STATUS_INTERNALSERVERERROR,
4411 "osrfMethodException",
4413 "Unable to build default SELECT clause in JSON query"
4415 free( join_clause );
4420 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4421 jsonObjectSetKey( selhash, core_class, default_list );
4424 // The SELECT clause can be encoded only by a hash
4425 if( !from_function && selhash->type != JSON_HASH ) {
4428 "%s: Expected JSON_HASH for SELECT clause; found %s",
4430 json_type( selhash->type )
4434 osrfAppSessionStatus(
4436 OSRF_STATUS_INTERNALSERVERERROR,
4437 "osrfMethodException",
4439 "Malformed SELECT clause in JSON query"
4441 free( join_clause );
4445 // If you see a null or wild card specifier for the core class, or an
4446 // empty array, replace it with a default SELECT list
4447 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4449 int default_needed = 0; // boolean
4450 if( JSON_STRING == tmp_const->type
4451 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4453 else if( JSON_NULL == tmp_const->type )
4456 if( default_needed ) {
4457 // Build a default SELECT list
4458 jsonObject* default_list = defaultSelectList( core_class );
4459 if( ! default_list ) {
4461 osrfAppSessionStatus(
4463 OSRF_STATUS_INTERNALSERVERERROR,
4464 "osrfMethodException",
4466 "Can't build default SELECT clause in JSON query"
4468 free( join_clause );
4473 jsonObjectSetKey( selhash, core_class, default_list );
4477 // temp buffers for the SELECT list and GROUP BY clause
4478 growing_buffer* select_buf = buffer_init( 128 );
4479 growing_buffer* group_buf = buffer_init( 128 );
4481 int aggregate_found = 0; // boolean
4483 // Build a select list
4484 if( from_function ) // From a function we select everything
4485 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4488 // Build the SELECT list as SQL
4492 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4493 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4495 const char* cname = selclass_itr->key;
4497 // Make sure the target relation is in the FROM clause.
4499 // At this point join_hash is a step down from the join_hash we
4500 // received as a parameter. If the original was a JSON_STRING,
4501 // then json_hash is now NULL. If the original was a JSON_HASH,
4502 // then json_hash is now the first (and only) entry in it,
4503 // denoting the core class. We've already excluded the
4504 // possibility that the original was a JSON_ARRAY, because in
4505 // that case from_function would be non-NULL, and we wouldn't
4508 // If the current table alias isn't in scope, bail out
4509 ClassInfo* class_info = search_alias( cname );
4510 if( ! class_info ) {
4513 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4518 osrfAppSessionStatus(
4520 OSRF_STATUS_INTERNALSERVERERROR,
4521 "osrfMethodException",
4523 "Selected class not in FROM clause in JSON query"
4525 jsonIteratorFree( selclass_itr );
4526 buffer_free( select_buf );
4527 buffer_free( group_buf );
4528 if( defaultselhash )
4529 jsonObjectFree( defaultselhash );
4530 free( join_clause );
4534 if( selclass->type != JSON_ARRAY ) {
4537 "%s: Malformed SELECT list for class \"%s\"; not an array",
4542 osrfAppSessionStatus(
4544 OSRF_STATUS_INTERNALSERVERERROR,
4545 "osrfMethodException",
4547 "Selected class not in FROM clause in JSON query"
4550 jsonIteratorFree( selclass_itr );
4551 buffer_free( select_buf );
4552 buffer_free( group_buf );
4553 if( defaultselhash )
4554 jsonObjectFree( defaultselhash );
4555 free( join_clause );
4559 // Look up some attributes of the current class
4560 osrfHash* idlClass = class_info->class_def;
4561 osrfHash* class_field_set = class_info->fields;
4562 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4563 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4565 if( 0 == selclass->size ) {
4568 "%s: No columns selected from \"%s\"",
4574 // stitch together the column list for the current table alias...
4575 unsigned long field_idx = 0;
4576 jsonObject* selfield = NULL;
4577 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4579 // If we need a separator comma, add one
4583 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4586 // if the field specification is a string, add it to the list
4587 if( selfield->type == JSON_STRING ) {
4589 // Look up the field in the IDL
4590 const char* col_name = jsonObjectGetString( selfield );
4591 osrfHash* field_def = NULL;
4593 if (!osrfStringArrayContains(
4595 osrfHashGet( class_field_set, col_name ),
4596 "suppress_controller"),
4599 field_def = osrfHashGet( class_field_set, col_name );
4602 // No such field in current class
4605 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4611 osrfAppSessionStatus(
4613 OSRF_STATUS_INTERNALSERVERERROR,
4614 "osrfMethodException",
4616 "Selected column not defined in JSON query"
4618 jsonIteratorFree( selclass_itr );
4619 buffer_free( select_buf );
4620 buffer_free( group_buf );
4621 if( defaultselhash )
4622 jsonObjectFree( defaultselhash );
4623 free( join_clause );
4625 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4626 // Virtual field not allowed
4629 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4635 osrfAppSessionStatus(
4637 OSRF_STATUS_INTERNALSERVERERROR,
4638 "osrfMethodException",
4640 "Selected column may not be virtual in JSON query"
4642 jsonIteratorFree( selclass_itr );
4643 buffer_free( select_buf );
4644 buffer_free( group_buf );
4645 if( defaultselhash )
4646 jsonObjectFree( defaultselhash );
4647 free( join_clause );
4653 if( flags & DISABLE_I18N )
4656 i18n = osrfHashGet( field_def, "i18n" );
4658 if( str_is_true( i18n ) ) {
4659 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4660 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4661 class_tname, cname, col_name, class_pkey,
4662 cname, class_pkey, locale, col_name );
4664 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4665 cname, col_name, col_name );
4668 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4669 cname, col_name, col_name );
4672 // ... but it could be an object, in which case we check for a Field Transform
4673 } else if( selfield->type == JSON_HASH ) {
4675 const char* col_name = jsonObjectGetString(
4676 jsonObjectGetKeyConst( selfield, "column" ) );
4678 // Get the field definition from the IDL
4679 osrfHash* field_def = NULL;
4680 if (!osrfStringArrayContains(
4682 osrfHashGet( class_field_set, col_name ),
4683 "suppress_controller"),
4686 field_def = osrfHashGet( class_field_set, col_name );
4690 // No such field in current class
4693 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4699 osrfAppSessionStatus(
4701 OSRF_STATUS_INTERNALSERVERERROR,
4702 "osrfMethodException",
4704 "Selected column is not defined in JSON query"
4706 jsonIteratorFree( selclass_itr );
4707 buffer_free( select_buf );
4708 buffer_free( group_buf );
4709 if( defaultselhash )
4710 jsonObjectFree( defaultselhash );
4711 free( join_clause );
4713 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4714 // No such field in current class
4717 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4723 osrfAppSessionStatus(
4725 OSRF_STATUS_INTERNALSERVERERROR,
4726 "osrfMethodException",
4728 "Selected column is virtual in JSON query"
4730 jsonIteratorFree( selclass_itr );
4731 buffer_free( select_buf );
4732 buffer_free( group_buf );
4733 if( defaultselhash )
4734 jsonObjectFree( defaultselhash );
4735 free( join_clause );
4739 // Decide what to use as a column alias
4741 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4742 _alias = jsonObjectGetString( tmp_const );
4743 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4744 _alias = jsonObjectGetString( tmp_const );
4745 } else { // Use field name as the alias
4749 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4750 char* transform_str = searchFieldTransform(
4751 class_info->alias, field_def, selfield );
4752 if( transform_str ) {
4753 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4754 free( transform_str );
4757 osrfAppSessionStatus(
4759 OSRF_STATUS_INTERNALSERVERERROR,
4760 "osrfMethodException",
4762 "Unable to generate transform function in JSON query"
4764 jsonIteratorFree( selclass_itr );
4765 buffer_free( select_buf );
4766 buffer_free( group_buf );
4767 if( defaultselhash )
4768 jsonObjectFree( defaultselhash );
4769 free( join_clause );
4776 if( flags & DISABLE_I18N )
4779 i18n = osrfHashGet( field_def, "i18n" );
4781 if( str_is_true( i18n ) ) {
4782 buffer_fadd( select_buf,
4783 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4784 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4785 class_tname, cname, col_name, class_pkey, cname,
4786 class_pkey, locale, _alias );
4788 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4789 cname, col_name, _alias );
4792 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4793 cname, col_name, _alias );
4800 "%s: Selected item is unexpected JSON type: %s",
4802 json_type( selfield->type )
4805 osrfAppSessionStatus(
4807 OSRF_STATUS_INTERNALSERVERERROR,
4808 "osrfMethodException",
4810 "Ill-formed SELECT item in JSON query"
4812 jsonIteratorFree( selclass_itr );
4813 buffer_free( select_buf );
4814 buffer_free( group_buf );
4815 if( defaultselhash )
4816 jsonObjectFree( defaultselhash );
4817 free( join_clause );
4821 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4822 if( obj_is_true( agg_obj ) )
4823 aggregate_found = 1;
4825 // Append a comma (except for the first one)
4826 // and add the column to a GROUP BY clause
4830 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4832 buffer_fadd( group_buf, " %d", sel_pos );
4836 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4838 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( selfield, "aggregate");
4839 if ( ! obj_is_true( aggregate_obj ) ) {
4843 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4846 buffer_fadd(group_buf, " %d", sel_pos);
4849 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4853 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4856 _column = searchFieldTransform(class_info->alias, field, selfield);
4857 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4858 OSRF_BUFFER_ADD(group_buf, _column);
4859 _column = searchFieldTransform(class_info->alias, field, selfield);
4866 } // end while -- iterating across SELECT columns
4868 } // end while -- iterating across classes
4870 jsonIteratorFree( selclass_itr );
4873 char* col_list = buffer_release( select_buf );
4875 // Make sure the SELECT list isn't empty. This can happen, for example,
4876 // if we try to build a default SELECT clause from a non-core table.
4879 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4881 osrfAppSessionStatus(
4883 OSRF_STATUS_INTERNALSERVERERROR,
4884 "osrfMethodException",
4886 "SELECT list is empty"
4889 buffer_free( group_buf );
4890 if( defaultselhash )
4891 jsonObjectFree( defaultselhash );
4892 free( join_clause );
4898 table = searchValueTransform( join_hash );
4900 table = strdup( curr_query->core.source_def );
4904 osrfAppSessionStatus(
4906 OSRF_STATUS_INTERNALSERVERERROR,
4907 "osrfMethodException",
4909 "Unable to identify table for core class"
4912 buffer_free( group_buf );
4913 if( defaultselhash )
4914 jsonObjectFree( defaultselhash );
4915 free( join_clause );
4919 // Put it all together
4920 growing_buffer* sql_buf = buffer_init( 128 );
4921 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4925 // Append the join clause, if any
4927 buffer_add(sql_buf, join_clause );
4928 free( join_clause );
4931 char* order_by_list = NULL;
4932 char* having_buf = NULL;
4934 if( !from_function ) {
4936 // Build a WHERE clause, if there is one
4938 buffer_add( sql_buf, " WHERE " );
4940 // and it's on the WHERE clause
4941 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4944 osrfAppSessionStatus(
4946 OSRF_STATUS_INTERNALSERVERERROR,
4947 "osrfMethodException",
4949 "Severe query error in WHERE predicate -- see error log for more details"
4952 buffer_free( group_buf );
4953 buffer_free( sql_buf );
4954 if( defaultselhash )
4955 jsonObjectFree( defaultselhash );
4959 buffer_add( sql_buf, pred );
4963 // Build a HAVING clause, if there is one
4966 // and it's on the the WHERE clause
4967 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4969 if( ! having_buf ) {
4971 osrfAppSessionStatus(
4973 OSRF_STATUS_INTERNALSERVERERROR,
4974 "osrfMethodException",
4976 "Severe query error in HAVING predicate -- see error log for more details"
4979 buffer_free( group_buf );
4980 buffer_free( sql_buf );
4981 if( defaultselhash )
4982 jsonObjectFree( defaultselhash );
4987 // Build an ORDER BY clause, if there is one
4988 if( NULL == order_hash )
4989 ; // No ORDER BY? do nothing
4990 else if( JSON_ARRAY == order_hash->type ) {
4991 order_by_list = buildOrderByFromArray( ctx, order_hash );
4992 if( !order_by_list ) {
4994 buffer_free( group_buf );
4995 buffer_free( sql_buf );
4996 if( defaultselhash )
4997 jsonObjectFree( defaultselhash );
5000 } else if( JSON_HASH == order_hash->type ) {
5001 // This hash is keyed on class alias. Each class has either
5002 // an array of field names or a hash keyed on field name.
5003 growing_buffer* order_buf = NULL; // to collect ORDER BY list
5004 jsonIterator* class_itr = jsonNewIterator( order_hash );
5005 while( (snode = jsonIteratorNext( class_itr )) ) {
5007 ClassInfo* order_class_info = search_alias( class_itr->key );
5008 if( ! order_class_info ) {
5009 osrfLogError( OSRF_LOG_MARK,
5010 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
5011 modulename, class_itr->key );
5013 osrfAppSessionStatus(
5015 OSRF_STATUS_INTERNALSERVERERROR,
5016 "osrfMethodException",
5018 "Invalid class referenced in ORDER BY clause -- "
5019 "see error log for more details"
5021 jsonIteratorFree( class_itr );
5022 buffer_free( order_buf );
5024 buffer_free( group_buf );
5025 buffer_free( sql_buf );
5026 if( defaultselhash )
5027 jsonObjectFree( defaultselhash );
5031 osrfHash* field_list_def = order_class_info->fields;
5033 if( snode->type == JSON_HASH ) {
5035 // Hash is keyed on field names from the current class. For each field
5036 // there is another layer of hash to define the sorting details, if any,
5037 // or a string to indicate direction of sorting.
5038 jsonIterator* order_itr = jsonNewIterator( snode );
5039 while( (onode = jsonIteratorNext( order_itr )) ) {
5041 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
5043 osrfLogError( OSRF_LOG_MARK,
5044 "%s: Invalid field \"%s\" in ORDER BY clause",
5045 modulename, order_itr->key );
5047 osrfAppSessionStatus(
5049 OSRF_STATUS_INTERNALSERVERERROR,
5050 "osrfMethodException",
5052 "Invalid field in ORDER BY clause -- "
5053 "see error log for more details"
5055 jsonIteratorFree( order_itr );
5056 jsonIteratorFree( class_itr );
5057 buffer_free( order_buf );
5059 buffer_free( group_buf );
5060 buffer_free( sql_buf );
5061 if( defaultselhash )
5062 jsonObjectFree( defaultselhash );
5064 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5065 osrfLogError( OSRF_LOG_MARK,
5066 "%s: Virtual field \"%s\" in ORDER BY clause",
5067 modulename, order_itr->key );
5069 osrfAppSessionStatus(
5071 OSRF_STATUS_INTERNALSERVERERROR,
5072 "osrfMethodException",
5074 "Virtual field in ORDER BY clause -- "
5075 "see error log for more details"
5077 jsonIteratorFree( order_itr );
5078 jsonIteratorFree( class_itr );
5079 buffer_free( order_buf );
5081 buffer_free( group_buf );
5082 buffer_free( sql_buf );
5083 if( defaultselhash )
5084 jsonObjectFree( defaultselhash );
5088 const char* direction = NULL;
5089 if( onode->type == JSON_HASH ) {
5090 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5091 string = searchFieldTransform(
5093 osrfHashGet( field_list_def, order_itr->key ),
5097 if( ctx ) osrfAppSessionStatus(
5099 OSRF_STATUS_INTERNALSERVERERROR,
5100 "osrfMethodException",
5102 "Severe query error in ORDER BY clause -- "
5103 "see error log for more details"
5105 jsonIteratorFree( order_itr );
5106 jsonIteratorFree( class_itr );
5108 buffer_free( group_buf );
5109 buffer_free( order_buf);
5110 buffer_free( sql_buf );
5111 if( defaultselhash )
5112 jsonObjectFree( defaultselhash );
5116 growing_buffer* field_buf = buffer_init( 16 );
5117 buffer_fadd( field_buf, "\"%s\".%s",
5118 class_itr->key, order_itr->key );
5119 string = buffer_release( field_buf );
5122 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
5123 const char* dir = jsonObjectGetString( tmp_const );
5124 if(!strncasecmp( dir, "d", 1 )) {
5125 direction = " DESC";
5131 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
5132 osrfLogError( OSRF_LOG_MARK,
5133 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
5134 modulename, json_type( onode->type ) );
5136 osrfAppSessionStatus(
5138 OSRF_STATUS_INTERNALSERVERERROR,
5139 "osrfMethodException",
5141 "Malformed ORDER BY clause -- see error log for more details"
5143 jsonIteratorFree( order_itr );
5144 jsonIteratorFree( class_itr );
5146 buffer_free( group_buf );
5147 buffer_free( order_buf );
5148 buffer_free( sql_buf );
5149 if( defaultselhash )
5150 jsonObjectFree( defaultselhash );
5154 string = strdup( order_itr->key );
5155 const char* dir = jsonObjectGetString( onode );
5156 if( !strncasecmp( dir, "d", 1 )) {
5157 direction = " DESC";
5164 OSRF_BUFFER_ADD( order_buf, ", " );
5166 order_buf = buffer_init( 128 );
5168 OSRF_BUFFER_ADD( order_buf, string );
5172 OSRF_BUFFER_ADD( order_buf, direction );
5176 jsonIteratorFree( order_itr );
5178 } else if( snode->type == JSON_ARRAY ) {
5180 // Array is a list of fields from the current class
5181 unsigned long order_idx = 0;
5182 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
5184 const char* _f = jsonObjectGetString( onode );
5186 osrfHash* field_def = osrfHashGet( field_list_def, _f );
5188 osrfLogError( OSRF_LOG_MARK,
5189 "%s: Invalid field \"%s\" in ORDER BY clause",
5192 osrfAppSessionStatus(
5194 OSRF_STATUS_INTERNALSERVERERROR,
5195 "osrfMethodException",
5197 "Invalid field in ORDER BY clause -- "
5198 "see error log for more details"
5200 jsonIteratorFree( class_itr );
5201 buffer_free( order_buf );
5203 buffer_free( group_buf );
5204 buffer_free( sql_buf );
5205 if( defaultselhash )
5206 jsonObjectFree( defaultselhash );
5208 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5209 osrfLogError( OSRF_LOG_MARK,
5210 "%s: Virtual field \"%s\" in ORDER BY clause",
5213 osrfAppSessionStatus(
5215 OSRF_STATUS_INTERNALSERVERERROR,
5216 "osrfMethodException",
5218 "Virtual field in ORDER BY clause -- "
5219 "see error log for more details"
5221 jsonIteratorFree( class_itr );
5222 buffer_free( order_buf );
5224 buffer_free( group_buf );
5225 buffer_free( sql_buf );
5226 if( defaultselhash )
5227 jsonObjectFree( defaultselhash );
5232 OSRF_BUFFER_ADD( order_buf, ", " );
5234 order_buf = buffer_init( 128 );
5236 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
5240 // IT'S THE OOOOOOOOOOOLD STYLE!
5242 osrfLogError( OSRF_LOG_MARK,
5243 "%s: Possible SQL injection attempt; direct order by is not allowed",
5246 osrfAppSessionStatus(
5248 OSRF_STATUS_INTERNALSERVERERROR,
5249 "osrfMethodException",
5251 "Severe query error -- see error log for more details"
5256 buffer_free( group_buf );
5257 buffer_free( order_buf );
5258 buffer_free( sql_buf );
5259 if( defaultselhash )
5260 jsonObjectFree( defaultselhash );
5261 jsonIteratorFree( class_itr );
5265 jsonIteratorFree( class_itr );
5267 order_by_list = buffer_release( order_buf );
5269 osrfLogError( OSRF_LOG_MARK,
5270 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
5271 modulename, json_type( order_hash->type ) );
5273 osrfAppSessionStatus(
5275 OSRF_STATUS_INTERNALSERVERERROR,
5276 "osrfMethodException",
5278 "Malformed ORDER BY clause -- see error log for more details"
5281 buffer_free( group_buf );
5282 buffer_free( sql_buf );
5283 if( defaultselhash )
5284 jsonObjectFree( defaultselhash );
5289 string = buffer_release( group_buf );
5291 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
5292 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
5293 OSRF_BUFFER_ADD( sql_buf, string );
5298 if( having_buf && *having_buf ) {
5299 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
5300 OSRF_BUFFER_ADD( sql_buf, having_buf );
5304 if( order_by_list ) {
5306 if( *order_by_list ) {
5307 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5308 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5311 free( order_by_list );
5315 const char* str = jsonObjectGetString( limit );
5316 if (str) { // limit could be JSON_NULL, etc.
5317 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
5322 const char* str = jsonObjectGetString( offset );
5324 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
5328 if( !(flags & SUBSELECT) )
5329 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5331 if( defaultselhash )
5332 jsonObjectFree( defaultselhash );
5334 return buffer_release( sql_buf );
5336 } // end of SELECT()
5339 @brief Build a list of ORDER BY expressions.
5340 @param ctx Pointer to the method context.
5341 @param order_array Pointer to a JSON_ARRAY of field specifications.
5342 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
5343 Each expression may be either a column reference or a function call whose first parameter
5344 is a column reference.
5346 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
5347 It may optionally include entries for "direction" and/or "transform".
5349 The calling code is responsible for freeing the returned string.
5351 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
5352 if( ! order_array ) {
5353 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
5356 osrfAppSessionStatus(
5358 OSRF_STATUS_INTERNALSERVERERROR,
5359 "osrfMethodException",
5361 "Logic error: ORDER BY clause expected, not found; "
5362 "see error log for more details"
5365 } else if( order_array->type != JSON_ARRAY ) {
5366 osrfLogError( OSRF_LOG_MARK,
5367 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
5369 osrfAppSessionStatus(
5371 OSRF_STATUS_INTERNALSERVERERROR,
5372 "osrfMethodException",
5374 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
5378 growing_buffer* order_buf = buffer_init( 128 );
5379 int first = 1; // boolean
5381 jsonObject* order_spec;
5382 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
5384 if( JSON_HASH != order_spec->type ) {
5385 osrfLogError( OSRF_LOG_MARK,
5386 "%s: Malformed field specification in ORDER BY clause; "
5387 "expected JSON_HASH, found %s",
5388 modulename, json_type( order_spec->type ) );
5390 osrfAppSessionStatus(
5392 OSRF_STATUS_INTERNALSERVERERROR,
5393 "osrfMethodException",
5395 "Malformed ORDER BY clause -- see error log for more details"
5397 buffer_free( order_buf );
5401 const char* class_alias =
5402 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5404 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5406 jsonObject* compare_to = jsonObjectGetKey( order_spec, "compare" );
5408 if( !field || !class_alias ) {
5409 osrfLogError( OSRF_LOG_MARK,
5410 "%s: Missing class or field name in field specification of ORDER BY clause",
5413 osrfAppSessionStatus(
5415 OSRF_STATUS_INTERNALSERVERERROR,
5416 "osrfMethodException",
5418 "Malformed ORDER BY clause -- see error log for more details"
5420 buffer_free( order_buf );
5424 const ClassInfo* order_class_info = search_alias( class_alias );
5425 if( ! order_class_info ) {
5426 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5427 "not in FROM clause, skipping it", modulename, class_alias );
5431 // Add a separating comma, except at the beginning
5435 OSRF_BUFFER_ADD( order_buf, ", " );
5437 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5439 osrfLogError( OSRF_LOG_MARK,
5440 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5441 modulename, class_alias, field );
5443 osrfAppSessionStatus(
5445 OSRF_STATUS_INTERNALSERVERERROR,
5446 "osrfMethodException",
5448 "Invalid field referenced in ORDER BY clause -- "
5449 "see error log for more details"
5453 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5454 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5455 modulename, field );
5457 osrfAppSessionStatus(
5459 OSRF_STATUS_INTERNALSERVERERROR,
5460 "osrfMethodException",
5462 "Virtual field in ORDER BY clause -- see error log for more details"
5464 buffer_free( order_buf );
5468 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5469 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5470 if( ! transform_str ) {
5472 osrfAppSessionStatus(
5474 OSRF_STATUS_INTERNALSERVERERROR,
5475 "osrfMethodException",
5477 "Severe query error in ORDER BY clause -- "
5478 "see error log for more details"
5480 buffer_free( order_buf );
5484 OSRF_BUFFER_ADD( order_buf, transform_str );
5485 free( transform_str );
5486 } else if( compare_to ) {
5487 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5488 if( ! compare_str ) {
5490 osrfAppSessionStatus(
5492 OSRF_STATUS_INTERNALSERVERERROR,
5493 "osrfMethodException",
5495 "Severe query error in ORDER BY clause -- "
5496 "see error log for more details"
5498 buffer_free( order_buf );
5502 buffer_fadd( order_buf, "(%s)", compare_str );
5503 free( compare_str );
5506 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5508 const char* direction =
5509 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5511 if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5512 OSRF_BUFFER_ADD( order_buf, " DESC" );
5514 OSRF_BUFFER_ADD( order_buf, " ASC" );
5518 return buffer_release( order_buf );
5522 @brief Build a SELECT statement.
5523 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5524 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5525 @param meta Pointer to the class metadata for the core class.
5526 @param ctx Pointer to the method context.
5527 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5529 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5530 "order_by", "limit", and "offset".
5532 The SELECT statements built here are distinct from those built for the json_query method.
5534 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5535 osrfHash* meta, osrfMethodContext* ctx ) {
5537 const char* locale = osrf_message_get_last_locale();
5539 osrfHash* fields = osrfHashGet( meta, "fields" );
5540 const char* core_class = osrfHashGet( meta, "classname" );
5542 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5544 jsonObject* selhash = NULL;
5545 jsonObject* defaultselhash = NULL;
5547 growing_buffer* sql_buf = buffer_init( 128 );
5548 growing_buffer* select_buf = buffer_init( 128 );
5550 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5551 defaultselhash = jsonNewObjectType( JSON_HASH );
5552 selhash = defaultselhash;
5555 // If there's no SELECT list for the core class, build one
5556 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5557 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5559 // Add every non-virtual field to the field list
5560 osrfHash* field_def = NULL;
5561 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5562 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5563 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5564 const char* field = osrfHashIteratorKey( field_itr );
5565 jsonObjectPush( field_list, jsonNewObject( field ) );
5568 osrfHashIteratorFree( field_itr );
5569 jsonObjectSetKey( selhash, core_class, field_list );
5572 // Build a list of columns for the SELECT clause
5574 const jsonObject* snode = NULL;
5575 jsonIterator* class_itr = jsonNewIterator( selhash );
5576 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5578 // If the class isn't in the IDL, ignore it
5579 const char* cname = class_itr->key;
5580 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5584 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5585 if( strcmp( core_class, class_itr->key )) {
5589 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5590 if( !found->size ) {
5591 jsonObjectFree( found );
5595 jsonObjectFree( found );
5598 const jsonObject* node = NULL;
5599 jsonIterator* select_itr = jsonNewIterator( snode );
5600 while( (node = jsonIteratorNext( select_itr )) ) {
5601 const char* item_str = jsonObjectGetString( node );
5602 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5603 char* fname = osrfHashGet( field, "name" );
5608 if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5614 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5619 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5620 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5623 i18n = osrfHashGet( field, "i18n" );
5625 if( str_is_true( i18n ) ) {
5626 char* pkey = osrfHashGet( idlClass, "primarykey" );
5627 char* tname = osrfHashGet( idlClass, "tablename" );
5629 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5630 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5631 tname, cname, fname, pkey, cname, pkey, locale, fname );
5633 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5636 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5640 jsonIteratorFree( select_itr );
5643 jsonIteratorFree( class_itr );
5645 char* col_list = buffer_release( select_buf );
5646 char* table = oilsGetRelation( meta );
5648 table = strdup( "(null)" );
5650 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5654 // Clear the query stack (as a fail-safe precaution against possible
5655 // leftover garbage); then push the first query frame onto the stack.
5656 clear_query_stack();
5658 if( add_query_core( NULL, core_class ) ) {
5660 osrfAppSessionStatus(
5662 OSRF_STATUS_INTERNALSERVERERROR,
5663 "osrfMethodException",
5665 "Unable to build query frame for core class"
5667 buffer_free( sql_buf );
5668 if( defaultselhash )
5669 jsonObjectFree( defaultselhash );
5673 // Add the JOIN clauses, if any
5675 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5676 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5677 OSRF_BUFFER_ADD( sql_buf, join_clause );
5678 free( join_clause );
5681 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5682 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5684 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5686 // Add the conditions in the WHERE clause
5687 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5689 osrfAppSessionStatus(
5691 OSRF_STATUS_INTERNALSERVERERROR,
5692 "osrfMethodException",
5694 "Severe query error -- see error log for more details"
5696 buffer_free( sql_buf );
5697 if( defaultselhash )
5698 jsonObjectFree( defaultselhash );
5699 clear_query_stack();
5702 buffer_add( sql_buf, pred );
5706 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5707 if( rest_of_query ) {
5708 const jsonObject* order_by = NULL;
5709 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5711 char* order_by_list = NULL;
5713 if( JSON_ARRAY == order_by->type ) {
5714 order_by_list = buildOrderByFromArray( ctx, order_by );
5715 if( !order_by_list ) {
5716 buffer_free( sql_buf );
5717 if( defaultselhash )
5718 jsonObjectFree( defaultselhash );
5719 clear_query_stack();
5722 } else if( JSON_HASH == order_by->type ) {
5723 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5724 // and build a list of ORDER BY expressions.
5725 growing_buffer* order_buf = buffer_init( 128 );
5727 jsonIterator* class_itr = jsonNewIterator( order_by );
5728 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5730 ClassInfo* order_class_info = search_alias( class_itr->key );
5731 if( ! order_class_info )
5732 continue; // class not referenced by FROM clause? Ignore it.
5734 if( JSON_HASH == snode->type ) {
5736 // If the data for the current class is a JSON_HASH, then it is
5737 // keyed on field name.
5739 const jsonObject* onode = NULL;
5740 jsonIterator* order_itr = jsonNewIterator( snode );
5741 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5743 osrfHash* field_def = osrfHashGet(
5744 order_class_info->fields, order_itr->key );
5746 continue; // Field not defined in IDL? Ignore it.
5747 if( str_is_true( osrfHashGet( field_def, "virtual")))
5748 continue; // Field is virtual? Ignore it.
5750 char* field_str = NULL;
5751 char* direction = NULL;
5752 if( onode->type == JSON_HASH ) {
5753 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5754 field_str = searchFieldTransform(
5755 class_itr->key, field_def, onode );
5757 osrfAppSessionStatus(
5759 OSRF_STATUS_INTERNALSERVERERROR,
5760 "osrfMethodException",
5762 "Severe query error in ORDER BY clause -- "
5763 "see error log for more details"
5765 jsonIteratorFree( order_itr );
5766 jsonIteratorFree( class_itr );
5767 buffer_free( order_buf );
5768 buffer_free( sql_buf );
5769 if( defaultselhash )
5770 jsonObjectFree( defaultselhash );
5771 clear_query_stack();
5775 growing_buffer* field_buf = buffer_init( 16 );
5776 buffer_fadd( field_buf, "\"%s\".%s",
5777 class_itr->key, order_itr->key );
5778 field_str = buffer_release( field_buf );
5781 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5782 const char* dir = jsonObjectGetString( order_by );
5783 if(!strncasecmp( dir, "d", 1 )) {
5784 direction = " DESC";
5788 field_str = strdup( order_itr->key );
5789 const char* dir = jsonObjectGetString( onode );
5790 if( !strncasecmp( dir, "d", 1 )) {
5791 direction = " DESC";
5800 buffer_add( order_buf, ", " );
5803 buffer_add( order_buf, field_str );
5807 buffer_add( order_buf, direction );
5809 } // end while; looping over ORDER BY expressions
5811 jsonIteratorFree( order_itr );
5813 } else if( JSON_STRING == snode->type ) {
5814 // We expect a comma-separated list of sort fields.
5815 const char* str = jsonObjectGetString( snode );
5816 if( strchr( str, ';' )) {
5817 // No semicolons allowed. It is theoretically possible for a
5818 // legitimate semicolon to occur within quotes, but it's not likely
5819 // to occur in practice in the context of an ORDER BY list.
5820 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5821 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5823 osrfAppSessionStatus(
5825 OSRF_STATUS_INTERNALSERVERERROR,
5826 "osrfMethodException",
5828 "Possible attempt at SOL injection -- "
5829 "semicolon found in ORDER BY list"
5832 jsonIteratorFree( class_itr );
5833 buffer_free( order_buf );
5834 buffer_free( sql_buf );
5835 if( defaultselhash )
5836 jsonObjectFree( defaultselhash );
5837 clear_query_stack();
5840 buffer_add( order_buf, str );
5844 } // end while; looping over order_by classes
5846 jsonIteratorFree( class_itr );
5847 order_by_list = buffer_release( order_buf );
5850 osrfLogWarning( OSRF_LOG_MARK,
5851 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5852 "no ORDER BY generated" );
5855 if( order_by_list && *order_by_list ) {
5856 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5857 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5860 free( order_by_list );
5863 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5865 const char* str = jsonObjectGetString( limit );
5875 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5877 const char* str = jsonObjectGetString( offset );
5888 if( defaultselhash )
5889 jsonObjectFree( defaultselhash );
5890 clear_query_stack();
5892 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5893 return buffer_release( sql_buf );
5896 int doJSONSearch ( osrfMethodContext* ctx ) {
5897 if(osrfMethodVerifyContext( ctx )) {
5898 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5902 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5906 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5910 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5911 flags |= SELECT_DISTINCT;
5913 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5914 flags |= DISABLE_I18N;
5916 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5917 clear_query_stack(); // a possibly needless precaution
5918 char* sql = buildQuery( ctx, hash, flags );
5919 clear_query_stack();
5926 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5929 dbhandle = writehandle;
5931 dbi_result result = dbi_conn_query( dbhandle, sql );
5934 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5936 if( dbi_result_first_row( result )) {
5937 /* JSONify the result */
5938 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5941 jsonObject* return_val = oilsMakeJSONFromResult( result );
5942 osrfAppRespond( ctx, return_val );
5943 jsonObjectFree( return_val );
5944 } while( dbi_result_next_row( result ));
5947 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5950 osrfAppRespondComplete( ctx, NULL );
5952 /* clean up the query */
5953 dbi_result_free( result );
5958 int errnum = dbi_conn_error( dbhandle, &msg );
5959 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5960 modulename, sql, errnum, msg ? msg : "(No description available)" );
5961 osrfAppSessionStatus(
5963 OSRF_STATUS_INTERNALSERVERERROR,
5964 "osrfMethodException",
5966 "Severe query error -- see error log for more details"
5968 if( !oilsIsDBConnected( dbhandle ))
5969 osrfAppSessionPanic( ctx->session );
5976 // The last parameter, err, is used to report an error condition by updating an int owned by
5977 // the calling code.
5979 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5980 // It is the responsibility of the calling code to initialize *err before the
5981 // call, so that it will be able to make sense of the result.
5983 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5984 // redundant anyway.
5985 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5986 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5988 const char* tz = _sanitize_tz_name(ctx->session->session_tz);
5991 dbhandle = writehandle;
5993 char* core_class = osrfHashGet( class_meta, "classname" );
5994 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5996 char* pkey = osrfHashGet( class_meta, "primarykey" );
5998 if (!ctx->session->userData)
5999 (void) initSessionCache( ctx );
6001 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
6002 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
6003 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
6005 int i_respond_directly = 0;
6006 int flesh_depth = 0;
6008 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
6010 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
6015 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
6017 // Setting the timezone if requested and not in a transaction
6018 if (!getXactId(ctx)) {
6022 dbi_result tz_res = dbi_conn_queryf( writehandle, "SET timezone TO '%s'; -- cstore", tz );
6024 osrfLogError( OSRF_LOG_MARK, "%s: Error setting timezone %s", modulename, tz);
6025 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
6026 "osrfMethodException", ctx->request, "Error setting timezone" );
6027 if( !oilsIsDBConnected( writehandle )) {
6028 osrfAppSessionPanic( ctx->session );
6032 dbi_result_free( tz_res );
6037 dbi_result res = dbi_conn_queryf( writehandle, "SET timezone TO DEFAULT; -- cstore" );
6039 osrfLogError( OSRF_LOG_MARK, "%s: Error resetting timezone", modulename);
6040 if( !oilsIsDBConnected( writehandle )) {
6041 osrfAppSessionPanic( ctx->session );
6045 dbi_result_free( res );
6051 dbi_result result = dbi_conn_query( dbhandle, sql );
6053 if( NULL == result ) {
6055 int errnum = dbi_conn_error( dbhandle, &msg );
6056 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
6057 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
6058 msg ? msg : "(No description available)" );
6059 if( !oilsIsDBConnected( dbhandle ))
6060 osrfAppSessionPanic( ctx->session );
6061 osrfAppSessionStatus(
6063 OSRF_STATUS_INTERNALSERVERERROR,
6064 "osrfMethodException",
6066 "Severe query error -- see error log for more details"
6073 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
6077 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
6078 jsonObject* row_obj = NULL;
6080 // The following two steps are for verifyObjectPCRUD()'s benefit.
6081 // 1. get the flesh depth
6082 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
6084 flesh_depth = (int) jsonObjectGetNumber( _tmp );
6085 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
6086 flesh_depth = max_flesh_depth;
6089 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
6090 // over the whole life of this request. This means if we've already set
6091 // up a rs_size_req_%d, do nothing.
6092 // a. Incidentally, we can also use this opportunity to set i_respond_directly
6093 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
6094 if( !rs_size ) { // pointer null, so value not set in hash
6095 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
6096 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
6098 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
6099 unsigned long long result_count = dbi_result_get_numrows( result );
6100 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
6101 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
6104 if( dbi_result_first_row( result )) {
6106 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
6107 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
6108 // eliminate the duplicates.
6109 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
6110 osrfHash* dedup = osrfNewHash();
6112 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
6113 char* pkey_val = oilsFMGetString( row_obj, pkey );
6114 if( osrfHashGet( dedup, pkey_val ) ) {
6115 jsonObjectFree( row_obj );
6118 if( !enforce_pcrud || !need_to_verify ||
6119 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
6120 osrfHashSet( dedup, pkey_val, pkey_val );
6121 jsonObjectPush( res_list, row_obj );
6124 } while( dbi_result_next_row( result ));
6125 osrfHashFree( dedup );
6128 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
6132 /* clean up the query */
6133 dbi_result_free( result );
6136 // If we're asked to flesh, and there's anything to flesh, then flesh it
6137 // (formerly we would skip fleshing if in pcrud mode, but now we support
6138 // fleshing even in PCRUD).
6139 if( res_list->size ) {
6140 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
6141 jsonObject* flesh_fields;
6142 jsonObject* flesh_blob = NULL;
6143 osrfStringArray* link_fields = NULL;
6144 osrfHash* links = NULL;
6148 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
6149 if( temp_blob && flesh_depth > 0 ) {
6151 flesh_blob = jsonObjectClone( temp_blob );
6152 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
6154 links = osrfHashGet( class_meta, "links" );
6156 // Make an osrfStringArray of the names of fields to be fleshed
6157 if( flesh_fields ) {
6158 if( flesh_fields->size == 1 ) {
6159 const char* _t = jsonObjectGetString(
6160 jsonObjectGetIndex( flesh_fields, 0 ) );
6161 if( !strcmp( _t, "*" ))
6162 link_fields = osrfHashKeys( links );
6165 if( !link_fields ) {
6167 link_fields = osrfNewStringArray( 1 );
6168 jsonIterator* _i = jsonNewIterator( flesh_fields );
6169 while ((_f = jsonIteratorNext( _i ))) {
6170 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
6172 jsonIteratorFree( _i );
6175 want_flesh = link_fields ? 1 : 0;
6179 osrfHash* fields = osrfHashGet( class_meta, "fields" );
6181 // Iterate over the JSON_ARRAY of rows
6183 unsigned long res_idx = 0;
6184 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
6187 const char* link_field;
6189 // Iterate over the list of fleshable fields
6191 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
6193 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
6195 osrfHash* kid_link = osrfHashGet( links, link_field );
6197 continue; // Not a link field; skip it
6199 osrfHash* field = osrfHashGet( fields, link_field );
6201 continue; // Not a field at all; skip it (IDL is ill-formed)
6203 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
6204 osrfHashGet( kid_link, "class" ));
6206 continue; // The class it links to doesn't exist; skip it
6208 const char* reltype = osrfHashGet( kid_link, "reltype" );
6210 continue; // No reltype; skip it (IDL is ill-formed)
6212 osrfHash* value_field = field;
6214 if( !strcmp( reltype, "has_many" )
6215 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
6216 value_field = osrfHashGet(
6217 fields, osrfHashGet( class_meta, "primarykey" ) );
6220 int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
6221 // fleshing pcrud case: we require the controller in need_to_verify mode
6222 if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
6223 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
6227 (unsigned long) atoi( osrfHashGet(field, "array_position") ),
6229 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
6235 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
6237 if( link_map->size > 0 ) {
6238 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
6241 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
6246 osrfHashGet( kid_link, "class" ),
6253 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
6254 osrfHashGet( kid_link, "field" ),
6255 osrfHashGet( kid_link, "class" ),
6256 osrfHashGet( kid_link, "key" ),
6257 osrfHashGet( kid_link, "reltype" )
6260 const char* search_key = jsonObjectGetString(
6261 jsonObjectGetIndex( cur,
6262 atoi( osrfHashGet( value_field, "array_position" ) )
6267 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
6271 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
6273 // construct WHERE clause
6274 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
6277 osrfHashGet( kid_link, "key" ),
6278 jsonNewObject( search_key )
6281 // construct the rest of the query, mostly
6282 // by copying pieces of the previous level of query
6283 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
6284 jsonObjectSetKey( rest_of_query, "flesh",
6285 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
6289 jsonObjectSetKey( rest_of_query, "flesh_fields",
6290 jsonObjectClone( flesh_blob ));
6292 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
6293 jsonObjectSetKey( rest_of_query, "order_by",
6294 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
6298 if( jsonObjectGetKeyConst( query_hash, "select" )) {
6299 jsonObjectSetKey( rest_of_query, "select",
6300 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
6304 // do the query, recursively, to expand the fleshable field
6305 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
6306 where_clause, rest_of_query, err );
6308 jsonObjectFree( where_clause );
6309 jsonObjectFree( rest_of_query );
6312 osrfStringArrayFree( link_fields );
6313 jsonObjectFree( res_list );
6314 jsonObjectFree( flesh_blob );
6318 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
6319 osrfHashGet( kid_link, "class" ), kids->size );
6321 // Traverse the result set
6322 jsonObject* X = NULL;
6323 if( link_map->size > 0 && kids->size > 0 ) {
6325 kids = jsonNewObjectType( JSON_ARRAY );
6327 jsonObject* _k_node;
6328 unsigned long res_idx = 0;
6329 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
6335 (unsigned long) atoi(
6341 osrfHashGet( kid_link, "class" )
6345 osrfStringArrayGetString( link_map, 0 )
6353 } // end while loop traversing X
6356 if (kids->size > 0) {
6358 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
6359 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
6361 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
6362 osrfHashGet( kid_link, "field" ));
6365 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6366 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
6371 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
6373 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
6374 osrfHashGet( kid_link, "field" ) );
6377 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6378 jsonObjectClone( kids )
6383 jsonObjectFree( kids );
6387 jsonObjectFree( kids );
6389 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
6390 osrfHashGet( kid_link, "field" ) );
6391 } // end while loop traversing list of fleshable fields
6394 if( i_respond_directly ) {
6395 if ( *methodtype == 'i' ) {
6396 osrfAppRespond( ctx,
6397 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
6399 osrfAppRespond( ctx, cur );
6402 } // end while loop traversing res_list
6403 jsonObjectFree( flesh_blob );
6404 osrfStringArrayFree( link_fields );
6407 if( i_respond_directly ) {
6408 jsonObjectFree( res_list );
6409 return jsonNewObjectType( JSON_ARRAY );
6416 int doUpdate( osrfMethodContext* ctx ) {
6417 if( osrfMethodVerifyContext( ctx )) {
6418 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6423 timeout_needs_resetting = 1;
6425 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6427 jsonObject* target = NULL;
6429 target = jsonObjectGetIndex( ctx->params, 1 );
6431 target = jsonObjectGetIndex( ctx->params, 0 );
6433 if(!verifyObjectClass( ctx, target )) {
6434 osrfAppRespondComplete( ctx, NULL );
6438 if( getXactId( ctx ) == NULL ) {
6439 osrfAppSessionStatus(
6441 OSRF_STATUS_BADREQUEST,
6442 "osrfMethodException",
6444 "No active transaction -- required for UPDATE"
6446 osrfAppRespondComplete( ctx, NULL );
6450 // The following test is harmless but redundant. If a class is
6451 // readonly, we don't register an update method for it.
6452 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6453 osrfAppSessionStatus(
6455 OSRF_STATUS_BADREQUEST,
6456 "osrfMethodException",
6458 "Cannot UPDATE readonly class"
6460 osrfAppRespondComplete( ctx, NULL );
6464 const char* trans_id = getXactId( ctx );
6466 // Set the last_xact_id
6467 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6469 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6470 trans_id, target->classname, index );
6471 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6474 char* pkey = osrfHashGet( meta, "primarykey" );
6475 osrfHash* fields = osrfHashGet( meta, "fields" );
6477 char* id = oilsFMGetString( target, pkey );
6481 "%s updating %s object with %s = %s",
6483 osrfHashGet( meta, "fieldmapper" ),
6488 dbhandle = writehandle;
6489 growing_buffer* sql = buffer_init( 128 );
6490 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6493 osrfHash* field_def = NULL;
6494 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6495 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6497 // Skip virtual fields, and the primary key
6498 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6501 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6505 const char* field_name = osrfHashIteratorKey( field_itr );
6506 if( ! strcmp( field_name, pkey ) )
6509 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6511 int value_is_numeric = 0; // boolean
6513 if( field_object && field_object->classname ) {
6514 value = oilsFMGetString(
6516 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6518 } else if( field_object && JSON_BOOL == field_object->type ) {
6519 if( jsonBoolIsTrue( field_object ) )
6520 value = strdup( "t" );
6522 value = strdup( "f" );
6524 value = jsonObjectToSimpleString( field_object );
6525 if( field_object && JSON_NUMBER == field_object->type )
6526 value_is_numeric = 1;
6529 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6530 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6532 if( !field_object || field_object->type == JSON_NULL ) {
6533 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6534 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6538 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6539 buffer_fadd( sql, " %s = NULL", field_name );
6542 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6546 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6548 const char* numtype = get_datatype( field_def );
6549 if( !strncmp( numtype, "INT", 3 ) ) {
6550 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6551 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6552 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6554 // Must really be intended as a string, so quote it
6555 if( dbi_conn_quote_string( dbhandle, &value )) {
6556 buffer_fadd( sql, " %s = %s", field_name, value );
6558 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6559 modulename, value );
6560 osrfAppSessionStatus(
6562 OSRF_STATUS_INTERNALSERVERERROR,
6563 "osrfMethodException",
6565 "Error quoting string -- please see the error log for more details"
6569 osrfHashIteratorFree( field_itr );
6571 osrfAppRespondComplete( ctx, NULL );
6576 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6579 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6583 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6584 buffer_fadd( sql, " %s = %s", field_name, value );
6586 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6587 osrfAppSessionStatus(
6589 OSRF_STATUS_INTERNALSERVERERROR,
6590 "osrfMethodException",
6592 "Error quoting string -- please see the error log for more details"
6596 osrfHashIteratorFree( field_itr );
6598 osrfAppRespondComplete( ctx, NULL );
6607 osrfHashIteratorFree( field_itr );
6609 jsonObject* obj = jsonNewObject( id );
6611 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6612 dbi_conn_quote_string( dbhandle, &id );
6614 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6616 char* query = buffer_release( sql );
6617 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6619 dbi_result result = dbi_conn_query( dbhandle, query );
6624 jsonObjectFree( obj );
6625 obj = jsonNewObject( NULL );
6627 int errnum = dbi_conn_error( dbhandle, &msg );
6630 "%s ERROR updating %s object with %s = %s: %d %s",
6632 osrfHashGet( meta, "fieldmapper" ),
6636 msg ? msg : "(No description available)"
6638 osrfAppSessionStatus(
6640 OSRF_STATUS_INTERNALSERVERERROR,
6641 "osrfMethodException",
6643 "Error in updating a row -- please see the error log for more details"
6645 if( !oilsIsDBConnected( dbhandle ))
6646 osrfAppSessionPanic( ctx->session );
6649 dbi_result_free( result );
6652 osrfAppRespondComplete( ctx, obj );
6653 jsonObjectFree( obj );
6657 int doDelete( osrfMethodContext* ctx ) {
6658 if( osrfMethodVerifyContext( ctx )) {
6659 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6664 timeout_needs_resetting = 1;
6666 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6668 if( getXactId( ctx ) == NULL ) {
6669 osrfAppSessionStatus(
6671 OSRF_STATUS_BADREQUEST,
6672 "osrfMethodException",
6674 "No active transaction -- required for DELETE"
6676 osrfAppRespondComplete( ctx, NULL );
6680 // The following test is harmless but redundant. If a class is
6681 // readonly, we don't register a delete method for it.
6682 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6683 osrfAppSessionStatus(
6685 OSRF_STATUS_BADREQUEST,
6686 "osrfMethodException",
6688 "Cannot DELETE readonly class"
6690 osrfAppRespondComplete( ctx, NULL );
6694 dbhandle = writehandle;
6696 char* pkey = osrfHashGet( meta, "primarykey" );
6703 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6704 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6705 osrfAppRespondComplete( ctx, NULL );
6709 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6711 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6712 osrfAppRespondComplete( ctx, NULL );
6715 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6720 "%s deleting %s object with %s = %s",
6722 osrfHashGet( meta, "fieldmapper" ),
6727 jsonObject* obj = jsonNewObject( id );
6729 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6730 dbi_conn_quote_string( writehandle, &id );
6732 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6733 osrfHashGet( meta, "tablename" ), pkey, id );
6738 jsonObjectFree( obj );
6739 obj = jsonNewObject( NULL );
6741 int errnum = dbi_conn_error( writehandle, &msg );
6744 "%s ERROR deleting %s object with %s = %s: %d %s",
6746 osrfHashGet( meta, "fieldmapper" ),
6750 msg ? msg : "(No description available)"
6752 osrfAppSessionStatus(
6754 OSRF_STATUS_INTERNALSERVERERROR,
6755 "osrfMethodException",
6757 "Error in deleting a row -- please see the error log for more details"
6759 if( !oilsIsDBConnected( writehandle ))
6760 osrfAppSessionPanic( ctx->session );
6762 dbi_result_free( result );
6766 osrfAppRespondComplete( ctx, obj );
6767 jsonObjectFree( obj );
6772 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6773 @param result An iterator for a result set; we only look at the current row.
6774 @param @meta Pointer to the class metadata for the core class.
6775 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6777 If a column is not defined in the IDL, or if it has no array_position defined for it in
6778 the IDL, or if it is defined as virtual, ignore it.
6780 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6781 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6782 array_position in the IDL.
6784 A field defined in the IDL but not represented in the returned row will leave a hole
6785 in the JSON_ARRAY. In effect it will be treated as a null value.
6787 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6788 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6789 classname corresponding to the @a meta argument.
6791 The calling code is responsible for freeing the the resulting jsonObject by calling
6794 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6795 if( !( result && meta )) return NULL;
6797 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6798 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6799 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6801 osrfHash* fields = osrfHashGet( meta, "fields" );
6803 int columnIndex = 1;
6804 const char* columnName;
6806 /* cycle through the columns in the row returned from the database */
6807 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6809 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6811 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6813 /* determine the field type and storage attributes */
6814 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6815 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6817 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6818 // or if it has no sequence number there, or if it's virtual, skip it.
6819 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6822 if( str_is_true( osrfHashGet( _f, "virtual" )))
6823 continue; // skip this column: IDL says it's virtual
6825 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6826 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6827 continue; // since we assign sequence numbers dynamically as we load the IDL.
6829 fmIndex = atoi( pos );
6830 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6832 continue; // This field is not defined in the IDL
6835 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6836 // sequence number from the IDL (which is likely to be different from the sequence
6837 // of columns in the SELECT clause).
6838 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6839 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6844 case DBI_TYPE_INTEGER :
6846 if( attr & DBI_INTEGER_SIZE8 )
6847 jsonObjectSetIndex( object, fmIndex,
6848 jsonNewNumberObject(
6849 dbi_result_get_longlong_idx( result, columnIndex )));
6851 jsonObjectSetIndex( object, fmIndex,
6852 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6856 case DBI_TYPE_DECIMAL :
6857 jsonObjectSetIndex( object, fmIndex,
6858 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6861 case DBI_TYPE_STRING :
6866 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6871 case DBI_TYPE_DATETIME : {
6873 char dt_string[ 256 ] = "";
6876 // Fetch the date column as a time_t
6877 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6879 // Translate the time_t to a human-readable string
6880 if( !( attr & DBI_DATETIME_DATE )) {
6881 gmtime_r( &_tmp_dt, &gmdt );
6882 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6883 } else if( !( attr & DBI_DATETIME_TIME )) {
6884 gmtime_r( &_tmp_dt, &gmdt );
6885 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6887 localtime_r( &_tmp_dt, &gmdt );
6888 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6891 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6895 case DBI_TYPE_BINARY :
6896 osrfLogError( OSRF_LOG_MARK,
6897 "Can't do binary at column %s : index %d", columnName, columnIndex );
6906 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6907 if( !result ) return NULL;
6909 jsonObject* object = jsonNewObject( NULL );
6912 char dt_string[ 256 ];
6915 int columnIndex = 1;
6917 unsigned short type;
6918 const char* columnName;
6920 /* cycle through the column list */
6921 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6923 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6925 /* determine the field type and storage attributes */
6926 type = dbi_result_get_field_type_idx( result, columnIndex );
6927 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6929 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6930 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6935 case DBI_TYPE_INTEGER :
6937 if( attr & DBI_INTEGER_SIZE8 )
6938 jsonObjectSetKey( object, columnName,
6939 jsonNewNumberObject( dbi_result_get_longlong_idx(
6940 result, columnIndex )) );
6942 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6943 dbi_result_get_int_idx( result, columnIndex )) );
6946 case DBI_TYPE_DECIMAL :
6947 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6948 dbi_result_get_double_idx( result, columnIndex )) );
6951 case DBI_TYPE_STRING :
6952 jsonObjectSetKey( object, columnName,
6953 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6956 case DBI_TYPE_DATETIME :
6958 memset( dt_string, '\0', sizeof( dt_string ));
6959 memset( &gmdt, '\0', sizeof( gmdt ));
6961 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6963 if( !( attr & DBI_DATETIME_DATE )) {
6964 gmtime_r( &_tmp_dt, &gmdt );
6965 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6966 } else if( !( attr & DBI_DATETIME_TIME )) {
6967 gmtime_r( &_tmp_dt, &gmdt );
6968 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6970 localtime_r( &_tmp_dt, &gmdt );
6971 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6974 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6977 case DBI_TYPE_BINARY :
6978 osrfLogError( OSRF_LOG_MARK,
6979 "Can't do binary at column %s : index %d", columnName, columnIndex );
6983 } // end while loop traversing result
6988 // Interpret a string as true or false
6989 int str_is_true( const char* str ) {
6990 if( NULL == str || strcasecmp( str, "true" ) )
6996 // Interpret a jsonObject as true or false
6997 static int obj_is_true( const jsonObject* obj ) {
7000 else switch( obj->type )
7008 if( strcasecmp( obj->value.s, "true" ) )
7012 case JSON_NUMBER : // Support 1/0 for perl's sake
7013 if( jsonObjectGetNumber( obj ) == 1.0 )
7022 // Translate a numeric code into a text string identifying a type of
7023 // jsonObject. To be used for building error messages.
7024 static const char* json_type( int code ) {
7030 return "JSON_ARRAY";
7032 return "JSON_STRING";
7034 return "JSON_NUMBER";
7040 return "(unrecognized)";
7044 // Extract the "primitive" attribute from an IDL field definition.
7045 // If we haven't initialized the app, then we must be running in
7046 // some kind of testbed. In that case, default to "string".
7047 static const char* get_primitive( osrfHash* field ) {
7048 const char* s = osrfHashGet( field, "primitive" );
7050 if( child_initialized )
7053 "%s ERROR No \"datatype\" attribute for field \"%s\"",
7055 osrfHashGet( field, "name" )
7063 // Extract the "datatype" attribute from an IDL field definition.
7064 // If we haven't initialized the app, then we must be running in
7065 // some kind of testbed. In that case, default to to NUMERIC,
7066 // since we look at the datatype only for numbers.
7067 static const char* get_datatype( osrfHash* field ) {
7068 const char* s = osrfHashGet( field, "datatype" );
7070 if( child_initialized )
7073 "%s ERROR No \"datatype\" attribute for field \"%s\"",
7075 osrfHashGet( field, "name" )
7084 @brief Determine whether a string is potentially a valid SQL identifier.
7085 @param s The identifier to be tested.
7086 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
7088 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
7089 need to follow all the rules exactly, such as requiring that the first character not
7092 We allow leading and trailing white space. In between, we do not allow punctuation
7093 (except for underscores and dollar signs), control characters, or embedded white space.
7095 More pedantically we should allow quoted identifiers containing arbitrary characters, but
7096 for the foreseeable future such quoted identifiers are not likely to be an issue.
7098 int is_identifier( const char* s) {
7102 // Skip leading white space
7103 while( isspace( (unsigned char) *s ) )
7107 return 0; // Nothing but white space? Not okay.
7109 // Check each character until we reach white space or
7110 // end-of-string. Letters, digits, underscores, and
7111 // dollar signs are okay. With the exception of periods
7112 // (as in schema.identifier), control characters and other
7113 // punctuation characters are not okay. Anything else
7114 // is okay -- it could for example be part of a multibyte
7115 // UTF8 character such as a letter with diacritical marks,
7116 // and those are allowed.
7118 if( isalnum( (unsigned char) *s )
7122 ; // Fine; keep going
7123 else if( ispunct( (unsigned char) *s )
7124 || iscntrl( (unsigned char) *s ) )
7127 } while( *s && ! isspace( (unsigned char) *s ) );
7129 // If we found any white space in the above loop,
7130 // the rest had better be all white space.
7132 while( isspace( (unsigned char) *s ) )
7136 return 0; // White space was embedded within non-white space
7142 @brief Determine whether to accept a character string as a comparison operator.
7143 @param op The candidate comparison operator.
7144 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
7146 We don't validate the operator for real. We just make sure that it doesn't contain
7147 any semicolons or white space (with special exceptions for a few specific operators).
7148 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
7149 space but it's still not a valid operator, then the database will complain.
7151 Another approach would be to compare the string against a short list of approved operators.
7152 We don't do that because we want to allow custom operators like ">100*", which at this
7153 writing would be difficult or impossible to express otherwise in a JSON query.
7155 int is_good_operator( const char* op ) {
7156 if( !op ) return 0; // Sanity check
7160 if( isspace( (unsigned char) *s ) ) {
7161 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
7162 // and IS NOT DISTINCT FROM.
7163 if( !strcasecmp( op, "similar to" ) )
7165 else if( !strcasecmp( op, "is distinct from" ) )
7167 else if( !strcasecmp( op, "is not distinct from" ) )
7172 else if( ';' == *s )
7180 @name Query Frame Management
7182 The following machinery supports a stack of query frames for use by SELECT().
7184 A query frame caches information about one level of a SELECT query. When we enter
7185 a subquery, we push another query frame onto the stack, and pop it off when we leave.
7187 The query frame stores information about the core class, and about any joined classes
7190 The main purpose is to map table aliases to classes and tables, so that a query can
7191 join to the same table more than once. A secondary goal is to reduce the number of
7192 lookups in the IDL by caching the results.
7196 #define STATIC_CLASS_INFO_COUNT 3
7198 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
7201 @brief Allocate a ClassInfo as raw memory.
7202 @return Pointer to the newly allocated ClassInfo.
7204 Except for the in_use flag, which is used only by the allocation and deallocation
7205 logic, we don't initialize the ClassInfo here.
7207 static ClassInfo* allocate_class_info( void ) {
7208 // In order to reduce the number of mallocs and frees, we return a static
7209 // instance of ClassInfo, if we can find one that we're not already using.
7210 // We rely on the fact that the compiler will implicitly initialize the
7211 // static instances so that in_use == 0.
7214 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7215 if( ! static_class_info[ i ].in_use ) {
7216 static_class_info[ i ].in_use = 1;
7217 return static_class_info + i;
7221 // The static ones are all in use. Malloc one.
7223 return safe_malloc( sizeof( ClassInfo ) );
7227 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
7228 @param info Pointer to the ClassInfo to be cleared.
7230 static void clear_class_info( ClassInfo* info ) {
7235 // Free any malloc'd strings
7237 if( info->alias != info->alias_store )
7238 free( info->alias );
7240 if( info->class_name != info->class_name_store )
7241 free( info->class_name );
7243 free( info->source_def );
7245 info->alias = info->class_name = info->source_def = NULL;
7250 @brief Free a ClassInfo and everything it owns.
7251 @param info Pointer to the ClassInfo to be freed.
7253 static void free_class_info( ClassInfo* info ) {
7258 clear_class_info( info );
7260 // If it's one of the static instances, just mark it as not in use
7263 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7264 if( info == static_class_info + i ) {
7265 static_class_info[ i ].in_use = 0;
7270 // Otherwise it must have been malloc'd, so free it
7276 @brief Populate an already-allocated ClassInfo.
7277 @param info Pointer to the ClassInfo to be populated.
7278 @param alias Alias for the class. If it is NULL, or an empty string, use the class
7280 @param class Name of the class.
7281 @return Zero if successful, or 1 if not.
7283 Populate the ClassInfo with copies of the alias and class name, and with pointers to
7284 the relevant portions of the IDL for the specified class.
7286 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
7289 osrfLogError( OSRF_LOG_MARK,
7290 "%s ERROR: No ClassInfo available to populate", modulename );
7291 info->alias = info->class_name = info->source_def = NULL;
7292 info->class_def = info->fields = info->links = NULL;
7297 osrfLogError( OSRF_LOG_MARK,
7298 "%s ERROR: No class name provided for lookup", modulename );
7299 info->alias = info->class_name = info->source_def = NULL;
7300 info->class_def = info->fields = info->links = NULL;
7304 // Alias defaults to class name if not supplied
7305 if( ! alias || ! alias[ 0 ] )
7308 // Look up class info in the IDL
7309 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
7311 osrfLogError( OSRF_LOG_MARK,
7312 "%s ERROR: Class %s not defined in IDL", modulename, class );
7313 info->alias = info->class_name = info->source_def = NULL;
7314 info->class_def = info->fields = info->links = NULL;
7316 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
7317 osrfLogError( OSRF_LOG_MARK,
7318 "%s ERROR: Class %s is defined as virtual", modulename, class );
7319 info->alias = info->class_name = info->source_def = NULL;
7320 info->class_def = info->fields = info->links = NULL;
7324 osrfHash* links = osrfHashGet( class_def, "links" );
7326 osrfLogError( OSRF_LOG_MARK,
7327 "%s ERROR: No links defined in IDL for class %s", modulename, class );
7328 info->alias = info->class_name = info->source_def = NULL;
7329 info->class_def = info->fields = info->links = NULL;
7333 osrfHash* fields = osrfHashGet( class_def, "fields" );
7335 osrfLogError( OSRF_LOG_MARK,
7336 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
7337 info->alias = info->class_name = info->source_def = NULL;
7338 info->class_def = info->fields = info->links = NULL;
7342 char* source_def = oilsGetRelation( class_def );
7346 // We got everything we need, so populate the ClassInfo
7347 if( strlen( alias ) > ALIAS_STORE_SIZE )
7348 info->alias = strdup( alias );
7350 strcpy( info->alias_store, alias );
7351 info->alias = info->alias_store;
7354 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
7355 info->class_name = strdup( class );
7357 strcpy( info->class_name_store, class );
7358 info->class_name = info->class_name_store;
7361 info->source_def = source_def;
7363 info->class_def = class_def;
7364 info->links = links;
7365 info->fields = fields;
7370 #define STATIC_FRAME_COUNT 3
7372 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
7375 @brief Allocate a QueryFrame as raw memory.
7376 @return Pointer to the newly allocated QueryFrame.
7378 Except for the in_use flag, which is used only by the allocation and deallocation
7379 logic, we don't initialize the QueryFrame here.
7381 static QueryFrame* allocate_frame( void ) {
7382 // In order to reduce the number of mallocs and frees, we return a static
7383 // instance of QueryFrame, if we can find one that we're not already using.
7384 // We rely on the fact that the compiler will implicitly initialize the
7385 // static instances so that in_use == 0.
7388 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7389 if( ! static_frame[ i ].in_use ) {
7390 static_frame[ i ].in_use = 1;
7391 return static_frame + i;
7395 // The static ones are all in use. Malloc one.
7397 return safe_malloc( sizeof( QueryFrame ) );
7401 @brief Free a QueryFrame, and all the memory it owns.
7402 @param frame Pointer to the QueryFrame to be freed.
7404 static void free_query_frame( QueryFrame* frame ) {
7409 clear_class_info( &frame->core );
7411 // Free the join list
7413 ClassInfo* info = frame->join_list;
7416 free_class_info( info );
7420 frame->join_list = NULL;
7423 // If the frame is a static instance, just mark it as unused
7425 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7426 if( frame == static_frame + i ) {
7427 static_frame[ i ].in_use = 0;
7432 // Otherwise it must have been malloc'd, so free it
7438 @brief Search a given QueryFrame for a specified alias.
7439 @param frame Pointer to the QueryFrame to be searched.
7440 @param target The alias for which to search.
7441 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7443 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7444 if( ! frame || ! target ) {
7448 ClassInfo* found_class = NULL;
7450 if( !strcmp( target, frame->core.alias ) )
7451 return &(frame->core);
7453 ClassInfo* curr_class = frame->join_list;
7454 while( curr_class ) {
7455 if( strcmp( target, curr_class->alias ) )
7456 curr_class = curr_class->next;
7458 found_class = curr_class;
7468 @brief Push a new (blank) QueryFrame onto the stack.
7470 static void push_query_frame( void ) {
7471 QueryFrame* frame = allocate_frame();
7472 frame->join_list = NULL;
7473 frame->next = curr_query;
7475 // Initialize the ClassInfo for the core class
7476 ClassInfo* core = &frame->core;
7477 core->alias = core->class_name = core->source_def = NULL;
7478 core->class_def = core->fields = core->links = NULL;
7484 @brief Pop a QueryFrame off the stack and destroy it.
7486 static void pop_query_frame( void ) {
7491 QueryFrame* popped = curr_query;
7492 curr_query = popped->next;
7494 free_query_frame( popped );
7498 @brief Populate the ClassInfo for the core class.
7499 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7500 class name as an alias.
7501 @param class_name Name of the core class.
7502 @return Zero if successful, or 1 if not.
7504 Populate the ClassInfo of the core class with copies of the alias and class name, and
7505 with pointers to the relevant portions of the IDL for the core class.
7507 static int add_query_core( const char* alias, const char* class_name ) {
7510 if( ! curr_query ) {
7511 osrfLogError( OSRF_LOG_MARK,
7512 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7514 } else if( curr_query->core.alias ) {
7515 osrfLogError( OSRF_LOG_MARK,
7516 "%s ERROR: Core class %s already populated as %s",
7517 modulename, curr_query->core.class_name, curr_query->core.alias );
7521 build_class_info( &curr_query->core, alias, class_name );
7522 if( curr_query->core.alias )
7525 osrfLogError( OSRF_LOG_MARK,
7526 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7532 @brief Search the current QueryFrame for a specified alias.
7533 @param target The alias for which to search.
7534 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7536 static inline ClassInfo* search_alias( const char* target ) {
7537 return search_alias_in_frame( curr_query, target );
7541 @brief Search all levels of query for a specified alias, starting with the current query.
7542 @param target The alias for which to search.
7543 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7545 static ClassInfo* search_all_alias( const char* target ) {
7546 ClassInfo* found_class = NULL;
7547 QueryFrame* curr_frame = curr_query;
7549 while( curr_frame ) {
7550 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7553 curr_frame = curr_frame->next;
7560 @brief Add a class to the list of classes joined to the current query.
7561 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7562 the class name as an alias.
7563 @param classname The name of the class to be added.
7564 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7566 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7568 if( ! classname || ! *classname ) { // sanity check
7569 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7576 const ClassInfo* conflict = search_alias( alias );
7578 osrfLogError( OSRF_LOG_MARK,
7579 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7580 modulename, alias, conflict->class_name );
7584 ClassInfo* info = allocate_class_info();
7586 if( build_class_info( info, alias, classname ) ) {
7587 free_class_info( info );
7591 // Add the new ClassInfo to the join list of the current QueryFrame
7592 info->next = curr_query->join_list;
7593 curr_query->join_list = info;
7599 @brief Destroy all nodes on the query stack.
7601 static void clear_query_stack( void ) {
7607 @brief Implement the set_audit_info method.
7608 @param ctx Pointer to the method context.
7609 @return Zero if successful, or -1 if not.
7611 Issue a SAVEPOINT to the database server.
7616 - workstation id (int)
7618 If user id is not provided the authkey will be used.
7619 For PCRUD the authkey is always used, even if a user is provided.
7621 int setAuditInfo( osrfMethodContext* ctx ) {
7622 if(osrfMethodVerifyContext( ctx )) {
7623 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
7627 // Get the user id from the parameters
7628 const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7630 if( enforce_pcrud || !user_id ) {
7631 timeout_needs_resetting = 1;
7632 const jsonObject* user = verifyUserPCRUD( ctx );
7635 osrfAppRespondComplete( ctx, NULL );
7639 // Not PCRUD and have a user_id?
7640 int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7641 osrfAppRespondComplete( ctx, NULL );
7646 @brief Save a audit info
7647 @param ctx Pointer to the method context.
7648 @param user_id User ID to write as a string
7649 @param ws_id Workstation ID to write as a string
7651 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7652 if( ctx && ctx->session ) {
7653 osrfAppSession* session = ctx->session;
7655 osrfHash* cache = session->userData;
7657 // If the session doesn't already have a hash, create one. Make sure
7658 // that the application session frees the hash when it terminates.
7659 if( NULL == cache ) {
7660 session->userData = cache = osrfNewHash();
7661 osrfHashSetCallback( cache, &sessionDataFree );
7662 ctx->session->userDataFree = &userDataFree;
7665 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7667 osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7669 int errnum = dbi_conn_error( writehandle, &msg );
7672 "%s: Error setting auditor information: %d %s",
7675 msg ? msg : "(No description available)"
7677 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7678 "osrfMethodException", ctx->request, "Error setting auditor info" );
7679 if( !oilsIsDBConnected( writehandle ))
7680 osrfAppSessionPanic( ctx->session );
7683 dbi_result_free( result );
7690 @brief Remove all but safe character from savepoint name
7691 @param sp User-supplied savepoint name
7692 @return sanitized savepoint name, or NULL
7694 The caller is expected to free the returned string. Note that
7695 this function exists only because we can't use PQescapeLiteral
7696 without either forking libdbi or abandoning it.
7698 static char* _sanitize_savepoint_name( const char* sp ) {
7700 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_";
7702 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7703 // and the default value of NAMEDATALEN is 64; that should be long enough
7704 // for our purposes, and it's unlikely that anyone is going to recompile
7705 // PostgreSQL to have a smaller value, so cap the identifier name
7706 // accordingly to avoid the remote chance that someone manages to pass in a
7707 // 12GB savepoint name
7708 const int MAX_LITERAL_NAMELEN = 63;
7711 if (len > MAX_LITERAL_NAMELEN) {
7712 len = MAX_LITERAL_NAMELEN;
7715 char* safeSpName = safe_malloc( len + 1 );
7719 for (j = 0; j < len; j++) {
7720 found = strchr(safe_chars, sp[j]);
7722 safeSpName[ i++ ] = found[0];
7725 safeSpName[ i ] = '\0';
7730 @brief Remove all but safe character from TZ name
7731 @param tz User-supplied TZ name
7732 @return sanitized TZ name, or NULL
7734 The caller is expected to free the returned string. Note that
7735 this function exists only because we can't use PQescapeLiteral
7736 without either forking libdbi or abandoning it.
7738 static char* _sanitize_tz_name( const char* tz ) {
7740 if (NULL == tz) return NULL;
7742 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_/-+";
7744 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7745 // and the default value of NAMEDATALEN is 64; that should be long enough
7746 // for our purposes, and it's unlikely that anyone is going to recompile
7747 // PostgreSQL to have a smaller value, so cap the identifier name
7748 // accordingly to avoid the remote chance that someone manages to pass in a
7749 // 12GB savepoint name
7750 const int MAX_LITERAL_NAMELEN = 63;
7753 if (len > MAX_LITERAL_NAMELEN) {
7754 len = MAX_LITERAL_NAMELEN;
7757 char* safeSpName = safe_malloc( len + 1 );
7761 for (j = 0; j < len; j++) {
7762 found = strchr(safe_chars, tz[j]);
7764 safeSpName[ i++ ] = found[0];
7767 safeSpName[ i ] = '\0';