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;
1462 // If we are /not/ in anonymous mode
1463 if( strcmp( "ANONYMOUS", auth ) ) {
1464 // See if we have the same authkey, and a user object,
1465 // locally cached from a previous call
1466 const char* cached_authkey = getAuthkey( ctx );
1467 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1468 const jsonObject* cached_user = getUserLogin( ctx );
1473 // We have no matching authentication data in the cache. Authenticate from scratch.
1474 jsonObject* auth_object = jsonNewObject( auth );
1476 // Fetch the user object from the authentication server
1477 user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve", auth_object );
1478 jsonObjectFree( auth_object );
1480 if( !user->classname || strcmp(user->classname, "au" )) {
1482 growing_buffer* msg = buffer_init( 128 );
1485 "%s: permacrud received a bad auth token: %s",
1490 char* m = buffer_release( msg );
1491 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1495 jsonObjectFree( user );
1497 } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
1498 // Failed to set audit information - But note that write_audit_info already set error information.
1499 jsonObjectFree( user );
1504 } else if ( anon_ok ) { // we /are/ (attempting to be) anonymous
1505 user = jsonNewObjectType(JSON_ARRAY);
1506 jsonObjectSetClass( user, "aou" );
1507 oilsFMSetString(user, "id", "-1");
1510 setUserLogin( ctx, user );
1511 setAuthkey( ctx, auth );
1513 // Allow ourselves up to a second before we have to reset the login timeout.
1514 // It would be nice to use some fraction of the timeout interval enforced by the
1515 // authentication server, but that value is not readily available at this point.
1516 // Instead, we use a conservative default interval.
1517 time_next_reset = time( NULL ) + 1;
1523 @brief For PCRUD: Determine whether the current user may access the current row.
1524 @param ctx Pointer to the method context.
1525 @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1526 @param obj Pointer to the row being potentially accessed.
1527 @return 1 if access is permitted, or 0 if it isn't.
1529 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1531 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1533 dbhandle = writehandle;
1535 // Figure out what class and method are involved
1536 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1537 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1540 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1541 if (rs_size_from_hash) {
1542 rs_size = *rs_size_from_hash;
1543 osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1547 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1548 // contexts we will do another lookup of the current row, even if we already have a
1549 // previously fetched row image, because the row image in hand may not include the
1550 // foreign key(s) that we need.
1552 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1553 // but they aren't implemented yet.
1556 if( *method_type == 's' || *method_type == 'i' ) {
1557 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1559 } else if( *method_type == 'u' || *method_type == 'd' ) {
1560 fetch = 1; // MUST go to the db for the object for update and delete
1563 // In retrieve or search ONLY we allow anon. Later perm checks will fail as they should,
1564 // in the face of a fake user but required permissions.
1566 if( *method_type == 'r' )
1569 // Get the appropriate permacrud entry from the IDL, depending on method type
1570 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1572 // No permacrud for this method type on this class
1574 growing_buffer* msg = buffer_init( 128 );
1577 "%s: %s on class %s has no permacrud IDL entry",
1579 osrfHashGet( method_metadata, "methodtype" ),
1580 osrfHashGet( class, "classname" )
1583 char* m = buffer_release( msg );
1584 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1585 "osrfMethodException", ctx->request, m );
1592 // Get the user id, and make sure the user is logged in
1593 const jsonObject* user = verifyUserPCRUDfull( ctx, anon_ok );
1595 return 0; // Not logged in or anon? No access.
1597 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1599 // Get a list of permissions from the permacrud entry.
1600 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1601 if( permission->size == 0 ) {
1604 "No permissions required for this action (class %s), passing through",
1605 osrfHashGet(class, "classname")
1610 // But, if there are perms and the user is anonymous ... FAIL
1614 // Build a list of org units that own the row. This is fairly convoluted because there
1615 // are several different ways that an org unit may own the row, as defined by the
1618 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1619 // identifying an owning org_unit..
1620 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1622 // Foreign context adds a layer of indirection. The row points to some other row that
1623 // an org unit may own. The "jump" attribute, if present, adds another layer of
1625 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1627 // The following string array stores the list of org units. (We don't have a thingie
1628 // for storing lists of integers, so we fake it with a list of strings.)
1629 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1631 const char* context_org = NULL;
1632 const char* pkey = NULL;
1633 jsonObject *param = NULL;
1634 const char* perm = NULL;
1638 const char* pkey_value = NULL;
1639 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1640 // If the global_required attribute is present and true, then the only owning
1641 // org unit is the root org unit, i.e. the one with no parent.
1642 osrfLogDebug( OSRF_LOG_MARK,
1643 "global-level permissions required, fetching top of the org tree" );
1645 // no need to check perms for org tree root retrieval
1646 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1647 // check for perm at top of org tree
1648 const char* org_tree_root_id = org_tree_root( ctx );
1649 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1651 if( org_tree_root_id ) {
1652 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1653 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1655 osrfStringArrayFree( context_org_array );
1660 // If the global_required attribute is absent or false, then we look for
1661 // local and/or foreign context. In order to find the relevant foreign
1662 // keys, we must either read the relevant row from the database, or look at
1663 // the image of the row that we already have in memory.
1665 // Even if we have an image of the row in memory, that image may not include the
1666 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1667 // of the row to make sure that we have what we need.
1669 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1670 "fetching context org ids" );
1672 pkey = osrfHashGet( class, "primarykey" );
1675 // There is no primary key, so we can't do a fresh lookup. Use the row
1676 // image that we already have. If it doesn't have everything we need, too bad.
1678 param = jsonObjectClone( obj );
1679 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1680 } else if( obj->classname ) {
1681 pkey_value = oilsFMGetStringConst( obj, pkey );
1683 param = jsonObjectClone( obj );
1684 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1687 pkey_value = jsonObjectGetString( obj );
1689 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1690 "of %s and retrieving from the database", pkey_value );
1694 // Fetch the row so that we can look at the foreign key(s)
1695 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1696 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1697 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1698 jsonObjectFree( _tmp_params );
1699 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1701 param = jsonObjectExtractIndex( _list, 0 );
1702 jsonObjectFree( _list );
1708 // The row doesn't exist. Complain, and deny access.
1709 osrfLogDebug( OSRF_LOG_MARK,
1710 "Object not found in the database with primary key %s of %s",
1713 growing_buffer* msg = buffer_init( 128 );
1716 "%s: no object found with primary key %s of %s",
1722 char* m = buffer_release( msg );
1723 osrfAppSessionStatus(
1725 OSRF_STATUS_INTERNALSERVERERROR,
1726 "osrfMethodException",
1735 if( local_context && local_context->size > 0 ) {
1736 // The IDL provides a list of column names for the foreign keys denoting
1737 // local context, i.e. columns identifying owing org units directly. Look up
1738 // the value of each one, and if it isn't null, add it to the list of org units.
1739 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1740 local_context->size );
1742 const char* lcontext = NULL;
1743 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1744 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1745 if( fkey_value ) { // if not null
1746 osrfStringArrayAdd( context_org_array, fkey_value );
1749 "adding class-local field %s (value: %s) to the context org list",
1751 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1757 if( foreign_context ) {
1758 unsigned long class_count = osrfHashGetCount( foreign_context );
1759 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1761 if( class_count > 0 ) {
1763 // The IDL provides a list of foreign key columns pointing to rows that
1764 // an org unit may own. Follow each link, identify the owning org unit,
1765 // and add it to the list.
1766 osrfHash* fcontext = NULL;
1767 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1768 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1769 // For each class to which a foreign key points:
1770 const char* class_name = osrfHashIteratorKey( class_itr );
1771 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1775 "%d foreign context fields(s) specified for class %s",
1776 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1780 // Get the name of the key field in the foreign table
1781 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1783 // Get the value of the foreign key pointing to the foreign table
1784 char* foreign_pkey_value =
1785 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1786 if( !foreign_pkey_value )
1787 continue; // Foreign key value is null; skip it
1789 // Look up the row to which the foreign key points
1790 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1792 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1793 jsonObject* _list = doFieldmapperSearch(
1794 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1795 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1797 jsonObject* _fparam = NULL;
1798 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1799 _fparam = jsonObjectExtractIndex( _list, 0 );
1801 jsonObjectFree( _tmp_params );
1802 jsonObjectFree( _list );
1804 // At this point _fparam either points to the row identified by the
1805 // foreign key, or it's NULL (no such row found).
1807 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1809 const char* bad_class = NULL; // For noting failed lookups
1811 bad_class = class_name; // Referenced row not found
1812 else if( jump_list ) {
1813 // Follow a chain of rows, linked by foreign keys, to find an owner
1814 const char* flink = NULL;
1816 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1817 // For each entry in the jump list. Each entry (i.e. flink) is
1818 // the name of a foreign key column in the current row.
1820 // From the IDL, get the linkage information for the next jump
1821 osrfHash* foreign_link_hash =
1822 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1824 // Get the class metadata for the class
1825 // to which the foreign key points
1826 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1827 osrfHashGet( foreign_link_hash, "class" ));
1829 // Get the name of the referenced key of that class
1830 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1832 // Get the value of the foreign key pointing to that class
1833 free( foreign_pkey_value );
1834 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1835 if( !foreign_pkey_value )
1836 break; // Foreign key is null; quit looking
1838 // Build a WHERE clause for the lookup
1839 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1842 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1843 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1844 _tmp_params, NULL, &err );
1845 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1847 // Get the resulting row
1848 jsonObjectFree( _fparam );
1849 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1850 _fparam = jsonObjectExtractIndex( _list, 0 );
1852 // Referenced row not found
1854 bad_class = osrfHashGet( foreign_link_hash, "class" );
1857 jsonObjectFree( _tmp_params );
1858 jsonObjectFree( _list );
1864 // We had a foreign key pointing to such-and-such a row, but then
1865 // we couldn't fetch that row. The data in the database are in an
1866 // inconsistent state; the database itself may even be corrupted.
1867 growing_buffer* msg = buffer_init( 128 );
1870 "%s: no object of class %s found with primary key %s of %s",
1874 foreign_pkey_value ? foreign_pkey_value : "(null)"
1877 char* m = buffer_release( msg );
1878 osrfAppSessionStatus(
1880 OSRF_STATUS_INTERNALSERVERERROR,
1881 "osrfMethodException",
1887 osrfHashIteratorFree( class_itr );
1888 free( foreign_pkey_value );
1889 jsonObjectFree( param );
1894 free( foreign_pkey_value );
1897 // Examine each context column of the foreign row,
1898 // and add its value to the list of org units.
1900 const char* foreign_field = NULL;
1901 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1902 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1903 osrfStringArrayAdd( context_org_array,
1904 oilsFMGetStringConst( _fparam, foreign_field ));
1905 osrfLogDebug( OSRF_LOG_MARK,
1906 "adding foreign class %s field %s (value: %s) "
1907 "to the context org list",
1910 osrfStringArrayGetString(
1911 context_org_array, context_org_array->size - 1 )
1915 jsonObjectFree( _fparam );
1919 osrfHashIteratorFree( class_itr );
1924 // If there is an owning_user attached to the action, we allow that user and users with
1925 // object perms on the object. CREATE can't use this. We only do this when we're not
1926 // ignoring object perms.
1927 char* owning_user_field = osrfHashGet( pcrud, "owning_user" );
1929 *method_type != 'c' &&
1930 (!str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) || // Always honor
1933 if (owning_user_field) { // see if we can short-cut by comparing the owner to the requestor
1935 if (!param) { // We didn't get it during the context lookup
1936 pkey = osrfHashGet( class, "primarykey" );
1939 // There is no primary key, so we can't do a fresh lookup. Use the row
1940 // image that we already have. If it doesn't have everything we need, too bad.
1942 param = jsonObjectClone( obj );
1943 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1944 } else if( obj->classname ) {
1945 pkey_value = oilsFMGetStringConst( obj, pkey );
1947 param = jsonObjectClone( obj );
1948 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1951 pkey_value = jsonObjectGetString( obj );
1953 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1954 "of %s and retrieving from the database", pkey_value );
1958 // Fetch the row so that we can look at the foreign key(s)
1959 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1960 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1961 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1962 jsonObjectFree( _tmp_params );
1963 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1965 param = jsonObjectExtractIndex( _list, 0 );
1966 jsonObjectFree( _list );
1971 // The row doesn't exist. Complain, and deny access.
1972 osrfLogDebug( OSRF_LOG_MARK,
1973 "Object not found in the database with primary key %s of %s",
1976 growing_buffer* msg = buffer_init( 128 );
1979 "%s: no object found with primary key %s of %s",
1985 char* m = buffer_release( msg );
1986 osrfAppSessionStatus(
1988 OSRF_STATUS_INTERNALSERVERERROR,
1989 "osrfMethodException",
1998 int ownerid = atoi( oilsFMGetStringConst( param, owning_user_field ) );
2000 // Allow the owner to do whatever
2001 if (ownerid == userid)
2008 (perm = osrfStringArrayGetString(permission, i++)) &&
2009 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms"))
2015 "Checking object permission [%s] for user %d "
2016 "on object %s (class %s)",
2020 osrfHashGet( class, "classname" )
2023 result = dbi_conn_queryf(
2025 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s') AS has_perm;",
2028 osrfHashGet( class, "classname" ),
2035 "Received a result for object permission [%s] "
2036 "for user %d on object %s (class %s)",
2040 osrfHashGet( class, "classname" )
2043 if( dbi_result_first_row( result )) {
2044 jsonObject* return_val = oilsMakeJSONFromResult( result );
2045 const char* has_perm = jsonObjectGetString(
2046 jsonObjectGetKeyConst( return_val, "has_perm" ));
2050 "Status of object permission [%s] for user %d "
2051 "on object %s (class %s) is %s",
2055 osrfHashGet(class, "classname"),
2059 if( *has_perm == 't' )
2061 jsonObjectFree( return_val );
2064 dbi_result_free( result );
2069 int errnum = dbi_conn_error( writehandle, &msg );
2070 osrfLogWarning( OSRF_LOG_MARK,
2071 "Unable to call check object permissions: %d, %s",
2072 errnum, msg ? msg : "(No description available)" );
2073 if( !oilsIsDBConnected( writehandle ))
2074 osrfAppSessionPanic( ctx->session );
2079 // For every combination of permission and context org unit: call a stored procedure
2080 // to determine if the user has this permission in the context of this org unit.
2081 // If the answer is yes at any point, then we're done, and the user has permission.
2082 // In other words permissions are additive.
2084 while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
2087 osrfStringArray* pcache = NULL;
2088 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
2089 pcache = getPermLocationCache(ctx, perm);
2092 pcache = osrfNewStringArray(0);
2094 result = dbi_conn_queryf(
2096 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
2104 "Received a result for permission [%s] for user %d",
2109 if( dbi_result_first_row( result )) {
2111 jsonObject* return_val = oilsMakeJSONFromResult( result );
2112 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
2113 jsonObjectFree( return_val );
2114 } while( dbi_result_next_row( result ));
2116 setPermLocationCache(ctx, perm, pcache);
2119 dbi_result_free( result );
2125 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
2127 if (rs_size > perm_at_threshold) {
2128 if (osrfStringArrayContains( pcache, context_org )) {
2136 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
2138 !str_is_true( osrfHashGet(pcrud, "global_required") ) ||
2139 osrfHashGet(pcrud, "owning_user")
2144 "Checking object permission [%s] for user %d "
2145 "on object %s (class %s) at org %d",
2149 osrfHashGet( class, "classname" ),
2153 result = dbi_conn_queryf(
2155 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
2158 osrfHashGet( class, "classname" ),
2166 "Received a result for object permission [%s] "
2167 "for user %d on object %s (class %s) at org %d",
2171 osrfHashGet( class, "classname" ),
2175 if( dbi_result_first_row( result )) {
2176 jsonObject* return_val = oilsMakeJSONFromResult( result );
2177 const char* has_perm = jsonObjectGetString(
2178 jsonObjectGetKeyConst( return_val, "has_perm" ));
2182 "Status of object permission [%s] for user %d "
2183 "on object %s (class %s) at org %d is %s",
2187 osrfHashGet(class, "classname"),
2192 if( *has_perm == 't' )
2194 jsonObjectFree( return_val );
2197 dbi_result_free( result );
2202 int errnum = dbi_conn_error( writehandle, &msg );
2203 osrfLogWarning( OSRF_LOG_MARK,
2204 "Unable to call check object permissions: %d, %s",
2205 errnum, msg ? msg : "(No description available)" );
2206 if( !oilsIsDBConnected( writehandle ))
2207 osrfAppSessionPanic( ctx->session );
2211 if (rs_size > perm_at_threshold) break;
2213 osrfLogDebug( OSRF_LOG_MARK,
2214 "Checking non-object permission [%s] for user %d at org %d",
2215 perm, userid, atoi(context_org) );
2216 result = dbi_conn_queryf(
2218 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
2225 osrfLogDebug( OSRF_LOG_MARK,
2226 "Received a result for permission [%s] for user %d at org %d",
2227 perm, userid, atoi( context_org ));
2228 if( dbi_result_first_row( result )) {
2229 jsonObject* return_val = oilsMakeJSONFromResult( result );
2230 const char* has_perm = jsonObjectGetString(
2231 jsonObjectGetKeyConst( return_val, "has_perm" ));
2232 osrfLogDebug( OSRF_LOG_MARK,
2233 "Status of permission [%s] for user %d at org %d is [%s]",
2234 perm, userid, atoi( context_org ), has_perm );
2235 if( *has_perm == 't' )
2237 jsonObjectFree( return_val );
2240 dbi_result_free( result );
2245 int errnum = dbi_conn_error( writehandle, &msg );
2246 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
2247 errnum, msg ? msg : "(No description available)" );
2248 if( !oilsIsDBConnected( writehandle ))
2249 osrfAppSessionPanic( ctx->session );
2258 osrfStringArrayFree( context_org_array );
2264 @brief Look up the root of the org_unit tree.
2265 @param ctx Pointer to the method context.
2266 @return The id of the root org unit, as a character string.
2268 Query actor.org_unit where parent_ou is null, and return the id as a string.
2270 This function assumes that there is only one root org unit, i.e. that we
2271 have a single tree, not a forest.
2273 The calling code is responsible for freeing the returned string.
2275 static const char* org_tree_root( osrfMethodContext* ctx ) {
2277 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
2278 static time_t last_lookup_time = 0;
2279 time_t current_time = time( NULL );
2281 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2282 // We successfully looked this up less than an hour ago.
2283 // It's not likely to have changed since then.
2284 return strdup( cached_root_id );
2286 last_lookup_time = current_time;
2289 jsonObject* where_clause = single_hash( "parent_ou", NULL );
2290 jsonObject* result = doFieldmapperSearch(
2291 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2292 jsonObjectFree( where_clause );
2294 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2297 jsonObjectFree( result );
2299 growing_buffer* msg = buffer_init( 128 );
2300 OSRF_BUFFER_ADD( msg, modulename );
2301 OSRF_BUFFER_ADD( msg,
2302 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2304 char* m = buffer_release( msg );
2305 osrfAppSessionStatus( ctx->session,
2306 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2309 cached_root_id[ 0 ] = '\0';
2313 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2314 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2316 strcpy( cached_root_id, root_org_unit_id );
2317 jsonObjectFree( result );
2318 return cached_root_id;
2322 @brief Create a JSON_HASH with a single key/value pair.
2323 @param key The key of the key/value pair.
2324 @param value the value of the key/value pair.
2325 @return Pointer to a newly created jsonObject of type JSON_HASH.
2327 The value of the key/value is either a string or (if @a value is NULL) a null.
2329 static jsonObject* single_hash( const char* key, const char* value ) {
2331 if( ! key ) key = "";
2333 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2334 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2339 int doCreate( osrfMethodContext* ctx ) {
2340 if(osrfMethodVerifyContext( ctx )) {
2341 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2346 timeout_needs_resetting = 1;
2348 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2349 jsonObject* target = NULL;
2350 jsonObject* options = NULL;
2352 if( enforce_pcrud ) {
2353 target = jsonObjectGetIndex( ctx->params, 1 );
2354 options = jsonObjectGetIndex( ctx->params, 2 );
2356 target = jsonObjectGetIndex( ctx->params, 0 );
2357 options = jsonObjectGetIndex( ctx->params, 1 );
2360 if( !verifyObjectClass( ctx, target )) {
2361 osrfAppRespondComplete( ctx, NULL );
2365 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2367 const char* trans_id = getXactId( ctx );
2369 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2371 osrfAppSessionStatus(
2373 OSRF_STATUS_BADREQUEST,
2374 "osrfMethodException",
2376 "No active transaction -- required for CREATE"
2378 osrfAppRespondComplete( ctx, NULL );
2382 // The following test is harmless but redundant. If a class is
2383 // readonly, we don't register a create method for it.
2384 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2385 osrfAppSessionStatus(
2387 OSRF_STATUS_BADREQUEST,
2388 "osrfMethodException",
2390 "Cannot INSERT readonly class"
2392 osrfAppRespondComplete( ctx, NULL );
2396 // Set the last_xact_id
2397 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2399 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2400 trans_id, target->classname, index);
2401 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2404 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2406 dbhandle = writehandle;
2408 osrfHash* fields = osrfHashGet( meta, "fields" );
2409 char* pkey = osrfHashGet( meta, "primarykey" );
2410 char* seq = osrfHashGet( meta, "sequence" );
2412 growing_buffer* table_buf = buffer_init( 128 );
2413 growing_buffer* col_buf = buffer_init( 128 );
2414 growing_buffer* val_buf = buffer_init( 128 );
2416 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2417 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2418 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2419 buffer_add( val_buf,"VALUES (" );
2423 osrfHash* field = NULL;
2424 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2425 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2427 const char* field_name = osrfHashIteratorKey( field_itr );
2429 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2432 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2435 if( field_object && field_object->classname ) {
2436 value = oilsFMGetString(
2438 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2440 } else if( field_object && JSON_BOOL == field_object->type ) {
2441 if( jsonBoolIsTrue( field_object ) )
2442 value = strdup( "t" );
2444 value = strdup( "f" );
2446 value = jsonObjectToSimpleString( field_object );
2452 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2453 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2456 buffer_add( col_buf, field_name );
2458 if( !field_object || field_object->type == JSON_NULL ) {
2459 buffer_add( val_buf, "DEFAULT" );
2461 } else if( !strcmp( get_primitive( field ), "number" )) {
2462 const char* numtype = get_datatype( field );
2463 if( !strcmp( numtype, "INT8" )) {
2464 buffer_fadd( val_buf, "%lld", atoll( value ));
2466 } else if( !strcmp( numtype, "INT" )) {
2467 buffer_fadd( val_buf, "%d", atoi( value ));
2469 } else if( !strcmp( numtype, "NUMERIC" )) {
2470 buffer_fadd( val_buf, "%f", atof( value ));
2473 if( dbi_conn_quote_string( writehandle, &value )) {
2474 OSRF_BUFFER_ADD( val_buf, value );
2477 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2478 osrfAppSessionStatus(
2480 OSRF_STATUS_INTERNALSERVERERROR,
2481 "osrfMethodException",
2483 "Error quoting string -- please see the error log for more details"
2486 buffer_free( table_buf );
2487 buffer_free( col_buf );
2488 buffer_free( val_buf );
2489 osrfAppRespondComplete( ctx, NULL );
2497 osrfHashIteratorFree( field_itr );
2499 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2500 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2502 char* table_str = buffer_release( table_buf );
2503 char* col_str = buffer_release( col_buf );
2504 char* val_str = buffer_release( val_buf );
2505 growing_buffer* sql = buffer_init( 128 );
2506 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2511 char* query = buffer_release( sql );
2513 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2515 jsonObject* obj = NULL;
2518 dbi_result result = dbi_conn_query( writehandle, query );
2520 obj = jsonNewObject( NULL );
2522 int errnum = dbi_conn_error( writehandle, &msg );
2525 "%s ERROR inserting %s object using query [%s]: %d %s",
2527 osrfHashGet(meta, "fieldmapper"),
2530 msg ? msg : "(No description available)"
2532 osrfAppSessionStatus(
2534 OSRF_STATUS_INTERNALSERVERERROR,
2535 "osrfMethodException",
2537 "INSERT error -- please see the error log for more details"
2539 if( !oilsIsDBConnected( writehandle ))
2540 osrfAppSessionPanic( ctx->session );
2543 dbi_result_free( result );
2545 char* id = oilsFMGetString( target, pkey );
2547 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2548 growing_buffer* _id = buffer_init( 10 );
2549 buffer_fadd( _id, "%lld", new_id );
2550 id = buffer_release( _id );
2553 // Find quietness specification, if present
2554 const char* quiet_str = NULL;
2556 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2558 quiet_str = jsonObjectGetString( quiet_obj );
2561 if( str_is_true( quiet_str )) { // if quietness is specified
2562 obj = jsonNewObject( id );
2566 // Fetch the row that we just inserted, so that we can return it to the client
2567 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2568 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2571 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2575 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2577 jsonObjectFree( list );
2578 jsonObjectFree( where_clause );
2585 osrfAppRespondComplete( ctx, obj );
2586 jsonObjectFree( obj );
2591 @brief Implement the retrieve method.
2592 @param ctx Pointer to the method context.
2593 @param err Pointer through which to return an error code.
2594 @return If successful, a pointer to the result to be returned to the client;
2597 From the method's class, fetch a row with a specified value in the primary key. This
2598 method relies on the database design convention that a primary key consists of a single
2602 - authkey (PCRUD only)
2603 - value of the primary key for the desired row, for building the WHERE clause
2604 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2606 Return to client: One row from the query.
2608 int doRetrieve( osrfMethodContext* ctx ) {
2609 if(osrfMethodVerifyContext( ctx )) {
2610 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2615 timeout_needs_resetting = 1;
2620 if( enforce_pcrud ) {
2625 // Get the class metadata
2626 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2628 // Get the value of the primary key, from a method parameter
2629 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2633 "%s retrieving %s object with primary key value of %s",
2635 osrfHashGet( class_def, "fieldmapper" ),
2636 jsonObjectGetString( id_obj )
2639 // Build a WHERE clause based on the key value
2640 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2643 osrfHashGet( class_def, "primarykey" ), // name of key column
2644 jsonObjectClone( id_obj ) // value of key column
2647 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2651 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2653 jsonObjectFree( where_clause );
2655 osrfAppRespondComplete( ctx, NULL );
2659 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2660 jsonObjectFree( list );
2662 if( enforce_pcrud ) {
2663 // no result, skip this entirely
2664 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2665 jsonObjectFree( obj );
2667 growing_buffer* msg = buffer_init( 128 );
2668 OSRF_BUFFER_ADD( msg, modulename );
2669 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2671 char* m = buffer_release( msg );
2672 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2676 osrfAppRespondComplete( ctx, NULL );
2681 // doFieldmapperSearch() now does the responding for us
2682 //osrfAppRespondComplete( ctx, obj );
2683 osrfAppRespondComplete( ctx, NULL );
2685 jsonObjectFree( obj );
2690 @brief Translate a numeric value to a string representation for the database.
2691 @param field Pointer to the IDL field definition.
2692 @param value Pointer to a jsonObject holding the value of a field.
2693 @return Pointer to a newly allocated string.
2695 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2696 its contents are numeric. A non-numeric string is likely to result in invalid SQL.
2698 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2700 The calling code is responsible for freeing the resulting string by calling free().
2702 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2703 growing_buffer* val_buf = buffer_init( 32 );
2705 // If the value is a number and the DB field is numeric, no quotes needed
2706 if( value->type == JSON_NUMBER && !strcmp( get_primitive( field ), "number") ) {
2707 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2709 // Presumably this was really intended to be a string, so quote it
2710 char* str = jsonObjectToSimpleString( value );
2711 if( dbi_conn_quote_string( dbhandle, &str )) {
2712 OSRF_BUFFER_ADD( val_buf, str );
2715 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2717 buffer_free( val_buf );
2722 return buffer_release( val_buf );
2725 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2726 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2728 char* field_transform = searchFieldTransform( class_alias, field, node );
2729 if( ! field_transform )
2733 char* in_list = searchINList(field, node, op, ctx);
2737 growing_buffer* sql_buf = buffer_init( 32 );
2738 buffer_add( sql_buf, field_transform );
2741 buffer_add( sql_buf, " IN (" );
2742 } else if( !strcasecmp( op,"not in" )) {
2743 buffer_add( sql_buf, " NOT IN (" );
2745 buffer_add( sql_buf, " IN (" );
2748 buffer_add( sql_buf, in_list);
2749 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2751 free(field_transform);
2754 return buffer_release( sql_buf );
2757 static char* searchINList( osrfHash* field,
2758 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2759 growing_buffer* sql_buf = buffer_init( 32 );
2761 const jsonObject* local_node = node;
2762 if( local_node->type == JSON_HASH ) { // may be the case that the node tranforms the field
2763 // if so, grab the "value" property
2764 local_node = jsonObjectGetKeyConst( node, "value" );
2765 if (!local_node) local_node = node;
2768 if( local_node->type == JSON_HASH ) {
2769 // subquery predicate
2771 char* subpred = buildQuery( ctx, (jsonObject*) local_node, SUBSELECT );
2773 buffer_free( sql_buf );
2777 buffer_add( sql_buf, subpred );
2780 } else if( local_node->type == JSON_ARRAY ) {
2781 // literal value list
2782 int in_item_index = 0;
2783 int in_item_first = 1;
2784 const jsonObject* in_item;
2785 while( (in_item = jsonObjectGetIndex( local_node, in_item_index++ )) ) {
2790 buffer_add( sql_buf, ", " );
2793 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2794 osrfLogError( OSRF_LOG_MARK,
2795 "%s: Expected string or number within IN list; found %s",
2796 modulename, json_type( in_item->type ) );
2797 buffer_free( sql_buf );
2801 // Append the literal value -- quoted if not a number
2802 if( JSON_NUMBER == in_item->type ) {
2803 char* val = jsonNumberToDBString( field, in_item );
2804 OSRF_BUFFER_ADD( sql_buf, val );
2807 } else if( !strcmp( get_primitive( field ), "number" )) {
2808 char* val = jsonNumberToDBString( field, in_item );
2809 OSRF_BUFFER_ADD( sql_buf, val );
2813 char* key_string = jsonObjectToSimpleString( in_item );
2814 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2815 OSRF_BUFFER_ADD( sql_buf, key_string );
2818 osrfLogError( OSRF_LOG_MARK,
2819 "%s: Error quoting key string [%s]", modulename, key_string );
2821 buffer_free( sql_buf );
2827 if( in_item_first ) {
2828 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2829 buffer_free( sql_buf );
2833 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2834 modulename, json_type( local_node->type ));
2835 buffer_free( sql_buf );
2839 return buffer_release( sql_buf );
2842 // Receive a JSON_ARRAY representing a function call. The first
2843 // entry in the array is the function name. The rest are parameters.
2844 static char* searchValueTransform( const jsonObject* array ) {
2846 if( array->size < 1 ) {
2847 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2851 // Get the function name
2852 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2853 if( func_item->type != JSON_STRING ) {
2854 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2855 modulename, json_type( func_item->type ));
2859 growing_buffer* sql_buf = buffer_init( 32 );
2861 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2862 OSRF_BUFFER_ADD( sql_buf, "( " );
2864 // Get the parameters
2865 int func_item_index = 1; // We already grabbed the zeroth entry
2866 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2868 // Add a separator comma, if we need one
2869 if( func_item_index > 2 )
2870 buffer_add( sql_buf, ", " );
2872 // Add the current parameter
2873 if( func_item->type == JSON_NULL ) {
2874 buffer_add( sql_buf, "NULL" );
2876 if( func_item->type == JSON_BOOL ) {
2877 if( jsonBoolIsTrue(func_item) ) {
2878 buffer_add( sql_buf, "TRUE" );
2880 buffer_add( sql_buf, "FALSE" );
2883 char* val = jsonObjectToSimpleString( func_item );
2884 if( dbi_conn_quote_string( dbhandle, &val )) {
2885 OSRF_BUFFER_ADD( sql_buf, val );
2888 osrfLogError( OSRF_LOG_MARK,
2889 "%s: Error quoting key string [%s]", modulename, val );
2890 buffer_free( sql_buf );
2898 buffer_add( sql_buf, " )" );
2900 return buffer_release( sql_buf );
2903 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2904 const jsonObject* node, const char* op ) {
2906 if( ! is_good_operator( op ) ) {
2907 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2911 char* val = searchValueTransform( node );
2915 const char* right_percent = "";
2916 const char* real_op = op;
2918 if( !strcasecmp( op, "startwith") ) {
2920 right_percent = "|| '%'";
2923 growing_buffer* sql_buf = buffer_init( 32 );
2926 "\"%s\".%s %s %s%s",
2928 osrfHashGet( field, "name" ),
2936 return buffer_release( sql_buf );
2939 // class_alias is a class name or other table alias
2940 // field is a field definition as stored in the IDL
2941 // node comes from the method parameter, and may represent an entry in the SELECT list
2942 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2943 const jsonObject* node ) {
2944 growing_buffer* sql_buf = buffer_init( 32 );
2946 if( node->type == JSON_HASH ) {
2947 const char* field_transform = jsonObjectGetString(
2948 jsonObjectGetKeyConst( node, "transform" ) );
2949 const char* transform_subcolumn = jsonObjectGetString(
2950 jsonObjectGetKeyConst( node, "result_field" ) );
2952 if( field_transform && transform_subcolumn ) {
2953 if( ! is_identifier( transform_subcolumn ) ) {
2954 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2955 modulename, transform_subcolumn );
2956 buffer_free( sql_buf );
2959 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2962 if( field_transform ) {
2964 if( ! is_identifier( field_transform ) ) {
2965 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2966 modulename, field_transform );
2967 buffer_free( sql_buf );
2971 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2972 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2973 field_transform, class_alias, osrfHashGet( field, "name" ));
2975 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2976 field_transform, class_alias, osrfHashGet( field, "name" ));
2979 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2982 if( array->type != JSON_ARRAY ) {
2983 osrfLogError( OSRF_LOG_MARK,
2984 "%s: Expected JSON_ARRAY for function params; found %s",
2985 modulename, json_type( array->type ) );
2986 buffer_free( sql_buf );
2989 int func_item_index = 0;
2990 jsonObject* func_item;
2991 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2993 char* val = jsonObjectToSimpleString( func_item );
2996 buffer_add( sql_buf, ",NULL" );
2997 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2998 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2999 OSRF_BUFFER_ADD( sql_buf, val );
3001 osrfLogError( OSRF_LOG_MARK,
3002 "%s: Error quoting key string [%s]", modulename, val );
3004 buffer_free( sql_buf );
3011 buffer_add( sql_buf, ")" );
3013 if( transform_subcolumn )
3014 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
3017 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
3020 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
3024 return buffer_release( sql_buf );
3027 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
3028 const jsonObject* node, const char* op ) {
3030 if( ! is_good_operator( op ) ) {
3031 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
3035 char* field_transform = searchFieldTransform( class_info->alias, field, node );
3036 if( ! field_transform )
3039 int extra_parens = 0; // boolean
3041 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
3043 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
3045 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
3047 free( field_transform );
3051 } else if( value_obj->type == JSON_ARRAY ) {
3052 value = searchValueTransform( value_obj );
3054 osrfLogError( OSRF_LOG_MARK,
3055 "%s: Error building value transform for field transform", modulename );
3056 free( field_transform );
3059 } else if( value_obj->type == JSON_HASH ) {
3060 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
3062 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
3064 free( field_transform );
3068 } else if( value_obj->type == JSON_NUMBER ) {
3069 value = jsonNumberToDBString( field, value_obj );
3070 } else if( value_obj->type == JSON_NULL ) {
3071 osrfLogError( OSRF_LOG_MARK,
3072 "%s: Error building predicate for field transform: null value", modulename );
3073 free( field_transform );
3075 } else if( value_obj->type == JSON_BOOL ) {
3076 osrfLogError( OSRF_LOG_MARK,
3077 "%s: Error building predicate for field transform: boolean value", modulename );
3078 free( field_transform );
3081 if( !strcmp( get_primitive( field ), "number") ) {
3082 value = jsonNumberToDBString( field, value_obj );
3084 value = jsonObjectToSimpleString( value_obj );
3085 if( !dbi_conn_quote_string( dbhandle, &value )) {
3086 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3087 modulename, value );
3089 free( field_transform );
3095 const char* left_parens = "";
3096 const char* right_parens = "";
3098 if( extra_parens ) {
3103 const char* right_percent = "";
3104 const char* real_op = op;
3106 if( !strcasecmp( op, "startwith") ) {
3108 right_percent = "|| '%'";
3111 growing_buffer* sql_buf = buffer_init( 32 );
3115 "%s%s %s %s %s%s %s%s",
3127 free( field_transform );
3129 return buffer_release( sql_buf );
3132 static char* searchSimplePredicate( const char* op, const char* class_alias,
3133 osrfHash* field, const jsonObject* node ) {
3135 if( ! is_good_operator( op ) ) {
3136 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
3142 // Get the value to which we are comparing the specified column
3143 if( node->type != JSON_NULL ) {
3144 if( node->type == JSON_NUMBER ) {
3145 val = jsonNumberToDBString( field, node );
3146 } else if( !strcmp( get_primitive( field ), "number" ) ) {
3147 val = jsonNumberToDBString( field, node );
3149 val = jsonObjectToSimpleString( node );
3154 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
3155 // Value is not numeric; enclose it in quotes
3156 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
3157 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3164 // Compare to a null value
3165 val = strdup( "NULL" );
3166 if( strcmp( op, "=" ))
3172 const char* right_percent = "";
3173 const char* real_op = op;
3175 if( !strcasecmp( op, "startwith") ) {
3177 right_percent = "|| '%'";
3180 growing_buffer* sql_buf = buffer_init( 32 );
3181 buffer_fadd( sql_buf, "\"%s\".%s %s %s%s", class_alias, osrfHashGet(field, "name"), real_op, val, right_percent );
3182 char* pred = buffer_release( sql_buf );
3189 static char* searchBETWEENRange( osrfHash* field, const jsonObject* node ) {
3191 const jsonObject* local_node = node;
3192 if( node->type == JSON_HASH ) { // will be the case if the node tranforms the field
3193 local_node = jsonObjectGetKeyConst( node, "value" );
3194 if (!local_node) local_node = node;
3197 const jsonObject* x_node = jsonObjectGetIndex( local_node, 0 );
3198 const jsonObject* y_node = jsonObjectGetIndex( local_node, 1 );
3200 if( NULL == y_node ) {
3201 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
3203 } else if( NULL != jsonObjectGetIndex( local_node, 2 ) ) {
3204 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
3211 if( !strcmp( get_primitive( field ), "number") ) {
3212 x_string = jsonNumberToDBString( field, x_node );
3213 y_string = jsonNumberToDBString( field, y_node );
3216 x_string = jsonObjectToSimpleString( x_node );
3217 y_string = jsonObjectToSimpleString( y_node );
3218 if( !(dbi_conn_quote_string( dbhandle, &x_string )
3219 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
3220 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
3221 modulename, x_string, y_string );
3228 growing_buffer* sql_buf = buffer_init( 32 );
3229 buffer_fadd( sql_buf, "%s AND %s", x_string, y_string );
3233 return buffer_release( sql_buf );
3236 static char* searchBETWEENPredicate( const char* class_alias,
3237 osrfHash* field, const jsonObject* node ) {
3239 char* field_transform = searchFieldTransform( class_alias, field, node );
3240 if( ! field_transform )
3243 char* between_range = searchBETWEENRange(field, node);
3245 if( NULL == between_range )
3248 growing_buffer* sql_buf = buffer_init( 32 );
3249 buffer_fadd( sql_buf, "%s BETWEEN %s", field_transform, between_range);
3251 free(field_transform);
3252 free(between_range);
3254 return buffer_release( sql_buf );
3257 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
3258 jsonObject* node, osrfMethodContext* ctx ) {
3261 if( node->type == JSON_ARRAY ) { // equality IN search
3262 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
3263 } else if( node->type == JSON_HASH ) { // other search
3264 jsonIterator* pred_itr = jsonNewIterator( node );
3265 if( !jsonIteratorHasNext( pred_itr ) ) {
3266 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
3267 modulename, osrfHashGet(field, "name" ));
3269 jsonObject* pred_node = jsonIteratorNext( pred_itr );
3271 // Verify that there are no additional predicates
3272 if( jsonIteratorHasNext( pred_itr ) ) {
3273 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
3274 modulename, osrfHashGet(field, "name" ));
3275 } else if( !(strcasecmp( pred_itr->key,"between" )) ) {
3276 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
3277 } else if( !(strcasecmp( pred_itr->key,"in" ))
3278 || !(strcasecmp( pred_itr->key,"not in" )) ) {
3279 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
3280 } else if( pred_node->type == JSON_ARRAY ) {
3281 pred = searchFunctionPredicate(
3282 class_info->alias, field, pred_node, pred_itr->key );
3283 } else if( pred_node->type == JSON_HASH ) {
3284 pred = searchFieldTransformPredicate(
3285 class_info, field, pred_node, pred_itr->key );
3287 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
3290 jsonIteratorFree( pred_itr );
3292 } else if( node->type == JSON_NULL ) { // IS NULL search
3293 growing_buffer* _p = buffer_init( 64 );
3296 "\"%s\".%s IS NULL",
3298 osrfHashGet( field, "name" )
3300 pred = buffer_release( _p );
3301 } else { // equality search
3302 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3321 field : call_number,
3335 Or, to specify join order:
3338 {mrd:{field:'record', type:'inner'}},
3339 {acn:{field:'record', type:'left'}}
3344 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3346 jsonObject* working_hash;
3347 jsonObject* freeable_hash = NULL;
3349 jsonObject* working_array;
3350 jsonObject* freeable_array = NULL;
3352 if( join_hash->type == JSON_ARRAY ) {
3353 working_array = (jsonObject*)join_hash;
3355 working_array = jsonNewObjectType( JSON_ARRAY );
3357 if( join_hash->type == JSON_HASH ) {
3358 working_hash = (jsonObject*)join_hash;
3359 } else if( join_hash->type == JSON_STRING ) {
3360 freeable_array = working_array;
3361 // turn it into a JSON_HASH by creating a wrapper
3362 // around a copy of the original
3363 const char* _tmp = jsonObjectGetString( join_hash );
3364 freeable_hash = jsonNewObjectType( JSON_HASH );
3365 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3366 working_hash = freeable_hash;
3370 "%s: JOIN failed; expected JSON object type not found",
3376 jsonObjectPush( working_array, working_hash );
3379 growing_buffer* join_buf = buffer_init( 128 );
3380 const char* leftclass = left_info->class_name;
3382 unsigned long order_idx = 0;
3383 while(( working_hash = jsonObjectGetIndex( working_array, order_idx++ ) )) {
3385 jsonObject* freeable_subhash = NULL;
3386 if( working_hash->type == JSON_STRING ) {
3387 // turn it into a JSON_HASH by creating a wrapper
3388 // around a copy of the original
3389 const char* _inner_tmp = jsonObjectGetString( working_hash );
3390 freeable_subhash = jsonNewObjectType( JSON_HASH );
3391 jsonObjectSetKey( freeable_subhash, _inner_tmp, NULL );
3392 working_hash = freeable_subhash;
3395 jsonObject* snode = NULL;
3396 jsonIterator* search_itr = jsonNewIterator( working_hash );
3398 while ( (snode = jsonIteratorNext( search_itr )) ) {
3399 const char* right_alias = search_itr->key;
3401 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3403 class = right_alias;
3405 const ClassInfo* right_info = add_joined_class( right_alias, class );
3409 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3413 jsonIteratorFree( search_itr );
3414 buffer_free( join_buf );
3415 if( freeable_subhash )
3416 jsonObjectFree( freeable_subhash );
3418 jsonObjectFree( freeable_hash );
3419 if( freeable_array )
3420 jsonObjectFree( freeable_array );
3423 osrfHash* links = right_info->links;
3424 const char* table = right_info->source_def;
3426 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3427 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3429 if( field && !fkey ) {
3430 // Look up the corresponding join column in the IDL.
3431 // The link must be defined in the child table,
3432 // and point to the right parent table.
3433 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3434 const char* reltype = NULL;
3435 const char* other_class = NULL;
3436 reltype = osrfHashGet( idl_link, "reltype" );
3437 if( reltype && strcmp( reltype, "has_many" ) )
3438 other_class = osrfHashGet( idl_link, "class" );
3439 if( other_class && !strcmp( other_class, leftclass ) )
3440 fkey = osrfHashGet( idl_link, "key" );
3444 "%s: JOIN failed. No link defined from %s.%s to %s",
3450 buffer_free( join_buf );
3451 if( freeable_subhash )
3452 jsonObjectFree( freeable_subhash );
3454 jsonObjectFree( freeable_hash );
3455 if( freeable_array )
3456 jsonObjectFree( freeable_array );
3457 jsonIteratorFree( search_itr );
3461 } else if( !field && fkey ) {
3462 // Look up the corresponding join column in the IDL.
3463 // The link must be defined in the child table,
3464 // and point to the right parent table.
3465 osrfHash* left_links = left_info->links;
3466 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3467 const char* reltype = NULL;
3468 const char* other_class = NULL;
3469 reltype = osrfHashGet( idl_link, "reltype" );
3470 if( reltype && strcmp( reltype, "has_many" ) )
3471 other_class = osrfHashGet( idl_link, "class" );
3472 if( other_class && !strcmp( other_class, class ) )
3473 field = osrfHashGet( idl_link, "key" );
3477 "%s: JOIN failed. No link defined from %s.%s to %s",
3483 buffer_free( join_buf );
3484 if( freeable_subhash )
3485 jsonObjectFree( freeable_subhash );
3487 jsonObjectFree( freeable_hash );
3488 if( freeable_array )
3489 jsonObjectFree( freeable_array );
3490 jsonIteratorFree( search_itr );
3494 } else if( !field && !fkey ) {
3495 osrfHash* left_links = left_info->links;
3497 // For each link defined for the left class:
3498 // see if the link references the joined class
3499 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3500 osrfHash* curr_link = NULL;
3501 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3502 const char* other_class = osrfHashGet( curr_link, "class" );
3503 if( other_class && !strcmp( other_class, class ) ) {
3505 // In the IDL, the parent class doesn't always know then names of the child
3506 // columns that are pointing to it, so don't use that end of the link
3507 const char* reltype = osrfHashGet( curr_link, "reltype" );
3508 if( reltype && strcmp( reltype, "has_many" ) ) {
3509 // Found a link between the classes
3510 fkey = osrfHashIteratorKey( itr );
3511 field = osrfHashGet( curr_link, "key" );
3516 osrfHashIteratorFree( itr );
3518 if( !field || !fkey ) {
3519 // Do another such search, with the classes reversed
3521 // For each link defined for the joined class:
3522 // see if the link references the left class
3523 osrfHashIterator* itr = osrfNewHashIterator( links );
3524 osrfHash* curr_link = NULL;
3525 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3526 const char* other_class = osrfHashGet( curr_link, "class" );
3527 if( other_class && !strcmp( other_class, leftclass ) ) {
3529 // In the IDL, the parent class doesn't know then names of the child
3530 // columns that are pointing to it, so don't use that end of the link
3531 const char* reltype = osrfHashGet( curr_link, "reltype" );
3532 if( reltype && strcmp( reltype, "has_many" ) ) {
3533 // Found a link between the classes
3534 field = osrfHashIteratorKey( itr );
3535 fkey = osrfHashGet( curr_link, "key" );
3540 osrfHashIteratorFree( itr );
3543 if( !field || !fkey ) {
3546 "%s: JOIN failed. No link defined between %s and %s",
3551 buffer_free( join_buf );
3552 if( freeable_subhash )
3553 jsonObjectFree( freeable_subhash );
3555 jsonObjectFree( freeable_hash );
3556 if( freeable_array )
3557 jsonObjectFree( freeable_array );
3558 jsonIteratorFree( search_itr );
3563 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3565 if( !strcasecmp( type,"left" )) {
3566 buffer_add( join_buf, " LEFT JOIN" );
3567 } else if( !strcasecmp( type,"right" )) {
3568 buffer_add( join_buf, " RIGHT JOIN" );
3569 } else if( !strcasecmp( type,"full" )) {
3570 buffer_add( join_buf, " FULL JOIN" );
3572 buffer_add( join_buf, " INNER JOIN" );
3575 buffer_add( join_buf, " INNER JOIN" );
3578 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3579 table, right_alias, right_alias, field, left_info->alias, fkey );
3581 // Add any other join conditions as specified by "filter"
3582 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3584 const char* filter_op = jsonObjectGetString(
3585 jsonObjectGetKeyConst( snode, "filter_op" ) );
3586 if( filter_op && !strcasecmp( "or",filter_op )) {
3587 buffer_add( join_buf, " OR " );
3589 buffer_add( join_buf, " AND " );
3592 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3594 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3595 OSRF_BUFFER_ADD( join_buf, jpred );
3600 "%s: JOIN failed. Invalid conditional expression.",
3603 jsonIteratorFree( search_itr );
3604 buffer_free( join_buf );
3605 if( freeable_subhash )
3606 jsonObjectFree( freeable_subhash );
3608 jsonObjectFree( freeable_hash );
3609 if( freeable_array )
3610 jsonObjectFree( freeable_array );
3615 buffer_add( join_buf, " ) " );
3617 // Recursively add a nested join, if one is present
3618 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3620 char* jpred = searchJOIN( join_filter, right_info );
3622 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3623 OSRF_BUFFER_ADD( join_buf, jpred );
3626 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3627 jsonIteratorFree( search_itr );
3628 buffer_free( join_buf );
3629 if( freeable_subhash )
3630 jsonObjectFree( freeable_subhash );
3632 jsonObjectFree( freeable_hash );
3633 if( freeable_array )
3634 jsonObjectFree( freeable_array );
3640 if( freeable_subhash )
3641 jsonObjectFree( freeable_subhash );
3643 jsonIteratorFree( search_itr );
3647 jsonObjectFree( freeable_hash );
3649 if( freeable_array )
3650 jsonObjectFree( freeable_array );
3653 return buffer_release( join_buf );
3658 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3659 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3660 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3662 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3664 search_hash is the JSON expression of the conditions.
3665 meta is the class definition from the IDL, for the relevant table.
3666 opjoin_type indicates whether multiple conditions, if present, should be
3667 connected by AND or OR.
3668 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3669 to pass it to other functions -- and all they do with it is to use the session
3670 and request members to send error messages back to the client.
3674 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3675 int opjoin_type, osrfMethodContext* ctx ) {
3679 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3680 "opjoin_type = %d, ctx addr = %p",
3683 class_info->class_def,
3688 growing_buffer* sql_buf = buffer_init( 128 );
3690 jsonObject* node = NULL;
3693 if( search_hash->type == JSON_ARRAY ) {
3694 if( 0 == search_hash->size ) {
3697 "%s: Invalid predicate structure: empty JSON array",
3700 buffer_free( sql_buf );
3704 unsigned long i = 0;
3705 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3709 if( opjoin_type == OR_OP_JOIN )
3710 buffer_add( sql_buf, " OR " );
3712 buffer_add( sql_buf, " AND " );
3715 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3717 buffer_free( sql_buf );
3721 buffer_fadd( sql_buf, "( %s )", subpred );
3725 } else if( search_hash->type == JSON_HASH ) {
3726 osrfLogDebug( OSRF_LOG_MARK,
3727 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3728 jsonIterator* search_itr = jsonNewIterator( search_hash );
3729 if( !jsonIteratorHasNext( search_itr ) ) {
3732 "%s: Invalid predicate structure: empty JSON object",
3735 jsonIteratorFree( search_itr );
3736 buffer_free( sql_buf );
3740 while( (node = jsonIteratorNext( search_itr )) ) {
3745 if( opjoin_type == OR_OP_JOIN )
3746 buffer_add( sql_buf, " OR " );
3748 buffer_add( sql_buf, " AND " );
3751 if( '+' == search_itr->key[ 0 ] ) {
3753 // This plus sign prefixes a class name or other table alias;
3754 // make sure the table alias is in scope
3755 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3756 if( ! alias_info ) {
3759 "%s: Invalid table alias \"%s\" in WHERE clause",
3763 jsonIteratorFree( search_itr );
3764 buffer_free( sql_buf );
3768 if( node->type == JSON_STRING ) {
3769 // It's the name of a column; make sure it belongs to the class
3770 const char* fieldname = jsonObjectGetString( node );
3771 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3774 "%s: Invalid column name \"%s\" in WHERE clause "
3775 "for table alias \"%s\"",
3780 jsonIteratorFree( search_itr );
3781 buffer_free( sql_buf );
3785 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3787 // It's something more complicated
3788 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3790 jsonIteratorFree( search_itr );
3791 buffer_free( sql_buf );
3795 buffer_fadd( sql_buf, "( %s )", subpred );
3798 } else if( '-' == search_itr->key[ 0 ] ) {
3799 if( !strcasecmp( "-or", search_itr->key )) {
3800 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3802 jsonIteratorFree( search_itr );
3803 buffer_free( sql_buf );
3807 buffer_fadd( sql_buf, "( %s )", subpred );
3809 } else if( !strcasecmp( "-and", search_itr->key )) {
3810 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3812 jsonIteratorFree( search_itr );
3813 buffer_free( sql_buf );
3817 buffer_fadd( sql_buf, "( %s )", subpred );
3819 } else if( !strcasecmp("-not",search_itr->key) ) {
3820 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3822 jsonIteratorFree( search_itr );
3823 buffer_free( sql_buf );
3827 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3829 } else if( !strcasecmp( "-exists", search_itr->key )) {
3830 char* subpred = buildQuery( ctx, node, SUBSELECT );
3832 jsonIteratorFree( search_itr );
3833 buffer_free( sql_buf );
3837 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3839 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3840 char* subpred = buildQuery( ctx, node, SUBSELECT );
3842 jsonIteratorFree( search_itr );
3843 buffer_free( sql_buf );
3847 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3849 } else { // Invalid "minus" operator
3852 "%s: Invalid operator \"%s\" in WHERE clause",
3856 jsonIteratorFree( search_itr );
3857 buffer_free( sql_buf );
3863 const char* class = class_info->class_name;
3864 osrfHash* fields = class_info->fields;
3865 osrfHash* field = osrfHashGet( fields, search_itr->key );
3868 const char* table = class_info->source_def;
3871 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3874 table ? table : "?",
3877 jsonIteratorFree( search_itr );
3878 buffer_free( sql_buf );
3882 char* subpred = searchPredicate( class_info, field, node, ctx );
3884 buffer_free( sql_buf );
3885 jsonIteratorFree( search_itr );
3889 buffer_add( sql_buf, subpred );
3893 jsonIteratorFree( search_itr );
3896 // ERROR ... only hash and array allowed at this level
3897 char* predicate_string = jsonObjectToJSON( search_hash );
3900 "%s: Invalid predicate structure: %s",
3904 buffer_free( sql_buf );
3905 free( predicate_string );
3909 return buffer_release( sql_buf );
3912 /* Build a JSON_ARRAY of field names for a given table alias
3914 static jsonObject* defaultSelectList( const char* table_alias ) {
3919 ClassInfo* class_info = search_all_alias( table_alias );
3920 if( ! class_info ) {
3923 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3930 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3931 osrfHash* field_def = NULL;
3932 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3933 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3934 const char* field_name = osrfHashIteratorKey( field_itr );
3935 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3936 jsonObjectPush( array, jsonNewObject( field_name ) );
3939 osrfHashIteratorFree( field_itr );
3944 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3945 // The jsonObject must be a JSON_HASH with an single entry for "union",
3946 // "intersect", or "except". The data associated with this key must be an
3947 // array of hashes, each hash being a query.
3948 // Also allowed but currently ignored: entries for "order_by" and "alias".
3949 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3951 if( ! combo || combo->type != JSON_HASH )
3952 return NULL; // should be impossible; validated by caller
3954 const jsonObject* query_array = NULL; // array of subordinate queries
3955 const char* op = NULL; // name of operator, e.g. UNION
3956 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3957 int op_count = 0; // for detecting conflicting operators
3958 int excepting = 0; // boolean
3959 int all = 0; // boolean
3960 jsonObject* order_obj = NULL;
3962 // Identify the elements in the hash
3963 jsonIterator* query_itr = jsonNewIterator( combo );
3964 jsonObject* curr_obj = NULL;
3965 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3966 if( ! strcmp( "union", query_itr->key ) ) {
3969 query_array = curr_obj;
3970 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3973 query_array = curr_obj;
3974 } else if( ! strcmp( "except", query_itr->key ) ) {
3978 query_array = curr_obj;
3979 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3982 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3985 order_obj = curr_obj;
3986 } else if( ! strcmp( "alias", query_itr->key ) ) {
3987 if( curr_obj->type != JSON_STRING ) {
3988 jsonIteratorFree( query_itr );
3991 alias = jsonObjectGetString( curr_obj );
3992 } else if( ! strcmp( "all", query_itr->key ) ) {
3993 if( obj_is_true( curr_obj ) )
3997 osrfAppSessionStatus(
3999 OSRF_STATUS_INTERNALSERVERERROR,
4000 "osrfMethodException",
4002 "Malformed query; unexpected entry in query object"
4006 "%s: Unexpected entry for \"%s\" in%squery",
4011 jsonIteratorFree( query_itr );
4015 jsonIteratorFree( query_itr );
4017 // More sanity checks
4018 if( ! query_array ) {
4020 osrfAppSessionStatus(
4022 OSRF_STATUS_INTERNALSERVERERROR,
4023 "osrfMethodException",
4025 "Expected UNION, INTERSECT, or EXCEPT operator not found"
4029 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
4032 return NULL; // should be impossible...
4033 } else if( op_count > 1 ) {
4035 osrfAppSessionStatus(
4037 OSRF_STATUS_INTERNALSERVERERROR,
4038 "osrfMethodException",
4040 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
4044 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
4048 } if( query_array->type != JSON_ARRAY ) {
4050 osrfAppSessionStatus(
4052 OSRF_STATUS_INTERNALSERVERERROR,
4053 "osrfMethodException",
4055 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
4059 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
4062 json_type( query_array->type )
4065 } if( query_array->size < 2 ) {
4067 osrfAppSessionStatus(
4069 OSRF_STATUS_INTERNALSERVERERROR,
4070 "osrfMethodException",
4072 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
4076 "%s:%srequires multiple queries as operands",
4081 } else if( excepting && query_array->size > 2 ) {
4083 osrfAppSessionStatus(
4085 OSRF_STATUS_INTERNALSERVERERROR,
4086 "osrfMethodException",
4088 "EXCEPT operator has too many queries as operands"
4092 "%s:EXCEPT operator has too many queries as operands",
4096 } else if( order_obj && ! alias ) {
4098 osrfAppSessionStatus(
4100 OSRF_STATUS_INTERNALSERVERERROR,
4101 "osrfMethodException",
4103 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
4107 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
4113 // So far so good. Now build the SQL.
4114 growing_buffer* sql = buffer_init( 256 );
4116 // If we nested inside another UNION, INTERSECT, or EXCEPT,
4117 // Add a layer of parentheses
4118 if( flags & SUBCOMBO )
4119 OSRF_BUFFER_ADD( sql, "( " );
4121 // Traverse the query array. Each entry should be a hash.
4122 int first = 1; // boolean
4124 jsonObject* query = NULL;
4125 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
4126 if( query->type != JSON_HASH ) {
4128 osrfAppSessionStatus(
4130 OSRF_STATUS_INTERNALSERVERERROR,
4131 "osrfMethodException",
4133 "Malformed query under UNION, INTERSECT or EXCEPT"
4137 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
4140 json_type( query->type )
4149 OSRF_BUFFER_ADD( sql, op );
4151 OSRF_BUFFER_ADD( sql, "ALL " );
4154 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
4158 "%s: Error building query under%s",
4166 OSRF_BUFFER_ADD( sql, query_str );
4169 if( flags & SUBCOMBO )
4170 OSRF_BUFFER_ADD_CHAR( sql, ')' );
4172 if( !(flags & SUBSELECT) )
4173 OSRF_BUFFER_ADD_CHAR( sql, ';' );
4175 return buffer_release( sql );
4178 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
4179 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
4180 // or "except" to indicate the type of query.
4181 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
4185 osrfAppSessionStatus(
4187 OSRF_STATUS_INTERNALSERVERERROR,
4188 "osrfMethodException",
4190 "Malformed query; no query object"
4192 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
4194 } else if( query->type != JSON_HASH ) {
4196 osrfAppSessionStatus(
4198 OSRF_STATUS_INTERNALSERVERERROR,
4199 "osrfMethodException",
4201 "Malformed query object"
4205 "%s: Query object is %s instead of JSON_HASH",
4207 json_type( query->type )
4212 // Determine what kind of query it purports to be, and dispatch accordingly.
4213 if( jsonObjectGetKeyConst( query, "union" ) ||
4214 jsonObjectGetKeyConst( query, "intersect" ) ||
4215 jsonObjectGetKeyConst( query, "except" )) {
4216 return doCombo( ctx, query, flags );
4218 // It is presumably a SELECT query
4220 // Push a node onto the stack for the current query. Every level of
4221 // subquery gets its own QueryFrame on the Stack.
4224 // Build an SQL SELECT statement
4227 jsonObjectGetKey( query, "select" ),
4228 jsonObjectGetKeyConst( query, "from" ),
4229 jsonObjectGetKeyConst( query, "where" ),
4230 jsonObjectGetKeyConst( query, "having" ),
4231 jsonObjectGetKeyConst( query, "order_by" ),
4232 jsonObjectGetKeyConst( query, "limit" ),
4233 jsonObjectGetKeyConst( query, "offset" ),
4242 /* method context */ osrfMethodContext* ctx,
4244 /* SELECT */ jsonObject* selhash,
4245 /* FROM */ const jsonObject* join_hash,
4246 /* WHERE */ const jsonObject* search_hash,
4247 /* HAVING */ const jsonObject* having_hash,
4248 /* ORDER BY */ const jsonObject* order_hash,
4249 /* LIMIT */ const jsonObject* limit,
4250 /* OFFSET */ const jsonObject* offset,
4251 /* flags */ int flags
4253 const char* locale = osrf_message_get_last_locale();
4255 // general tmp objects
4256 const jsonObject* tmp_const;
4257 jsonObject* selclass = NULL;
4258 jsonObject* snode = NULL;
4259 jsonObject* onode = NULL;
4261 char* string = NULL;
4262 int from_function = 0;
4267 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
4269 // punt if there's no FROM clause
4270 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
4273 "%s: FROM clause is missing or empty",
4277 osrfAppSessionStatus(
4279 OSRF_STATUS_INTERNALSERVERERROR,
4280 "osrfMethodException",
4282 "FROM clause is missing or empty in JSON query"
4287 // the core search class
4288 const char* core_class = NULL;
4290 // get the core class -- the only key of the top level FROM clause, or a string
4291 if( join_hash->type == JSON_HASH ) {
4292 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
4293 snode = jsonIteratorNext( tmp_itr );
4295 // Populate the current QueryFrame with information
4296 // about the core class
4297 if( add_query_core( NULL, tmp_itr->key ) ) {
4299 osrfAppSessionStatus(
4301 OSRF_STATUS_INTERNALSERVERERROR,
4302 "osrfMethodException",
4304 "Unable to look up core class"
4308 core_class = curr_query->core.class_name;
4311 jsonObject* extra = jsonIteratorNext( tmp_itr );
4313 jsonIteratorFree( tmp_itr );
4316 // There shouldn't be more than one entry in join_hash
4320 "%s: Malformed FROM clause: extra entry in JSON_HASH",
4324 osrfAppSessionStatus(
4326 OSRF_STATUS_INTERNALSERVERERROR,
4327 "osrfMethodException",
4329 "Malformed FROM clause in JSON query"
4331 return NULL; // Malformed join_hash; extra entry
4333 } else if( join_hash->type == JSON_ARRAY ) {
4334 // We're selecting from a function, not from a table
4336 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
4339 } else if( join_hash->type == JSON_STRING ) {
4340 // Populate the current QueryFrame with information
4341 // about the core class
4342 core_class = jsonObjectGetString( join_hash );
4344 if( add_query_core( NULL, core_class ) ) {
4346 osrfAppSessionStatus(
4348 OSRF_STATUS_INTERNALSERVERERROR,
4349 "osrfMethodException",
4351 "Unable to look up core class"
4359 "%s: FROM clause is unexpected JSON type: %s",
4361 json_type( join_hash->type )
4364 osrfAppSessionStatus(
4366 OSRF_STATUS_INTERNALSERVERERROR,
4367 "osrfMethodException",
4369 "Ill-formed FROM clause in JSON query"
4374 // Build the join clause, if any, while filling out the list
4375 // of joined classes in the current QueryFrame.
4376 char* join_clause = NULL;
4377 if( join_hash && ! from_function ) {
4379 join_clause = searchJOIN( join_hash, &curr_query->core );
4380 if( ! join_clause ) {
4382 osrfAppSessionStatus(
4384 OSRF_STATUS_INTERNALSERVERERROR,
4385 "osrfMethodException",
4387 "Unable to construct JOIN clause(s)"
4393 // For in case we don't get a select list
4394 jsonObject* defaultselhash = NULL;
4396 // if there is no select list, build a default select list ...
4397 if( !selhash && !from_function ) {
4398 jsonObject* default_list = defaultSelectList( core_class );
4399 if( ! default_list ) {
4401 osrfAppSessionStatus(
4403 OSRF_STATUS_INTERNALSERVERERROR,
4404 "osrfMethodException",
4406 "Unable to build default SELECT clause in JSON query"
4408 free( join_clause );
4413 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4414 jsonObjectSetKey( selhash, core_class, default_list );
4417 // The SELECT clause can be encoded only by a hash
4418 if( !from_function && selhash->type != JSON_HASH ) {
4421 "%s: Expected JSON_HASH for SELECT clause; found %s",
4423 json_type( selhash->type )
4427 osrfAppSessionStatus(
4429 OSRF_STATUS_INTERNALSERVERERROR,
4430 "osrfMethodException",
4432 "Malformed SELECT clause in JSON query"
4434 free( join_clause );
4438 // If you see a null or wild card specifier for the core class, or an
4439 // empty array, replace it with a default SELECT list
4440 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4442 int default_needed = 0; // boolean
4443 if( JSON_STRING == tmp_const->type
4444 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4446 else if( JSON_NULL == tmp_const->type )
4449 if( default_needed ) {
4450 // Build a default SELECT list
4451 jsonObject* default_list = defaultSelectList( core_class );
4452 if( ! default_list ) {
4454 osrfAppSessionStatus(
4456 OSRF_STATUS_INTERNALSERVERERROR,
4457 "osrfMethodException",
4459 "Can't build default SELECT clause in JSON query"
4461 free( join_clause );
4466 jsonObjectSetKey( selhash, core_class, default_list );
4470 // temp buffers for the SELECT list and GROUP BY clause
4471 growing_buffer* select_buf = buffer_init( 128 );
4472 growing_buffer* group_buf = buffer_init( 128 );
4474 int aggregate_found = 0; // boolean
4476 // Build a select list
4477 if( from_function ) // From a function we select everything
4478 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4481 // Build the SELECT list as SQL
4485 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4486 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4488 const char* cname = selclass_itr->key;
4490 // Make sure the target relation is in the FROM clause.
4492 // At this point join_hash is a step down from the join_hash we
4493 // received as a parameter. If the original was a JSON_STRING,
4494 // then json_hash is now NULL. If the original was a JSON_HASH,
4495 // then json_hash is now the first (and only) entry in it,
4496 // denoting the core class. We've already excluded the
4497 // possibility that the original was a JSON_ARRAY, because in
4498 // that case from_function would be non-NULL, and we wouldn't
4501 // If the current table alias isn't in scope, bail out
4502 ClassInfo* class_info = search_alias( cname );
4503 if( ! class_info ) {
4506 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4511 osrfAppSessionStatus(
4513 OSRF_STATUS_INTERNALSERVERERROR,
4514 "osrfMethodException",
4516 "Selected class not in FROM clause in JSON query"
4518 jsonIteratorFree( selclass_itr );
4519 buffer_free( select_buf );
4520 buffer_free( group_buf );
4521 if( defaultselhash )
4522 jsonObjectFree( defaultselhash );
4523 free( join_clause );
4527 if( selclass->type != JSON_ARRAY ) {
4530 "%s: Malformed SELECT list for class \"%s\"; not an array",
4535 osrfAppSessionStatus(
4537 OSRF_STATUS_INTERNALSERVERERROR,
4538 "osrfMethodException",
4540 "Selected class not in FROM clause in JSON query"
4543 jsonIteratorFree( selclass_itr );
4544 buffer_free( select_buf );
4545 buffer_free( group_buf );
4546 if( defaultselhash )
4547 jsonObjectFree( defaultselhash );
4548 free( join_clause );
4552 // Look up some attributes of the current class
4553 osrfHash* idlClass = class_info->class_def;
4554 osrfHash* class_field_set = class_info->fields;
4555 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4556 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4558 if( 0 == selclass->size ) {
4561 "%s: No columns selected from \"%s\"",
4567 // stitch together the column list for the current table alias...
4568 unsigned long field_idx = 0;
4569 jsonObject* selfield = NULL;
4570 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4572 // If we need a separator comma, add one
4576 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4579 // if the field specification is a string, add it to the list
4580 if( selfield->type == JSON_STRING ) {
4582 // Look up the field in the IDL
4583 const char* col_name = jsonObjectGetString( selfield );
4584 osrfHash* field_def = NULL;
4586 if (!osrfStringArrayContains(
4588 osrfHashGet( class_field_set, col_name ),
4589 "suppress_controller"),
4592 field_def = osrfHashGet( class_field_set, col_name );
4595 // No such field in current class
4598 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4604 osrfAppSessionStatus(
4606 OSRF_STATUS_INTERNALSERVERERROR,
4607 "osrfMethodException",
4609 "Selected column not defined in JSON query"
4611 jsonIteratorFree( selclass_itr );
4612 buffer_free( select_buf );
4613 buffer_free( group_buf );
4614 if( defaultselhash )
4615 jsonObjectFree( defaultselhash );
4616 free( join_clause );
4618 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4619 // Virtual field not allowed
4622 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4628 osrfAppSessionStatus(
4630 OSRF_STATUS_INTERNALSERVERERROR,
4631 "osrfMethodException",
4633 "Selected column may not be virtual in JSON query"
4635 jsonIteratorFree( selclass_itr );
4636 buffer_free( select_buf );
4637 buffer_free( group_buf );
4638 if( defaultselhash )
4639 jsonObjectFree( defaultselhash );
4640 free( join_clause );
4646 if( flags & DISABLE_I18N )
4649 i18n = osrfHashGet( field_def, "i18n" );
4651 if( str_is_true( i18n ) ) {
4652 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4653 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4654 class_tname, cname, col_name, class_pkey,
4655 cname, class_pkey, locale, col_name );
4657 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4658 cname, col_name, col_name );
4661 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4662 cname, col_name, col_name );
4665 // ... but it could be an object, in which case we check for a Field Transform
4666 } else if( selfield->type == JSON_HASH ) {
4668 const char* col_name = jsonObjectGetString(
4669 jsonObjectGetKeyConst( selfield, "column" ) );
4671 // Get the field definition from the IDL
4672 osrfHash* field_def = NULL;
4673 if (!osrfStringArrayContains(
4675 osrfHashGet( class_field_set, col_name ),
4676 "suppress_controller"),
4679 field_def = osrfHashGet( class_field_set, col_name );
4683 // No such field in current class
4686 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4692 osrfAppSessionStatus(
4694 OSRF_STATUS_INTERNALSERVERERROR,
4695 "osrfMethodException",
4697 "Selected column is not defined in JSON query"
4699 jsonIteratorFree( selclass_itr );
4700 buffer_free( select_buf );
4701 buffer_free( group_buf );
4702 if( defaultselhash )
4703 jsonObjectFree( defaultselhash );
4704 free( join_clause );
4706 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4707 // No such field in current class
4710 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4716 osrfAppSessionStatus(
4718 OSRF_STATUS_INTERNALSERVERERROR,
4719 "osrfMethodException",
4721 "Selected column is virtual in JSON query"
4723 jsonIteratorFree( selclass_itr );
4724 buffer_free( select_buf );
4725 buffer_free( group_buf );
4726 if( defaultselhash )
4727 jsonObjectFree( defaultselhash );
4728 free( join_clause );
4732 // Decide what to use as a column alias
4734 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4735 _alias = jsonObjectGetString( tmp_const );
4736 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4737 _alias = jsonObjectGetString( tmp_const );
4738 } else { // Use field name as the alias
4742 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4743 char* transform_str = searchFieldTransform(
4744 class_info->alias, field_def, selfield );
4745 if( transform_str ) {
4746 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4747 free( transform_str );
4750 osrfAppSessionStatus(
4752 OSRF_STATUS_INTERNALSERVERERROR,
4753 "osrfMethodException",
4755 "Unable to generate transform function in JSON query"
4757 jsonIteratorFree( selclass_itr );
4758 buffer_free( select_buf );
4759 buffer_free( group_buf );
4760 if( defaultselhash )
4761 jsonObjectFree( defaultselhash );
4762 free( join_clause );
4769 if( flags & DISABLE_I18N )
4772 i18n = osrfHashGet( field_def, "i18n" );
4774 if( str_is_true( i18n ) ) {
4775 buffer_fadd( select_buf,
4776 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4777 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4778 class_tname, cname, col_name, class_pkey, cname,
4779 class_pkey, locale, _alias );
4781 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4782 cname, col_name, _alias );
4785 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4786 cname, col_name, _alias );
4793 "%s: Selected item is unexpected JSON type: %s",
4795 json_type( selfield->type )
4798 osrfAppSessionStatus(
4800 OSRF_STATUS_INTERNALSERVERERROR,
4801 "osrfMethodException",
4803 "Ill-formed SELECT item in JSON query"
4805 jsonIteratorFree( selclass_itr );
4806 buffer_free( select_buf );
4807 buffer_free( group_buf );
4808 if( defaultselhash )
4809 jsonObjectFree( defaultselhash );
4810 free( join_clause );
4814 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4815 if( obj_is_true( agg_obj ) )
4816 aggregate_found = 1;
4818 // Append a comma (except for the first one)
4819 // and add the column to a GROUP BY clause
4823 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4825 buffer_fadd( group_buf, " %d", sel_pos );
4829 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4831 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( selfield, "aggregate");
4832 if ( ! obj_is_true( aggregate_obj ) ) {
4836 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4839 buffer_fadd(group_buf, " %d", sel_pos);
4842 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4846 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4849 _column = searchFieldTransform(class_info->alias, field, selfield);
4850 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4851 OSRF_BUFFER_ADD(group_buf, _column);
4852 _column = searchFieldTransform(class_info->alias, field, selfield);
4859 } // end while -- iterating across SELECT columns
4861 } // end while -- iterating across classes
4863 jsonIteratorFree( selclass_itr );
4866 char* col_list = buffer_release( select_buf );
4868 // Make sure the SELECT list isn't empty. This can happen, for example,
4869 // if we try to build a default SELECT clause from a non-core table.
4872 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4874 osrfAppSessionStatus(
4876 OSRF_STATUS_INTERNALSERVERERROR,
4877 "osrfMethodException",
4879 "SELECT list is empty"
4882 buffer_free( group_buf );
4883 if( defaultselhash )
4884 jsonObjectFree( defaultselhash );
4885 free( join_clause );
4891 table = searchValueTransform( join_hash );
4893 table = strdup( curr_query->core.source_def );
4897 osrfAppSessionStatus(
4899 OSRF_STATUS_INTERNALSERVERERROR,
4900 "osrfMethodException",
4902 "Unable to identify table for core class"
4905 buffer_free( group_buf );
4906 if( defaultselhash )
4907 jsonObjectFree( defaultselhash );
4908 free( join_clause );
4912 // Put it all together
4913 growing_buffer* sql_buf = buffer_init( 128 );
4914 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4918 // Append the join clause, if any
4920 buffer_add(sql_buf, join_clause );
4921 free( join_clause );
4924 char* order_by_list = NULL;
4925 char* having_buf = NULL;
4927 if( !from_function ) {
4929 // Build a WHERE clause, if there is one
4931 buffer_add( sql_buf, " WHERE " );
4933 // and it's on the WHERE clause
4934 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4937 osrfAppSessionStatus(
4939 OSRF_STATUS_INTERNALSERVERERROR,
4940 "osrfMethodException",
4942 "Severe query error in WHERE predicate -- see error log for more details"
4945 buffer_free( group_buf );
4946 buffer_free( sql_buf );
4947 if( defaultselhash )
4948 jsonObjectFree( defaultselhash );
4952 buffer_add( sql_buf, pred );
4956 // Build a HAVING clause, if there is one
4959 // and it's on the the WHERE clause
4960 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4962 if( ! having_buf ) {
4964 osrfAppSessionStatus(
4966 OSRF_STATUS_INTERNALSERVERERROR,
4967 "osrfMethodException",
4969 "Severe query error in HAVING predicate -- see error log for more details"
4972 buffer_free( group_buf );
4973 buffer_free( sql_buf );
4974 if( defaultselhash )
4975 jsonObjectFree( defaultselhash );
4980 // Build an ORDER BY clause, if there is one
4981 if( NULL == order_hash )
4982 ; // No ORDER BY? do nothing
4983 else if( JSON_ARRAY == order_hash->type ) {
4984 order_by_list = buildOrderByFromArray( ctx, order_hash );
4985 if( !order_by_list ) {
4987 buffer_free( group_buf );
4988 buffer_free( sql_buf );
4989 if( defaultselhash )
4990 jsonObjectFree( defaultselhash );
4993 } else if( JSON_HASH == order_hash->type ) {
4994 // This hash is keyed on class alias. Each class has either
4995 // an array of field names or a hash keyed on field name.
4996 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4997 jsonIterator* class_itr = jsonNewIterator( order_hash );
4998 while( (snode = jsonIteratorNext( class_itr )) ) {
5000 ClassInfo* order_class_info = search_alias( class_itr->key );
5001 if( ! order_class_info ) {
5002 osrfLogError( OSRF_LOG_MARK,
5003 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
5004 modulename, class_itr->key );
5006 osrfAppSessionStatus(
5008 OSRF_STATUS_INTERNALSERVERERROR,
5009 "osrfMethodException",
5011 "Invalid class referenced in ORDER BY clause -- "
5012 "see error log for more details"
5014 jsonIteratorFree( class_itr );
5015 buffer_free( order_buf );
5017 buffer_free( group_buf );
5018 buffer_free( sql_buf );
5019 if( defaultselhash )
5020 jsonObjectFree( defaultselhash );
5024 osrfHash* field_list_def = order_class_info->fields;
5026 if( snode->type == JSON_HASH ) {
5028 // Hash is keyed on field names from the current class. For each field
5029 // there is another layer of hash to define the sorting details, if any,
5030 // or a string to indicate direction of sorting.
5031 jsonIterator* order_itr = jsonNewIterator( snode );
5032 while( (onode = jsonIteratorNext( order_itr )) ) {
5034 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
5036 osrfLogError( OSRF_LOG_MARK,
5037 "%s: Invalid field \"%s\" in ORDER BY clause",
5038 modulename, order_itr->key );
5040 osrfAppSessionStatus(
5042 OSRF_STATUS_INTERNALSERVERERROR,
5043 "osrfMethodException",
5045 "Invalid field in ORDER BY clause -- "
5046 "see error log for more details"
5048 jsonIteratorFree( order_itr );
5049 jsonIteratorFree( class_itr );
5050 buffer_free( order_buf );
5052 buffer_free( group_buf );
5053 buffer_free( sql_buf );
5054 if( defaultselhash )
5055 jsonObjectFree( defaultselhash );
5057 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5058 osrfLogError( OSRF_LOG_MARK,
5059 "%s: Virtual field \"%s\" in ORDER BY clause",
5060 modulename, order_itr->key );
5062 osrfAppSessionStatus(
5064 OSRF_STATUS_INTERNALSERVERERROR,
5065 "osrfMethodException",
5067 "Virtual field in ORDER BY clause -- "
5068 "see error log for more details"
5070 jsonIteratorFree( order_itr );
5071 jsonIteratorFree( class_itr );
5072 buffer_free( order_buf );
5074 buffer_free( group_buf );
5075 buffer_free( sql_buf );
5076 if( defaultselhash )
5077 jsonObjectFree( defaultselhash );
5081 const char* direction = NULL;
5082 if( onode->type == JSON_HASH ) {
5083 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5084 string = searchFieldTransform(
5086 osrfHashGet( field_list_def, order_itr->key ),
5090 if( ctx ) osrfAppSessionStatus(
5092 OSRF_STATUS_INTERNALSERVERERROR,
5093 "osrfMethodException",
5095 "Severe query error in ORDER BY clause -- "
5096 "see error log for more details"
5098 jsonIteratorFree( order_itr );
5099 jsonIteratorFree( class_itr );
5101 buffer_free( group_buf );
5102 buffer_free( order_buf);
5103 buffer_free( sql_buf );
5104 if( defaultselhash )
5105 jsonObjectFree( defaultselhash );
5109 growing_buffer* field_buf = buffer_init( 16 );
5110 buffer_fadd( field_buf, "\"%s\".%s",
5111 class_itr->key, order_itr->key );
5112 string = buffer_release( field_buf );
5115 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
5116 const char* dir = jsonObjectGetString( tmp_const );
5117 if(!strncasecmp( dir, "d", 1 )) {
5118 direction = " DESC";
5124 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
5125 osrfLogError( OSRF_LOG_MARK,
5126 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
5127 modulename, json_type( onode->type ) );
5129 osrfAppSessionStatus(
5131 OSRF_STATUS_INTERNALSERVERERROR,
5132 "osrfMethodException",
5134 "Malformed ORDER BY clause -- see error log for more details"
5136 jsonIteratorFree( order_itr );
5137 jsonIteratorFree( class_itr );
5139 buffer_free( group_buf );
5140 buffer_free( order_buf );
5141 buffer_free( sql_buf );
5142 if( defaultselhash )
5143 jsonObjectFree( defaultselhash );
5147 string = strdup( order_itr->key );
5148 const char* dir = jsonObjectGetString( onode );
5149 if( !strncasecmp( dir, "d", 1 )) {
5150 direction = " DESC";
5157 OSRF_BUFFER_ADD( order_buf, ", " );
5159 order_buf = buffer_init( 128 );
5161 OSRF_BUFFER_ADD( order_buf, string );
5165 OSRF_BUFFER_ADD( order_buf, direction );
5169 jsonIteratorFree( order_itr );
5171 } else if( snode->type == JSON_ARRAY ) {
5173 // Array is a list of fields from the current class
5174 unsigned long order_idx = 0;
5175 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
5177 const char* _f = jsonObjectGetString( onode );
5179 osrfHash* field_def = osrfHashGet( field_list_def, _f );
5181 osrfLogError( OSRF_LOG_MARK,
5182 "%s: Invalid field \"%s\" in ORDER BY clause",
5185 osrfAppSessionStatus(
5187 OSRF_STATUS_INTERNALSERVERERROR,
5188 "osrfMethodException",
5190 "Invalid field in ORDER BY clause -- "
5191 "see error log for more details"
5193 jsonIteratorFree( class_itr );
5194 buffer_free( order_buf );
5196 buffer_free( group_buf );
5197 buffer_free( sql_buf );
5198 if( defaultselhash )
5199 jsonObjectFree( defaultselhash );
5201 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5202 osrfLogError( OSRF_LOG_MARK,
5203 "%s: Virtual field \"%s\" in ORDER BY clause",
5206 osrfAppSessionStatus(
5208 OSRF_STATUS_INTERNALSERVERERROR,
5209 "osrfMethodException",
5211 "Virtual field in ORDER BY clause -- "
5212 "see error log for more details"
5214 jsonIteratorFree( class_itr );
5215 buffer_free( order_buf );
5217 buffer_free( group_buf );
5218 buffer_free( sql_buf );
5219 if( defaultselhash )
5220 jsonObjectFree( defaultselhash );
5225 OSRF_BUFFER_ADD( order_buf, ", " );
5227 order_buf = buffer_init( 128 );
5229 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
5233 // IT'S THE OOOOOOOOOOOLD STYLE!
5235 osrfLogError( OSRF_LOG_MARK,
5236 "%s: Possible SQL injection attempt; direct order by is not allowed",
5239 osrfAppSessionStatus(
5241 OSRF_STATUS_INTERNALSERVERERROR,
5242 "osrfMethodException",
5244 "Severe query error -- see error log for more details"
5249 buffer_free( group_buf );
5250 buffer_free( order_buf );
5251 buffer_free( sql_buf );
5252 if( defaultselhash )
5253 jsonObjectFree( defaultselhash );
5254 jsonIteratorFree( class_itr );
5258 jsonIteratorFree( class_itr );
5260 order_by_list = buffer_release( order_buf );
5262 osrfLogError( OSRF_LOG_MARK,
5263 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
5264 modulename, json_type( order_hash->type ) );
5266 osrfAppSessionStatus(
5268 OSRF_STATUS_INTERNALSERVERERROR,
5269 "osrfMethodException",
5271 "Malformed ORDER BY clause -- see error log for more details"
5274 buffer_free( group_buf );
5275 buffer_free( sql_buf );
5276 if( defaultselhash )
5277 jsonObjectFree( defaultselhash );
5282 string = buffer_release( group_buf );
5284 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
5285 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
5286 OSRF_BUFFER_ADD( sql_buf, string );
5291 if( having_buf && *having_buf ) {
5292 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
5293 OSRF_BUFFER_ADD( sql_buf, having_buf );
5297 if( order_by_list ) {
5299 if( *order_by_list ) {
5300 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5301 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5304 free( order_by_list );
5308 const char* str = jsonObjectGetString( limit );
5309 if (str) { // limit could be JSON_NULL, etc.
5310 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
5315 const char* str = jsonObjectGetString( offset );
5317 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
5321 if( !(flags & SUBSELECT) )
5322 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5324 if( defaultselhash )
5325 jsonObjectFree( defaultselhash );
5327 return buffer_release( sql_buf );
5329 } // end of SELECT()
5332 @brief Build a list of ORDER BY expressions.
5333 @param ctx Pointer to the method context.
5334 @param order_array Pointer to a JSON_ARRAY of field specifications.
5335 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
5336 Each expression may be either a column reference or a function call whose first parameter
5337 is a column reference.
5339 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
5340 It may optionally include entries for "direction" and/or "transform".
5342 The calling code is responsible for freeing the returned string.
5344 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
5345 if( ! order_array ) {
5346 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
5349 osrfAppSessionStatus(
5351 OSRF_STATUS_INTERNALSERVERERROR,
5352 "osrfMethodException",
5354 "Logic error: ORDER BY clause expected, not found; "
5355 "see error log for more details"
5358 } else if( order_array->type != JSON_ARRAY ) {
5359 osrfLogError( OSRF_LOG_MARK,
5360 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
5362 osrfAppSessionStatus(
5364 OSRF_STATUS_INTERNALSERVERERROR,
5365 "osrfMethodException",
5367 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
5371 growing_buffer* order_buf = buffer_init( 128 );
5372 int first = 1; // boolean
5374 jsonObject* order_spec;
5375 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
5377 if( JSON_HASH != order_spec->type ) {
5378 osrfLogError( OSRF_LOG_MARK,
5379 "%s: Malformed field specification in ORDER BY clause; "
5380 "expected JSON_HASH, found %s",
5381 modulename, json_type( order_spec->type ) );
5383 osrfAppSessionStatus(
5385 OSRF_STATUS_INTERNALSERVERERROR,
5386 "osrfMethodException",
5388 "Malformed ORDER BY clause -- see error log for more details"
5390 buffer_free( order_buf );
5394 const char* class_alias =
5395 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5397 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5399 jsonObject* compare_to = jsonObjectGetKey( order_spec, "compare" );
5401 if( !field || !class_alias ) {
5402 osrfLogError( OSRF_LOG_MARK,
5403 "%s: Missing class or field name in field specification of ORDER BY clause",
5406 osrfAppSessionStatus(
5408 OSRF_STATUS_INTERNALSERVERERROR,
5409 "osrfMethodException",
5411 "Malformed ORDER BY clause -- see error log for more details"
5413 buffer_free( order_buf );
5417 const ClassInfo* order_class_info = search_alias( class_alias );
5418 if( ! order_class_info ) {
5419 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5420 "not in FROM clause, skipping it", modulename, class_alias );
5424 // Add a separating comma, except at the beginning
5428 OSRF_BUFFER_ADD( order_buf, ", " );
5430 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5432 osrfLogError( OSRF_LOG_MARK,
5433 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5434 modulename, class_alias, field );
5436 osrfAppSessionStatus(
5438 OSRF_STATUS_INTERNALSERVERERROR,
5439 "osrfMethodException",
5441 "Invalid field referenced in ORDER BY clause -- "
5442 "see error log for more details"
5446 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5447 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5448 modulename, field );
5450 osrfAppSessionStatus(
5452 OSRF_STATUS_INTERNALSERVERERROR,
5453 "osrfMethodException",
5455 "Virtual field in ORDER BY clause -- see error log for more details"
5457 buffer_free( order_buf );
5461 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5462 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5463 if( ! transform_str ) {
5465 osrfAppSessionStatus(
5467 OSRF_STATUS_INTERNALSERVERERROR,
5468 "osrfMethodException",
5470 "Severe query error in ORDER BY clause -- "
5471 "see error log for more details"
5473 buffer_free( order_buf );
5477 OSRF_BUFFER_ADD( order_buf, transform_str );
5478 free( transform_str );
5479 } else if( compare_to ) {
5480 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5481 if( ! compare_str ) {
5483 osrfAppSessionStatus(
5485 OSRF_STATUS_INTERNALSERVERERROR,
5486 "osrfMethodException",
5488 "Severe query error in ORDER BY clause -- "
5489 "see error log for more details"
5491 buffer_free( order_buf );
5495 buffer_fadd( order_buf, "(%s)", compare_str );
5496 free( compare_str );
5499 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5501 const char* direction =
5502 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5504 if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5505 OSRF_BUFFER_ADD( order_buf, " DESC" );
5507 OSRF_BUFFER_ADD( order_buf, " ASC" );
5511 return buffer_release( order_buf );
5515 @brief Build a SELECT statement.
5516 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5517 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5518 @param meta Pointer to the class metadata for the core class.
5519 @param ctx Pointer to the method context.
5520 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5522 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5523 "order_by", "limit", and "offset".
5525 The SELECT statements built here are distinct from those built for the json_query method.
5527 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5528 osrfHash* meta, osrfMethodContext* ctx ) {
5530 const char* locale = osrf_message_get_last_locale();
5532 osrfHash* fields = osrfHashGet( meta, "fields" );
5533 const char* core_class = osrfHashGet( meta, "classname" );
5535 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5537 jsonObject* selhash = NULL;
5538 jsonObject* defaultselhash = NULL;
5540 growing_buffer* sql_buf = buffer_init( 128 );
5541 growing_buffer* select_buf = buffer_init( 128 );
5543 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5544 defaultselhash = jsonNewObjectType( JSON_HASH );
5545 selhash = defaultselhash;
5548 // If there's no SELECT list for the core class, build one
5549 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5550 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5552 // Add every non-virtual field to the field list
5553 osrfHash* field_def = NULL;
5554 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5555 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5556 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5557 const char* field = osrfHashIteratorKey( field_itr );
5558 jsonObjectPush( field_list, jsonNewObject( field ) );
5561 osrfHashIteratorFree( field_itr );
5562 jsonObjectSetKey( selhash, core_class, field_list );
5565 // Build a list of columns for the SELECT clause
5567 const jsonObject* snode = NULL;
5568 jsonIterator* class_itr = jsonNewIterator( selhash );
5569 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5571 // If the class isn't in the IDL, ignore it
5572 const char* cname = class_itr->key;
5573 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5577 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5578 if( strcmp( core_class, class_itr->key )) {
5582 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5583 if( !found->size ) {
5584 jsonObjectFree( found );
5588 jsonObjectFree( found );
5591 const jsonObject* node = NULL;
5592 jsonIterator* select_itr = jsonNewIterator( snode );
5593 while( (node = jsonIteratorNext( select_itr )) ) {
5594 const char* item_str = jsonObjectGetString( node );
5595 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5596 char* fname = osrfHashGet( field, "name" );
5601 if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5607 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5612 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5613 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5616 i18n = osrfHashGet( field, "i18n" );
5618 if( str_is_true( i18n ) ) {
5619 char* pkey = osrfHashGet( idlClass, "primarykey" );
5620 char* tname = osrfHashGet( idlClass, "tablename" );
5622 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5623 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5624 tname, cname, fname, pkey, cname, pkey, locale, fname );
5626 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5629 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5633 jsonIteratorFree( select_itr );
5636 jsonIteratorFree( class_itr );
5638 char* col_list = buffer_release( select_buf );
5639 char* table = oilsGetRelation( meta );
5641 table = strdup( "(null)" );
5643 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5647 // Clear the query stack (as a fail-safe precaution against possible
5648 // leftover garbage); then push the first query frame onto the stack.
5649 clear_query_stack();
5651 if( add_query_core( NULL, core_class ) ) {
5653 osrfAppSessionStatus(
5655 OSRF_STATUS_INTERNALSERVERERROR,
5656 "osrfMethodException",
5658 "Unable to build query frame for core class"
5660 buffer_free( sql_buf );
5661 if( defaultselhash )
5662 jsonObjectFree( defaultselhash );
5666 // Add the JOIN clauses, if any
5668 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5669 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5670 OSRF_BUFFER_ADD( sql_buf, join_clause );
5671 free( join_clause );
5674 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5675 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5677 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5679 // Add the conditions in the WHERE clause
5680 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5682 osrfAppSessionStatus(
5684 OSRF_STATUS_INTERNALSERVERERROR,
5685 "osrfMethodException",
5687 "Severe query error -- see error log for more details"
5689 buffer_free( sql_buf );
5690 if( defaultselhash )
5691 jsonObjectFree( defaultselhash );
5692 clear_query_stack();
5695 buffer_add( sql_buf, pred );
5699 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5700 if( rest_of_query ) {
5701 const jsonObject* order_by = NULL;
5702 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5704 char* order_by_list = NULL;
5706 if( JSON_ARRAY == order_by->type ) {
5707 order_by_list = buildOrderByFromArray( ctx, order_by );
5708 if( !order_by_list ) {
5709 buffer_free( sql_buf );
5710 if( defaultselhash )
5711 jsonObjectFree( defaultselhash );
5712 clear_query_stack();
5715 } else if( JSON_HASH == order_by->type ) {
5716 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5717 // and build a list of ORDER BY expressions.
5718 growing_buffer* order_buf = buffer_init( 128 );
5720 jsonIterator* class_itr = jsonNewIterator( order_by );
5721 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5723 ClassInfo* order_class_info = search_alias( class_itr->key );
5724 if( ! order_class_info )
5725 continue; // class not referenced by FROM clause? Ignore it.
5727 if( JSON_HASH == snode->type ) {
5729 // If the data for the current class is a JSON_HASH, then it is
5730 // keyed on field name.
5732 const jsonObject* onode = NULL;
5733 jsonIterator* order_itr = jsonNewIterator( snode );
5734 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5736 osrfHash* field_def = osrfHashGet(
5737 order_class_info->fields, order_itr->key );
5739 continue; // Field not defined in IDL? Ignore it.
5740 if( str_is_true( osrfHashGet( field_def, "virtual")))
5741 continue; // Field is virtual? Ignore it.
5743 char* field_str = NULL;
5744 char* direction = NULL;
5745 if( onode->type == JSON_HASH ) {
5746 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5747 field_str = searchFieldTransform(
5748 class_itr->key, field_def, onode );
5750 osrfAppSessionStatus(
5752 OSRF_STATUS_INTERNALSERVERERROR,
5753 "osrfMethodException",
5755 "Severe query error in ORDER BY clause -- "
5756 "see error log for more details"
5758 jsonIteratorFree( order_itr );
5759 jsonIteratorFree( class_itr );
5760 buffer_free( order_buf );
5761 buffer_free( sql_buf );
5762 if( defaultselhash )
5763 jsonObjectFree( defaultselhash );
5764 clear_query_stack();
5768 growing_buffer* field_buf = buffer_init( 16 );
5769 buffer_fadd( field_buf, "\"%s\".%s",
5770 class_itr->key, order_itr->key );
5771 field_str = buffer_release( field_buf );
5774 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5775 const char* dir = jsonObjectGetString( order_by );
5776 if(!strncasecmp( dir, "d", 1 )) {
5777 direction = " DESC";
5781 field_str = strdup( order_itr->key );
5782 const char* dir = jsonObjectGetString( onode );
5783 if( !strncasecmp( dir, "d", 1 )) {
5784 direction = " DESC";
5793 buffer_add( order_buf, ", " );
5796 buffer_add( order_buf, field_str );
5800 buffer_add( order_buf, direction );
5802 } // end while; looping over ORDER BY expressions
5804 jsonIteratorFree( order_itr );
5806 } else if( JSON_STRING == snode->type ) {
5807 // We expect a comma-separated list of sort fields.
5808 const char* str = jsonObjectGetString( snode );
5809 if( strchr( str, ';' )) {
5810 // No semicolons allowed. It is theoretically possible for a
5811 // legitimate semicolon to occur within quotes, but it's not likely
5812 // to occur in practice in the context of an ORDER BY list.
5813 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5814 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5816 osrfAppSessionStatus(
5818 OSRF_STATUS_INTERNALSERVERERROR,
5819 "osrfMethodException",
5821 "Possible attempt at SOL injection -- "
5822 "semicolon found in ORDER BY list"
5825 jsonIteratorFree( class_itr );
5826 buffer_free( order_buf );
5827 buffer_free( sql_buf );
5828 if( defaultselhash )
5829 jsonObjectFree( defaultselhash );
5830 clear_query_stack();
5833 buffer_add( order_buf, str );
5837 } // end while; looping over order_by classes
5839 jsonIteratorFree( class_itr );
5840 order_by_list = buffer_release( order_buf );
5843 osrfLogWarning( OSRF_LOG_MARK,
5844 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5845 "no ORDER BY generated" );
5848 if( order_by_list && *order_by_list ) {
5849 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5850 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5853 free( order_by_list );
5856 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5858 const char* str = jsonObjectGetString( limit );
5868 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5870 const char* str = jsonObjectGetString( offset );
5881 if( defaultselhash )
5882 jsonObjectFree( defaultselhash );
5883 clear_query_stack();
5885 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5886 return buffer_release( sql_buf );
5889 int doJSONSearch ( osrfMethodContext* ctx ) {
5890 if(osrfMethodVerifyContext( ctx )) {
5891 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5895 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5899 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5903 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5904 flags |= SELECT_DISTINCT;
5906 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5907 flags |= DISABLE_I18N;
5909 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5910 clear_query_stack(); // a possibly needless precaution
5911 char* sql = buildQuery( ctx, hash, flags );
5912 clear_query_stack();
5919 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5922 dbhandle = writehandle;
5924 dbi_result result = dbi_conn_query( dbhandle, sql );
5927 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5929 if( dbi_result_first_row( result )) {
5930 /* JSONify the result */
5931 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5934 jsonObject* return_val = oilsMakeJSONFromResult( result );
5935 osrfAppRespond( ctx, return_val );
5936 jsonObjectFree( return_val );
5937 } while( dbi_result_next_row( result ));
5940 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5943 osrfAppRespondComplete( ctx, NULL );
5945 /* clean up the query */
5946 dbi_result_free( result );
5951 int errnum = dbi_conn_error( dbhandle, &msg );
5952 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5953 modulename, sql, errnum, msg ? msg : "(No description available)" );
5954 osrfAppSessionStatus(
5956 OSRF_STATUS_INTERNALSERVERERROR,
5957 "osrfMethodException",
5959 "Severe query error -- see error log for more details"
5961 if( !oilsIsDBConnected( dbhandle ))
5962 osrfAppSessionPanic( ctx->session );
5969 // The last parameter, err, is used to report an error condition by updating an int owned by
5970 // the calling code.
5972 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5973 // It is the responsibility of the calling code to initialize *err before the
5974 // call, so that it will be able to make sense of the result.
5976 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5977 // redundant anyway.
5978 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5979 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5981 const char* tz = _sanitize_tz_name(ctx->session->session_tz);
5984 dbhandle = writehandle;
5986 char* core_class = osrfHashGet( class_meta, "classname" );
5987 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5989 char* pkey = osrfHashGet( class_meta, "primarykey" );
5991 if (!ctx->session->userData)
5992 (void) initSessionCache( ctx );
5994 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5995 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5996 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5998 int i_respond_directly = 0;
5999 int flesh_depth = 0;
6001 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
6003 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
6008 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
6010 // Setting the timezone if requested and not in a transaction
6011 if (!getXactId(ctx)) {
6015 dbi_result tz_res = dbi_conn_queryf( writehandle, "SET timezone TO '%s'; -- cstore", tz );
6017 osrfLogError( OSRF_LOG_MARK, "%s: Error setting timezone %s", modulename, tz);
6018 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
6019 "osrfMethodException", ctx->request, "Error setting timezone" );
6020 if( !oilsIsDBConnected( writehandle )) {
6021 osrfAppSessionPanic( ctx->session );
6025 dbi_result_free( tz_res );
6030 dbi_result res = dbi_conn_queryf( writehandle, "SET timezone TO DEFAULT; -- cstore" );
6032 osrfLogError( OSRF_LOG_MARK, "%s: Error resetting timezone", modulename);
6033 if( !oilsIsDBConnected( writehandle )) {
6034 osrfAppSessionPanic( ctx->session );
6038 dbi_result_free( res );
6044 dbi_result result = dbi_conn_query( dbhandle, sql );
6046 if( NULL == result ) {
6048 int errnum = dbi_conn_error( dbhandle, &msg );
6049 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
6050 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
6051 msg ? msg : "(No description available)" );
6052 if( !oilsIsDBConnected( dbhandle ))
6053 osrfAppSessionPanic( ctx->session );
6054 osrfAppSessionStatus(
6056 OSRF_STATUS_INTERNALSERVERERROR,
6057 "osrfMethodException",
6059 "Severe query error -- see error log for more details"
6066 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
6070 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
6071 jsonObject* row_obj = NULL;
6073 // The following two steps are for verifyObjectPCRUD()'s benefit.
6074 // 1. get the flesh depth
6075 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
6077 flesh_depth = (int) jsonObjectGetNumber( _tmp );
6078 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
6079 flesh_depth = max_flesh_depth;
6082 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
6083 // over the whole life of this request. This means if we've already set
6084 // up a rs_size_req_%d, do nothing.
6085 // a. Incidentally, we can also use this opportunity to set i_respond_directly
6086 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
6087 if( !rs_size ) { // pointer null, so value not set in hash
6088 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
6089 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
6091 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
6092 unsigned long long result_count = dbi_result_get_numrows( result );
6093 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
6094 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
6097 if( dbi_result_first_row( result )) {
6099 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
6100 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
6101 // eliminate the duplicates.
6102 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
6103 osrfHash* dedup = osrfNewHash();
6105 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
6106 char* pkey_val = oilsFMGetString( row_obj, pkey );
6107 if( osrfHashGet( dedup, pkey_val ) ) {
6108 jsonObjectFree( row_obj );
6111 if( !enforce_pcrud || !need_to_verify ||
6112 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
6113 osrfHashSet( dedup, pkey_val, pkey_val );
6114 jsonObjectPush( res_list, row_obj );
6117 } while( dbi_result_next_row( result ));
6118 osrfHashFree( dedup );
6121 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
6125 /* clean up the query */
6126 dbi_result_free( result );
6129 // If we're asked to flesh, and there's anything to flesh, then flesh it
6130 // (formerly we would skip fleshing if in pcrud mode, but now we support
6131 // fleshing even in PCRUD).
6132 if( res_list->size ) {
6133 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
6134 jsonObject* flesh_fields;
6135 jsonObject* flesh_blob = NULL;
6136 osrfStringArray* link_fields = NULL;
6137 osrfHash* links = NULL;
6141 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
6142 if( temp_blob && flesh_depth > 0 ) {
6144 flesh_blob = jsonObjectClone( temp_blob );
6145 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
6147 links = osrfHashGet( class_meta, "links" );
6149 // Make an osrfStringArray of the names of fields to be fleshed
6150 if( flesh_fields ) {
6151 if( flesh_fields->size == 1 ) {
6152 const char* _t = jsonObjectGetString(
6153 jsonObjectGetIndex( flesh_fields, 0 ) );
6154 if( !strcmp( _t, "*" ))
6155 link_fields = osrfHashKeys( links );
6158 if( !link_fields ) {
6160 link_fields = osrfNewStringArray( 1 );
6161 jsonIterator* _i = jsonNewIterator( flesh_fields );
6162 while ((_f = jsonIteratorNext( _i ))) {
6163 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
6165 jsonIteratorFree( _i );
6168 want_flesh = link_fields ? 1 : 0;
6172 osrfHash* fields = osrfHashGet( class_meta, "fields" );
6174 // Iterate over the JSON_ARRAY of rows
6176 unsigned long res_idx = 0;
6177 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
6180 const char* link_field;
6182 // Iterate over the list of fleshable fields
6184 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
6186 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
6188 osrfHash* kid_link = osrfHashGet( links, link_field );
6190 continue; // Not a link field; skip it
6192 osrfHash* field = osrfHashGet( fields, link_field );
6194 continue; // Not a field at all; skip it (IDL is ill-formed)
6196 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
6197 osrfHashGet( kid_link, "class" ));
6199 continue; // The class it links to doesn't exist; skip it
6201 const char* reltype = osrfHashGet( kid_link, "reltype" );
6203 continue; // No reltype; skip it (IDL is ill-formed)
6205 osrfHash* value_field = field;
6207 if( !strcmp( reltype, "has_many" )
6208 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
6209 value_field = osrfHashGet(
6210 fields, osrfHashGet( class_meta, "primarykey" ) );
6213 int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
6214 // fleshing pcrud case: we require the controller in need_to_verify mode
6215 if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
6216 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
6220 (unsigned long) atoi( osrfHashGet(field, "array_position") ),
6222 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
6228 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
6230 if( link_map->size > 0 ) {
6231 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
6234 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
6239 osrfHashGet( kid_link, "class" ),
6246 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
6247 osrfHashGet( kid_link, "field" ),
6248 osrfHashGet( kid_link, "class" ),
6249 osrfHashGet( kid_link, "key" ),
6250 osrfHashGet( kid_link, "reltype" )
6253 const char* search_key = jsonObjectGetString(
6254 jsonObjectGetIndex( cur,
6255 atoi( osrfHashGet( value_field, "array_position" ) )
6260 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
6264 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
6266 // construct WHERE clause
6267 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
6270 osrfHashGet( kid_link, "key" ),
6271 jsonNewObject( search_key )
6274 // construct the rest of the query, mostly
6275 // by copying pieces of the previous level of query
6276 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
6277 jsonObjectSetKey( rest_of_query, "flesh",
6278 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
6282 jsonObjectSetKey( rest_of_query, "flesh_fields",
6283 jsonObjectClone( flesh_blob ));
6285 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
6286 jsonObjectSetKey( rest_of_query, "order_by",
6287 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
6291 if( jsonObjectGetKeyConst( query_hash, "select" )) {
6292 jsonObjectSetKey( rest_of_query, "select",
6293 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
6297 // do the query, recursively, to expand the fleshable field
6298 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
6299 where_clause, rest_of_query, err );
6301 jsonObjectFree( where_clause );
6302 jsonObjectFree( rest_of_query );
6305 osrfStringArrayFree( link_fields );
6306 jsonObjectFree( res_list );
6307 jsonObjectFree( flesh_blob );
6311 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
6312 osrfHashGet( kid_link, "class" ), kids->size );
6314 // Traverse the result set
6315 jsonObject* X = NULL;
6316 if( link_map->size > 0 && kids->size > 0 ) {
6318 kids = jsonNewObjectType( JSON_ARRAY );
6320 jsonObject* _k_node;
6321 unsigned long res_idx = 0;
6322 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
6328 (unsigned long) atoi(
6334 osrfHashGet( kid_link, "class" )
6338 osrfStringArrayGetString( link_map, 0 )
6346 } // end while loop traversing X
6349 if (kids->size > 0) {
6351 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
6352 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
6354 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
6355 osrfHashGet( kid_link, "field" ));
6358 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6359 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
6364 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
6366 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
6367 osrfHashGet( kid_link, "field" ) );
6370 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6371 jsonObjectClone( kids )
6376 jsonObjectFree( kids );
6380 jsonObjectFree( kids );
6382 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
6383 osrfHashGet( kid_link, "field" ) );
6384 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
6386 } // end while loop traversing list of fleshable fields
6389 if( i_respond_directly ) {
6390 if ( *methodtype == 'i' ) {
6391 osrfAppRespond( ctx,
6392 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
6394 osrfAppRespond( ctx, cur );
6397 } // end while loop traversing res_list
6398 jsonObjectFree( flesh_blob );
6399 osrfStringArrayFree( link_fields );
6402 if( i_respond_directly ) {
6403 jsonObjectFree( res_list );
6404 return jsonNewObjectType( JSON_ARRAY );
6411 int doUpdate( osrfMethodContext* ctx ) {
6412 if( osrfMethodVerifyContext( ctx )) {
6413 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6418 timeout_needs_resetting = 1;
6420 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6422 jsonObject* target = NULL;
6424 target = jsonObjectGetIndex( ctx->params, 1 );
6426 target = jsonObjectGetIndex( ctx->params, 0 );
6428 if(!verifyObjectClass( ctx, target )) {
6429 osrfAppRespondComplete( ctx, NULL );
6433 if( getXactId( ctx ) == NULL ) {
6434 osrfAppSessionStatus(
6436 OSRF_STATUS_BADREQUEST,
6437 "osrfMethodException",
6439 "No active transaction -- required for UPDATE"
6441 osrfAppRespondComplete( ctx, NULL );
6445 // The following test is harmless but redundant. If a class is
6446 // readonly, we don't register an update method for it.
6447 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6448 osrfAppSessionStatus(
6450 OSRF_STATUS_BADREQUEST,
6451 "osrfMethodException",
6453 "Cannot UPDATE readonly class"
6455 osrfAppRespondComplete( ctx, NULL );
6459 const char* trans_id = getXactId( ctx );
6461 // Set the last_xact_id
6462 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6464 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6465 trans_id, target->classname, index );
6466 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6469 char* pkey = osrfHashGet( meta, "primarykey" );
6470 osrfHash* fields = osrfHashGet( meta, "fields" );
6472 char* id = oilsFMGetString( target, pkey );
6476 "%s updating %s object with %s = %s",
6478 osrfHashGet( meta, "fieldmapper" ),
6483 dbhandle = writehandle;
6484 growing_buffer* sql = buffer_init( 128 );
6485 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6488 osrfHash* field_def = NULL;
6489 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6490 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6492 // Skip virtual fields, and the primary key
6493 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6496 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6500 const char* field_name = osrfHashIteratorKey( field_itr );
6501 if( ! strcmp( field_name, pkey ) )
6504 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6506 int value_is_numeric = 0; // boolean
6508 if( field_object && field_object->classname ) {
6509 value = oilsFMGetString(
6511 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6513 } else if( field_object && JSON_BOOL == field_object->type ) {
6514 if( jsonBoolIsTrue( field_object ) )
6515 value = strdup( "t" );
6517 value = strdup( "f" );
6519 value = jsonObjectToSimpleString( field_object );
6520 if( field_object && JSON_NUMBER == field_object->type )
6521 value_is_numeric = 1;
6524 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6525 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6527 if( !field_object || field_object->type == JSON_NULL ) {
6528 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6529 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6533 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6534 buffer_fadd( sql, " %s = NULL", field_name );
6537 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6541 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6543 const char* numtype = get_datatype( field_def );
6544 if( !strncmp( numtype, "INT", 3 ) ) {
6545 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6546 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6547 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6549 // Must really be intended as a string, so quote it
6550 if( dbi_conn_quote_string( dbhandle, &value )) {
6551 buffer_fadd( sql, " %s = %s", field_name, value );
6553 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6554 modulename, value );
6555 osrfAppSessionStatus(
6557 OSRF_STATUS_INTERNALSERVERERROR,
6558 "osrfMethodException",
6560 "Error quoting string -- please see the error log for more details"
6564 osrfHashIteratorFree( field_itr );
6566 osrfAppRespondComplete( ctx, NULL );
6571 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6574 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6578 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6579 buffer_fadd( sql, " %s = %s", field_name, value );
6581 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6582 osrfAppSessionStatus(
6584 OSRF_STATUS_INTERNALSERVERERROR,
6585 "osrfMethodException",
6587 "Error quoting string -- please see the error log for more details"
6591 osrfHashIteratorFree( field_itr );
6593 osrfAppRespondComplete( ctx, NULL );
6602 osrfHashIteratorFree( field_itr );
6604 jsonObject* obj = jsonNewObject( id );
6606 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6607 dbi_conn_quote_string( dbhandle, &id );
6609 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6611 char* query = buffer_release( sql );
6612 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6614 dbi_result result = dbi_conn_query( dbhandle, query );
6619 jsonObjectFree( obj );
6620 obj = jsonNewObject( NULL );
6622 int errnum = dbi_conn_error( dbhandle, &msg );
6625 "%s ERROR updating %s object with %s = %s: %d %s",
6627 osrfHashGet( meta, "fieldmapper" ),
6631 msg ? msg : "(No description available)"
6633 osrfAppSessionStatus(
6635 OSRF_STATUS_INTERNALSERVERERROR,
6636 "osrfMethodException",
6638 "Error in updating a row -- please see the error log for more details"
6640 if( !oilsIsDBConnected( dbhandle ))
6641 osrfAppSessionPanic( ctx->session );
6644 dbi_result_free( result );
6647 osrfAppRespondComplete( ctx, obj );
6648 jsonObjectFree( obj );
6652 int doDelete( osrfMethodContext* ctx ) {
6653 if( osrfMethodVerifyContext( ctx )) {
6654 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6659 timeout_needs_resetting = 1;
6661 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6663 if( getXactId( ctx ) == NULL ) {
6664 osrfAppSessionStatus(
6666 OSRF_STATUS_BADREQUEST,
6667 "osrfMethodException",
6669 "No active transaction -- required for DELETE"
6671 osrfAppRespondComplete( ctx, NULL );
6675 // The following test is harmless but redundant. If a class is
6676 // readonly, we don't register a delete method for it.
6677 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6678 osrfAppSessionStatus(
6680 OSRF_STATUS_BADREQUEST,
6681 "osrfMethodException",
6683 "Cannot DELETE readonly class"
6685 osrfAppRespondComplete( ctx, NULL );
6689 dbhandle = writehandle;
6691 char* pkey = osrfHashGet( meta, "primarykey" );
6698 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6699 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6700 osrfAppRespondComplete( ctx, NULL );
6704 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6706 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6707 osrfAppRespondComplete( ctx, NULL );
6710 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6715 "%s deleting %s object with %s = %s",
6717 osrfHashGet( meta, "fieldmapper" ),
6722 jsonObject* obj = jsonNewObject( id );
6724 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6725 dbi_conn_quote_string( writehandle, &id );
6727 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6728 osrfHashGet( meta, "tablename" ), pkey, id );
6733 jsonObjectFree( obj );
6734 obj = jsonNewObject( NULL );
6736 int errnum = dbi_conn_error( writehandle, &msg );
6739 "%s ERROR deleting %s object with %s = %s: %d %s",
6741 osrfHashGet( meta, "fieldmapper" ),
6745 msg ? msg : "(No description available)"
6747 osrfAppSessionStatus(
6749 OSRF_STATUS_INTERNALSERVERERROR,
6750 "osrfMethodException",
6752 "Error in deleting a row -- please see the error log for more details"
6754 if( !oilsIsDBConnected( writehandle ))
6755 osrfAppSessionPanic( ctx->session );
6757 dbi_result_free( result );
6761 osrfAppRespondComplete( ctx, obj );
6762 jsonObjectFree( obj );
6767 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6768 @param result An iterator for a result set; we only look at the current row.
6769 @param @meta Pointer to the class metadata for the core class.
6770 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6772 If a column is not defined in the IDL, or if it has no array_position defined for it in
6773 the IDL, or if it is defined as virtual, ignore it.
6775 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6776 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6777 array_position in the IDL.
6779 A field defined in the IDL but not represented in the returned row will leave a hole
6780 in the JSON_ARRAY. In effect it will be treated as a null value.
6782 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6783 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6784 classname corresponding to the @a meta argument.
6786 The calling code is responsible for freeing the the resulting jsonObject by calling
6789 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6790 if( !( result && meta )) return NULL;
6792 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6793 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6794 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6796 osrfHash* fields = osrfHashGet( meta, "fields" );
6798 int columnIndex = 1;
6799 const char* columnName;
6801 /* cycle through the columns in the row returned from the database */
6802 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6804 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6806 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6808 /* determine the field type and storage attributes */
6809 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6810 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6812 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6813 // or if it has no sequence number there, or if it's virtual, skip it.
6814 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6817 if( str_is_true( osrfHashGet( _f, "virtual" )))
6818 continue; // skip this column: IDL says it's virtual
6820 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6821 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6822 continue; // since we assign sequence numbers dynamically as we load the IDL.
6824 fmIndex = atoi( pos );
6825 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6827 continue; // This field is not defined in the IDL
6830 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6831 // sequence number from the IDL (which is likely to be different from the sequence
6832 // of columns in the SELECT clause).
6833 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6834 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6839 case DBI_TYPE_INTEGER :
6841 if( attr & DBI_INTEGER_SIZE8 )
6842 jsonObjectSetIndex( object, fmIndex,
6843 jsonNewNumberObject(
6844 dbi_result_get_longlong_idx( result, columnIndex )));
6846 jsonObjectSetIndex( object, fmIndex,
6847 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6851 case DBI_TYPE_DECIMAL :
6852 jsonObjectSetIndex( object, fmIndex,
6853 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6856 case DBI_TYPE_STRING :
6861 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6866 case DBI_TYPE_DATETIME : {
6868 char dt_string[ 256 ] = "";
6871 // Fetch the date column as a time_t
6872 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6874 // Translate the time_t to a human-readable string
6875 if( !( attr & DBI_DATETIME_DATE )) {
6876 gmtime_r( &_tmp_dt, &gmdt );
6877 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6878 } else if( !( attr & DBI_DATETIME_TIME )) {
6879 gmtime_r( &_tmp_dt, &gmdt );
6880 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6882 localtime_r( &_tmp_dt, &gmdt );
6883 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6886 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6890 case DBI_TYPE_BINARY :
6891 osrfLogError( OSRF_LOG_MARK,
6892 "Can't do binary at column %s : index %d", columnName, columnIndex );
6901 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6902 if( !result ) return NULL;
6904 jsonObject* object = jsonNewObject( NULL );
6907 char dt_string[ 256 ];
6910 int columnIndex = 1;
6912 unsigned short type;
6913 const char* columnName;
6915 /* cycle through the column list */
6916 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6918 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6920 /* determine the field type and storage attributes */
6921 type = dbi_result_get_field_type_idx( result, columnIndex );
6922 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6924 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6925 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6930 case DBI_TYPE_INTEGER :
6932 if( attr & DBI_INTEGER_SIZE8 )
6933 jsonObjectSetKey( object, columnName,
6934 jsonNewNumberObject( dbi_result_get_longlong_idx(
6935 result, columnIndex )) );
6937 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6938 dbi_result_get_int_idx( result, columnIndex )) );
6941 case DBI_TYPE_DECIMAL :
6942 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6943 dbi_result_get_double_idx( result, columnIndex )) );
6946 case DBI_TYPE_STRING :
6947 jsonObjectSetKey( object, columnName,
6948 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6951 case DBI_TYPE_DATETIME :
6953 memset( dt_string, '\0', sizeof( dt_string ));
6954 memset( &gmdt, '\0', sizeof( gmdt ));
6956 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6958 if( !( attr & DBI_DATETIME_DATE )) {
6959 gmtime_r( &_tmp_dt, &gmdt );
6960 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6961 } else if( !( attr & DBI_DATETIME_TIME )) {
6962 gmtime_r( &_tmp_dt, &gmdt );
6963 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6965 localtime_r( &_tmp_dt, &gmdt );
6966 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6969 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6972 case DBI_TYPE_BINARY :
6973 osrfLogError( OSRF_LOG_MARK,
6974 "Can't do binary at column %s : index %d", columnName, columnIndex );
6978 } // end while loop traversing result
6983 // Interpret a string as true or false
6984 int str_is_true( const char* str ) {
6985 if( NULL == str || strcasecmp( str, "true" ) )
6991 // Interpret a jsonObject as true or false
6992 static int obj_is_true( const jsonObject* obj ) {
6995 else switch( obj->type )
7003 if( strcasecmp( obj->value.s, "true" ) )
7007 case JSON_NUMBER : // Support 1/0 for perl's sake
7008 if( jsonObjectGetNumber( obj ) == 1.0 )
7017 // Translate a numeric code into a text string identifying a type of
7018 // jsonObject. To be used for building error messages.
7019 static const char* json_type( int code ) {
7025 return "JSON_ARRAY";
7027 return "JSON_STRING";
7029 return "JSON_NUMBER";
7035 return "(unrecognized)";
7039 // Extract the "primitive" attribute from an IDL field definition.
7040 // If we haven't initialized the app, then we must be running in
7041 // some kind of testbed. In that case, default to "string".
7042 static const char* get_primitive( osrfHash* field ) {
7043 const char* s = osrfHashGet( field, "primitive" );
7045 if( child_initialized )
7048 "%s ERROR No \"datatype\" attribute for field \"%s\"",
7050 osrfHashGet( field, "name" )
7058 // Extract the "datatype" attribute from an IDL field definition.
7059 // If we haven't initialized the app, then we must be running in
7060 // some kind of testbed. In that case, default to to NUMERIC,
7061 // since we look at the datatype only for numbers.
7062 static const char* get_datatype( osrfHash* field ) {
7063 const char* s = osrfHashGet( field, "datatype" );
7065 if( child_initialized )
7068 "%s ERROR No \"datatype\" attribute for field \"%s\"",
7070 osrfHashGet( field, "name" )
7079 @brief Determine whether a string is potentially a valid SQL identifier.
7080 @param s The identifier to be tested.
7081 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
7083 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
7084 need to follow all the rules exactly, such as requiring that the first character not
7087 We allow leading and trailing white space. In between, we do not allow punctuation
7088 (except for underscores and dollar signs), control characters, or embedded white space.
7090 More pedantically we should allow quoted identifiers containing arbitrary characters, but
7091 for the foreseeable future such quoted identifiers are not likely to be an issue.
7093 int is_identifier( const char* s) {
7097 // Skip leading white space
7098 while( isspace( (unsigned char) *s ) )
7102 return 0; // Nothing but white space? Not okay.
7104 // Check each character until we reach white space or
7105 // end-of-string. Letters, digits, underscores, and
7106 // dollar signs are okay. With the exception of periods
7107 // (as in schema.identifier), control characters and other
7108 // punctuation characters are not okay. Anything else
7109 // is okay -- it could for example be part of a multibyte
7110 // UTF8 character such as a letter with diacritical marks,
7111 // and those are allowed.
7113 if( isalnum( (unsigned char) *s )
7117 ; // Fine; keep going
7118 else if( ispunct( (unsigned char) *s )
7119 || iscntrl( (unsigned char) *s ) )
7122 } while( *s && ! isspace( (unsigned char) *s ) );
7124 // If we found any white space in the above loop,
7125 // the rest had better be all white space.
7127 while( isspace( (unsigned char) *s ) )
7131 return 0; // White space was embedded within non-white space
7137 @brief Determine whether to accept a character string as a comparison operator.
7138 @param op The candidate comparison operator.
7139 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
7141 We don't validate the operator for real. We just make sure that it doesn't contain
7142 any semicolons or white space (with special exceptions for a few specific operators).
7143 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
7144 space but it's still not a valid operator, then the database will complain.
7146 Another approach would be to compare the string against a short list of approved operators.
7147 We don't do that because we want to allow custom operators like ">100*", which at this
7148 writing would be difficult or impossible to express otherwise in a JSON query.
7150 int is_good_operator( const char* op ) {
7151 if( !op ) return 0; // Sanity check
7155 if( isspace( (unsigned char) *s ) ) {
7156 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
7157 // and IS NOT DISTINCT FROM.
7158 if( !strcasecmp( op, "similar to" ) )
7160 else if( !strcasecmp( op, "is distinct from" ) )
7162 else if( !strcasecmp( op, "is not distinct from" ) )
7167 else if( ';' == *s )
7175 @name Query Frame Management
7177 The following machinery supports a stack of query frames for use by SELECT().
7179 A query frame caches information about one level of a SELECT query. When we enter
7180 a subquery, we push another query frame onto the stack, and pop it off when we leave.
7182 The query frame stores information about the core class, and about any joined classes
7185 The main purpose is to map table aliases to classes and tables, so that a query can
7186 join to the same table more than once. A secondary goal is to reduce the number of
7187 lookups in the IDL by caching the results.
7191 #define STATIC_CLASS_INFO_COUNT 3
7193 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
7196 @brief Allocate a ClassInfo as raw memory.
7197 @return Pointer to the newly allocated ClassInfo.
7199 Except for the in_use flag, which is used only by the allocation and deallocation
7200 logic, we don't initialize the ClassInfo here.
7202 static ClassInfo* allocate_class_info( void ) {
7203 // In order to reduce the number of mallocs and frees, we return a static
7204 // instance of ClassInfo, if we can find one that we're not already using.
7205 // We rely on the fact that the compiler will implicitly initialize the
7206 // static instances so that in_use == 0.
7209 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7210 if( ! static_class_info[ i ].in_use ) {
7211 static_class_info[ i ].in_use = 1;
7212 return static_class_info + i;
7216 // The static ones are all in use. Malloc one.
7218 return safe_malloc( sizeof( ClassInfo ) );
7222 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
7223 @param info Pointer to the ClassInfo to be cleared.
7225 static void clear_class_info( ClassInfo* info ) {
7230 // Free any malloc'd strings
7232 if( info->alias != info->alias_store )
7233 free( info->alias );
7235 if( info->class_name != info->class_name_store )
7236 free( info->class_name );
7238 free( info->source_def );
7240 info->alias = info->class_name = info->source_def = NULL;
7245 @brief Free a ClassInfo and everything it owns.
7246 @param info Pointer to the ClassInfo to be freed.
7248 static void free_class_info( ClassInfo* info ) {
7253 clear_class_info( info );
7255 // If it's one of the static instances, just mark it as not in use
7258 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7259 if( info == static_class_info + i ) {
7260 static_class_info[ i ].in_use = 0;
7265 // Otherwise it must have been malloc'd, so free it
7271 @brief Populate an already-allocated ClassInfo.
7272 @param info Pointer to the ClassInfo to be populated.
7273 @param alias Alias for the class. If it is NULL, or an empty string, use the class
7275 @param class Name of the class.
7276 @return Zero if successful, or 1 if not.
7278 Populate the ClassInfo with copies of the alias and class name, and with pointers to
7279 the relevant portions of the IDL for the specified class.
7281 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
7284 osrfLogError( OSRF_LOG_MARK,
7285 "%s ERROR: No ClassInfo available to populate", modulename );
7286 info->alias = info->class_name = info->source_def = NULL;
7287 info->class_def = info->fields = info->links = NULL;
7292 osrfLogError( OSRF_LOG_MARK,
7293 "%s ERROR: No class name provided for lookup", modulename );
7294 info->alias = info->class_name = info->source_def = NULL;
7295 info->class_def = info->fields = info->links = NULL;
7299 // Alias defaults to class name if not supplied
7300 if( ! alias || ! alias[ 0 ] )
7303 // Look up class info in the IDL
7304 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
7306 osrfLogError( OSRF_LOG_MARK,
7307 "%s ERROR: Class %s not defined in IDL", modulename, class );
7308 info->alias = info->class_name = info->source_def = NULL;
7309 info->class_def = info->fields = info->links = NULL;
7311 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
7312 osrfLogError( OSRF_LOG_MARK,
7313 "%s ERROR: Class %s is defined as virtual", modulename, class );
7314 info->alias = info->class_name = info->source_def = NULL;
7315 info->class_def = info->fields = info->links = NULL;
7319 osrfHash* links = osrfHashGet( class_def, "links" );
7321 osrfLogError( OSRF_LOG_MARK,
7322 "%s ERROR: No links defined in IDL for class %s", modulename, class );
7323 info->alias = info->class_name = info->source_def = NULL;
7324 info->class_def = info->fields = info->links = NULL;
7328 osrfHash* fields = osrfHashGet( class_def, "fields" );
7330 osrfLogError( OSRF_LOG_MARK,
7331 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
7332 info->alias = info->class_name = info->source_def = NULL;
7333 info->class_def = info->fields = info->links = NULL;
7337 char* source_def = oilsGetRelation( class_def );
7341 // We got everything we need, so populate the ClassInfo
7342 if( strlen( alias ) > ALIAS_STORE_SIZE )
7343 info->alias = strdup( alias );
7345 strcpy( info->alias_store, alias );
7346 info->alias = info->alias_store;
7349 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
7350 info->class_name = strdup( class );
7352 strcpy( info->class_name_store, class );
7353 info->class_name = info->class_name_store;
7356 info->source_def = source_def;
7358 info->class_def = class_def;
7359 info->links = links;
7360 info->fields = fields;
7365 #define STATIC_FRAME_COUNT 3
7367 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
7370 @brief Allocate a QueryFrame as raw memory.
7371 @return Pointer to the newly allocated QueryFrame.
7373 Except for the in_use flag, which is used only by the allocation and deallocation
7374 logic, we don't initialize the QueryFrame here.
7376 static QueryFrame* allocate_frame( void ) {
7377 // In order to reduce the number of mallocs and frees, we return a static
7378 // instance of QueryFrame, if we can find one that we're not already using.
7379 // We rely on the fact that the compiler will implicitly initialize the
7380 // static instances so that in_use == 0.
7383 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7384 if( ! static_frame[ i ].in_use ) {
7385 static_frame[ i ].in_use = 1;
7386 return static_frame + i;
7390 // The static ones are all in use. Malloc one.
7392 return safe_malloc( sizeof( QueryFrame ) );
7396 @brief Free a QueryFrame, and all the memory it owns.
7397 @param frame Pointer to the QueryFrame to be freed.
7399 static void free_query_frame( QueryFrame* frame ) {
7404 clear_class_info( &frame->core );
7406 // Free the join list
7408 ClassInfo* info = frame->join_list;
7411 free_class_info( info );
7415 frame->join_list = NULL;
7418 // If the frame is a static instance, just mark it as unused
7420 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7421 if( frame == static_frame + i ) {
7422 static_frame[ i ].in_use = 0;
7427 // Otherwise it must have been malloc'd, so free it
7433 @brief Search a given QueryFrame for a specified alias.
7434 @param frame Pointer to the QueryFrame to be searched.
7435 @param target The alias for which to search.
7436 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7438 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7439 if( ! frame || ! target ) {
7443 ClassInfo* found_class = NULL;
7445 if( !strcmp( target, frame->core.alias ) )
7446 return &(frame->core);
7448 ClassInfo* curr_class = frame->join_list;
7449 while( curr_class ) {
7450 if( strcmp( target, curr_class->alias ) )
7451 curr_class = curr_class->next;
7453 found_class = curr_class;
7463 @brief Push a new (blank) QueryFrame onto the stack.
7465 static void push_query_frame( void ) {
7466 QueryFrame* frame = allocate_frame();
7467 frame->join_list = NULL;
7468 frame->next = curr_query;
7470 // Initialize the ClassInfo for the core class
7471 ClassInfo* core = &frame->core;
7472 core->alias = core->class_name = core->source_def = NULL;
7473 core->class_def = core->fields = core->links = NULL;
7479 @brief Pop a QueryFrame off the stack and destroy it.
7481 static void pop_query_frame( void ) {
7486 QueryFrame* popped = curr_query;
7487 curr_query = popped->next;
7489 free_query_frame( popped );
7493 @brief Populate the ClassInfo for the core class.
7494 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7495 class name as an alias.
7496 @param class_name Name of the core class.
7497 @return Zero if successful, or 1 if not.
7499 Populate the ClassInfo of the core class with copies of the alias and class name, and
7500 with pointers to the relevant portions of the IDL for the core class.
7502 static int add_query_core( const char* alias, const char* class_name ) {
7505 if( ! curr_query ) {
7506 osrfLogError( OSRF_LOG_MARK,
7507 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7509 } else if( curr_query->core.alias ) {
7510 osrfLogError( OSRF_LOG_MARK,
7511 "%s ERROR: Core class %s already populated as %s",
7512 modulename, curr_query->core.class_name, curr_query->core.alias );
7516 build_class_info( &curr_query->core, alias, class_name );
7517 if( curr_query->core.alias )
7520 osrfLogError( OSRF_LOG_MARK,
7521 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7527 @brief Search the current QueryFrame for a specified alias.
7528 @param target The alias for which to search.
7529 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7531 static inline ClassInfo* search_alias( const char* target ) {
7532 return search_alias_in_frame( curr_query, target );
7536 @brief Search all levels of query for a specified alias, starting with the current query.
7537 @param target The alias for which to search.
7538 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7540 static ClassInfo* search_all_alias( const char* target ) {
7541 ClassInfo* found_class = NULL;
7542 QueryFrame* curr_frame = curr_query;
7544 while( curr_frame ) {
7545 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7548 curr_frame = curr_frame->next;
7555 @brief Add a class to the list of classes joined to the current query.
7556 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7557 the class name as an alias.
7558 @param classname The name of the class to be added.
7559 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7561 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7563 if( ! classname || ! *classname ) { // sanity check
7564 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7571 const ClassInfo* conflict = search_alias( alias );
7573 osrfLogError( OSRF_LOG_MARK,
7574 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7575 modulename, alias, conflict->class_name );
7579 ClassInfo* info = allocate_class_info();
7581 if( build_class_info( info, alias, classname ) ) {
7582 free_class_info( info );
7586 // Add the new ClassInfo to the join list of the current QueryFrame
7587 info->next = curr_query->join_list;
7588 curr_query->join_list = info;
7594 @brief Destroy all nodes on the query stack.
7596 static void clear_query_stack( void ) {
7602 @brief Implement the set_audit_info method.
7603 @param ctx Pointer to the method context.
7604 @return Zero if successful, or -1 if not.
7606 Issue a SAVEPOINT to the database server.
7611 - workstation id (int)
7613 If user id is not provided the authkey will be used.
7614 For PCRUD the authkey is always used, even if a user is provided.
7616 int setAuditInfo( osrfMethodContext* ctx ) {
7617 if(osrfMethodVerifyContext( ctx )) {
7618 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
7622 // Get the user id from the parameters
7623 const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7625 if( enforce_pcrud || !user_id ) {
7626 timeout_needs_resetting = 1;
7627 const jsonObject* user = verifyUserPCRUD( ctx );
7630 osrfAppRespondComplete( ctx, NULL );
7634 // Not PCRUD and have a user_id?
7635 int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7636 osrfAppRespondComplete( ctx, NULL );
7641 @brief Save a audit info
7642 @param ctx Pointer to the method context.
7643 @param user_id User ID to write as a string
7644 @param ws_id Workstation ID to write as a string
7646 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7647 if( ctx && ctx->session ) {
7648 osrfAppSession* session = ctx->session;
7650 osrfHash* cache = session->userData;
7652 // If the session doesn't already have a hash, create one. Make sure
7653 // that the application session frees the hash when it terminates.
7654 if( NULL == cache ) {
7655 session->userData = cache = osrfNewHash();
7656 osrfHashSetCallback( cache, &sessionDataFree );
7657 ctx->session->userDataFree = &userDataFree;
7660 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7662 osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7664 int errnum = dbi_conn_error( writehandle, &msg );
7667 "%s: Error setting auditor information: %d %s",
7670 msg ? msg : "(No description available)"
7672 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7673 "osrfMethodException", ctx->request, "Error setting auditor info" );
7674 if( !oilsIsDBConnected( writehandle ))
7675 osrfAppSessionPanic( ctx->session );
7678 dbi_result_free( result );
7685 @brief Remove all but safe character from savepoint name
7686 @param sp User-supplied savepoint name
7687 @return sanitized savepoint name, or NULL
7689 The caller is expected to free the returned string. Note that
7690 this function exists only because we can't use PQescapeLiteral
7691 without either forking libdbi or abandoning it.
7693 static char* _sanitize_savepoint_name( const char* sp ) {
7695 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_";
7697 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7698 // and the default value of NAMEDATALEN is 64; that should be long enough
7699 // for our purposes, and it's unlikely that anyone is going to recompile
7700 // PostgreSQL to have a smaller value, so cap the identifier name
7701 // accordingly to avoid the remote chance that someone manages to pass in a
7702 // 12GB savepoint name
7703 const int MAX_LITERAL_NAMELEN = 63;
7706 if (len > MAX_LITERAL_NAMELEN) {
7707 len = MAX_LITERAL_NAMELEN;
7710 char* safeSpName = safe_malloc( len + 1 );
7714 for (j = 0; j < len; j++) {
7715 found = strchr(safe_chars, sp[j]);
7717 safeSpName[ i++ ] = found[0];
7720 safeSpName[ i ] = '\0';
7725 @brief Remove all but safe character from TZ name
7726 @param tz User-supplied TZ name
7727 @return sanitized TZ name, or NULL
7729 The caller is expected to free the returned string. Note that
7730 this function exists only because we can't use PQescapeLiteral
7731 without either forking libdbi or abandoning it.
7733 static char* _sanitize_tz_name( const char* tz ) {
7735 if (NULL == tz) return NULL;
7737 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_/-+";
7739 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7740 // and the default value of NAMEDATALEN is 64; that should be long enough
7741 // for our purposes, and it's unlikely that anyone is going to recompile
7742 // PostgreSQL to have a smaller value, so cap the identifier name
7743 // accordingly to avoid the remote chance that someone manages to pass in a
7744 // 12GB savepoint name
7745 const int MAX_LITERAL_NAMELEN = 63;
7748 if (len > MAX_LITERAL_NAMELEN) {
7749 len = MAX_LITERAL_NAMELEN;
7752 char* safeSpName = safe_malloc( len + 1 );
7756 for (j = 0; j < len; j++) {
7757 found = strchr(safe_chars, tz[j]);
7759 safeSpName[ i++ ] = found[0];
7762 safeSpName[ i ] = '\0';