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 = osrf_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 osrf_buffer_reset( query_buf );
364 osrf_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 osrf_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 = osrf_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 = osrf_buffer_release( msg );
1432 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1440 return verifyObjectPCRUD( ctx, class, param, 1 );
1446 @brief (PCRUD only) Verify that the user is properly logged in.
1447 @param ctx Pointer to the method context.
1448 @return If the user is logged in, a pointer to the user object from the authentication
1449 server; otherwise NULL.
1451 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1452 return verifyUserPCRUDfull( ctx, 0 );
1455 static const jsonObject* verifyUserPCRUDfull( osrfMethodContext* ctx, int anon_ok ) {
1457 // Get the authkey (the first method parameter)
1458 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1460 jsonObject* user = NULL;
1461 int notify_failed_verify = 1; // assume failure
1464 // auth token is null, fail by default
1465 } else if( strcmp( "ANONYMOUS", auth ) ) {
1466 // If we are /not/ in anonymous mode
1467 // See if we have the same authkey, and a user object,
1468 // locally cached from a previous call
1469 const char* cached_authkey = getAuthkey( ctx );
1470 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1471 const jsonObject* cached_user = getUserLogin( ctx );
1476 // We have no matching authentication data in the cache. Authenticate from scratch.
1477 jsonObject* auth_object = jsonNewObject( auth );
1479 // Fetch the user object from the authentication server
1480 user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve", auth_object );
1481 jsonObjectFree( auth_object );
1483 if( !user->classname || strcmp(user->classname, "au" )) {
1484 jsonObjectFree( user );
1486 } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
1487 // Failed to set audit information - But note that write_audit_info already set error information.
1488 notify_failed_verify = 0;
1489 jsonObjectFree( user );
1491 } else { // succeeded
1492 notify_failed_verify = 0;
1495 } else if ( anon_ok ) { // we /are/ (attempting to be) anonymous
1496 user = jsonNewObjectType(JSON_ARRAY);
1497 jsonObjectSetClass( user, "aou" );
1498 oilsFMSetString(user, "id", "-1");
1499 notify_failed_verify = 0;
1502 if (notify_failed_verify) {
1503 growing_buffer* msg = osrf_buffer_init( 128 );
1506 "%s: permacrud received a bad auth token: %s",
1511 char* m = osrf_buffer_release( msg );
1512 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1517 setUserLogin( ctx, user );
1518 setAuthkey( ctx, auth );
1520 // Allow ourselves up to a second before we have to reset the login timeout.
1521 // It would be nice to use some fraction of the timeout interval enforced by the
1522 // authentication server, but that value is not readily available at this point.
1523 // Instead, we use a conservative default interval.
1524 time_next_reset = time( NULL ) + 1;
1530 @brief For PCRUD: Determine whether the current user may access the current row.
1531 @param ctx Pointer to the method context.
1532 @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1533 @param obj Pointer to the row being potentially accessed.
1534 @return 1 if access is permitted, or 0 if it isn't.
1536 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1538 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1540 dbhandle = writehandle;
1542 // Figure out what class and method are involved
1543 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1544 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1547 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1548 if (rs_size_from_hash) {
1549 rs_size = *rs_size_from_hash;
1550 osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1554 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1555 // contexts we will do another lookup of the current row, even if we already have a
1556 // previously fetched row image, because the row image in hand may not include the
1557 // foreign key(s) that we need.
1559 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1560 // but they aren't implemented yet.
1563 if( *method_type == 's' || *method_type == 'i' ) {
1564 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1566 } else if( *method_type == 'u' || *method_type == 'd' ) {
1567 fetch = 1; // MUST go to the db for the object for update and delete
1570 // In retrieve or search ONLY we allow anon. Later perm checks will fail as they should,
1571 // in the face of a fake user but required permissions.
1573 if( *method_type == 'r' )
1576 // Get the appropriate permacrud entry from the IDL, depending on method type
1577 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1579 // No permacrud for this method type on this class
1581 growing_buffer* msg = osrf_buffer_init( 128 );
1584 "%s: %s on class %s has no permacrud IDL entry",
1586 osrfHashGet( method_metadata, "methodtype" ),
1587 osrfHashGet( class, "classname" )
1590 char* m = osrf_buffer_release( msg );
1591 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1592 "osrfMethodException", ctx->request, m );
1599 // Get the user id, and make sure the user is logged in
1600 const jsonObject* user = verifyUserPCRUDfull( ctx, anon_ok );
1602 return 0; // Not logged in or anon? No access.
1604 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1606 // Get a list of permissions from the permacrud entry.
1607 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1608 if( permission->size == 0 ) {
1611 "No permissions required for this action (class %s), passing through",
1612 osrfHashGet(class, "classname")
1617 // But, if there are perms and the user is anonymous ... FAIL
1621 // Build a list of org units that own the row. This is fairly convoluted because there
1622 // are several different ways that an org unit may own the row, as defined by the
1625 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1626 // identifying an owning org_unit..
1627 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1629 // Foreign context adds a layer of indirection. The row points to some other row that
1630 // an org unit may own. The "jump" attribute, if present, adds another layer of
1632 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1634 // The following string array stores the list of org units. (We don't have a thingie
1635 // for storing lists of integers, so we fake it with a list of strings.)
1636 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1638 const char* context_org = NULL;
1639 const char* pkey = NULL;
1640 jsonObject *param = NULL;
1641 const char* perm = NULL;
1645 const char* pkey_value = NULL;
1646 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1647 // If the global_required attribute is present and true, then the only owning
1648 // org unit is the root org unit, i.e. the one with no parent.
1649 osrfLogDebug( OSRF_LOG_MARK,
1650 "global-level permissions required, fetching top of the org tree" );
1652 // no need to check perms for org tree root retrieval
1653 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1654 // check for perm at top of org tree
1655 const char* org_tree_root_id = org_tree_root( ctx );
1656 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1658 if( org_tree_root_id ) {
1659 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1660 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1662 osrfStringArrayFree( context_org_array );
1667 // If the global_required attribute is absent or false, then we look for
1668 // local and/or foreign context. In order to find the relevant foreign
1669 // keys, we must either read the relevant row from the database, or look at
1670 // the image of the row that we already have in memory.
1672 // Even if we have an image of the row in memory, that image may not include the
1673 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1674 // of the row to make sure that we have what we need.
1676 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1677 "fetching context org ids" );
1679 pkey = osrfHashGet( class, "primarykey" );
1682 // There is no primary key, so we can't do a fresh lookup. Use the row
1683 // image that we already have. If it doesn't have everything we need, too bad.
1685 param = jsonObjectClone( obj );
1686 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1687 } else if( obj->classname ) {
1688 pkey_value = oilsFMGetStringConst( obj, pkey );
1690 param = jsonObjectClone( obj );
1691 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1694 pkey_value = jsonObjectGetString( obj );
1696 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1697 "of %s and retrieving from the database", pkey_value );
1701 // Fetch the row so that we can look at the foreign key(s)
1702 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1703 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1704 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1705 jsonObjectFree( _tmp_params );
1706 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1708 param = jsonObjectExtractIndex( _list, 0 );
1709 jsonObjectFree( _list );
1715 // The row doesn't exist. Complain, and deny access.
1716 osrfLogDebug( OSRF_LOG_MARK,
1717 "Object not found in the database with primary key %s of %s",
1720 growing_buffer* msg = osrf_buffer_init( 128 );
1723 "%s: no object found with primary key %s of %s",
1729 char* m = osrf_buffer_release( msg );
1730 osrfAppSessionStatus(
1732 OSRF_STATUS_INTERNALSERVERERROR,
1733 "osrfMethodException",
1742 if( local_context && local_context->size > 0 ) {
1743 // The IDL provides a list of column names for the foreign keys denoting
1744 // local context, i.e. columns identifying owing org units directly. Look up
1745 // the value of each one, and if it isn't null, add it to the list of org units.
1746 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1747 local_context->size );
1749 const char* lcontext = NULL;
1750 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1751 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1752 if( fkey_value ) { // if not null
1753 osrfStringArrayAdd( context_org_array, fkey_value );
1756 "adding class-local field %s (value: %s) to the context org list",
1758 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1764 if( foreign_context ) {
1765 unsigned long class_count = osrfHashGetCount( foreign_context );
1766 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1768 if( class_count > 0 ) {
1770 // The IDL provides a list of foreign key columns pointing to rows that
1771 // an org unit may own. Follow each link, identify the owning org unit,
1772 // and add it to the list.
1773 osrfHash* fcontext = NULL;
1774 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1775 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1776 // For each class to which a foreign key points:
1777 const char* class_name = osrfHashIteratorKey( class_itr );
1778 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1782 "%d foreign context fields(s) specified for class %s",
1783 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1787 // Get the name of the key field in the foreign table
1788 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1790 // Get the value of the foreign key pointing to the foreign table
1791 char* foreign_pkey_value =
1792 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1793 if( !foreign_pkey_value )
1794 continue; // Foreign key value is null; skip it
1796 // Look up the row to which the foreign key points
1797 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1799 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1800 jsonObject* _list = doFieldmapperSearch(
1801 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1802 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1804 jsonObject* _fparam = NULL;
1805 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1806 _fparam = jsonObjectExtractIndex( _list, 0 );
1808 jsonObjectFree( _tmp_params );
1809 jsonObjectFree( _list );
1811 // At this point _fparam either points to the row identified by the
1812 // foreign key, or it's NULL (no such row found).
1814 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1816 const char* bad_class = NULL; // For noting failed lookups
1818 bad_class = class_name; // Referenced row not found
1819 else if( jump_list ) {
1820 // Follow a chain of rows, linked by foreign keys, to find an owner
1821 const char* flink = NULL;
1823 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1824 // For each entry in the jump list. Each entry (i.e. flink) is
1825 // the name of a foreign key column in the current row.
1827 // From the IDL, get the linkage information for the next jump
1828 osrfHash* foreign_link_hash =
1829 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1831 // Get the class metadata for the class
1832 // to which the foreign key points
1833 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1834 osrfHashGet( foreign_link_hash, "class" ));
1836 // Get the name of the referenced key of that class
1837 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1839 // Get the value of the foreign key pointing to that class
1840 free( foreign_pkey_value );
1841 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1842 if( !foreign_pkey_value )
1843 break; // Foreign key is null; quit looking
1845 // Build a WHERE clause for the lookup
1846 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1849 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1850 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1851 _tmp_params, NULL, &err );
1852 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1854 // Get the resulting row
1855 jsonObjectFree( _fparam );
1856 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1857 _fparam = jsonObjectExtractIndex( _list, 0 );
1859 // Referenced row not found
1861 bad_class = osrfHashGet( foreign_link_hash, "class" );
1864 jsonObjectFree( _tmp_params );
1865 jsonObjectFree( _list );
1871 // We had a foreign key pointing to such-and-such a row, but then
1872 // we couldn't fetch that row. The data in the database are in an
1873 // inconsistent state; the database itself may even be corrupted.
1874 growing_buffer* msg = osrf_buffer_init( 128 );
1877 "%s: no object of class %s found with primary key %s of %s",
1881 foreign_pkey_value ? foreign_pkey_value : "(null)"
1884 char* m = osrf_buffer_release( msg );
1885 osrfAppSessionStatus(
1887 OSRF_STATUS_INTERNALSERVERERROR,
1888 "osrfMethodException",
1894 osrfHashIteratorFree( class_itr );
1895 free( foreign_pkey_value );
1896 jsonObjectFree( param );
1901 free( foreign_pkey_value );
1904 // Examine each context column of the foreign row,
1905 // and add its value to the list of org units.
1907 const char* foreign_field = NULL;
1908 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1909 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1910 osrfStringArrayAdd( context_org_array,
1911 oilsFMGetStringConst( _fparam, foreign_field ));
1912 osrfLogDebug( OSRF_LOG_MARK,
1913 "adding foreign class %s field %s (value: %s) "
1914 "to the context org list",
1917 osrfStringArrayGetString(
1918 context_org_array, context_org_array->size - 1 )
1922 jsonObjectFree( _fparam );
1926 osrfHashIteratorFree( class_itr );
1931 // If there is an owning_user attached to the action, we allow that user and users with
1932 // object perms on the object. CREATE can't use this. We only do this when we're not
1933 // ignoring object perms.
1934 char* owning_user_field = osrfHashGet( pcrud, "owning_user" );
1936 *method_type != 'c' &&
1937 (!str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) || // Always honor
1940 if (owning_user_field) { // see if we can short-cut by comparing the owner to the requestor
1942 if (!param) { // We didn't get it during the context lookup
1943 pkey = osrfHashGet( class, "primarykey" );
1946 // There is no primary key, so we can't do a fresh lookup. Use the row
1947 // image that we already have. If it doesn't have everything we need, too bad.
1949 param = jsonObjectClone( obj );
1950 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1951 } else if( obj->classname ) {
1952 pkey_value = oilsFMGetStringConst( obj, pkey );
1954 param = jsonObjectClone( obj );
1955 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1958 pkey_value = jsonObjectGetString( obj );
1960 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1961 "of %s and retrieving from the database", pkey_value );
1965 // Fetch the row so that we can look at the foreign key(s)
1966 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1967 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1968 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1969 jsonObjectFree( _tmp_params );
1970 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1972 param = jsonObjectExtractIndex( _list, 0 );
1973 jsonObjectFree( _list );
1978 // The row doesn't exist. Complain, and deny access.
1979 osrfLogDebug( OSRF_LOG_MARK,
1980 "Object not found in the database with primary key %s of %s",
1983 growing_buffer* msg = osrf_buffer_init( 128 );
1986 "%s: no object found with primary key %s of %s",
1992 char* m = osrf_buffer_release( msg );
1993 osrfAppSessionStatus(
1995 OSRF_STATUS_INTERNALSERVERERROR,
1996 "osrfMethodException",
2005 int ownerid = atoi( oilsFMGetStringConst( param, owning_user_field ) );
2007 // Allow the owner to do whatever
2008 if (ownerid == userid)
2015 (perm = osrfStringArrayGetString(permission, i++)) &&
2016 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms"))
2022 "Checking object permission [%s] for user %d "
2023 "on object %s (class %s)",
2027 osrfHashGet( class, "classname" )
2030 result = dbi_conn_queryf(
2032 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s') AS has_perm;",
2035 osrfHashGet( class, "classname" ),
2042 "Received a result for object permission [%s] "
2043 "for user %d on object %s (class %s)",
2047 osrfHashGet( class, "classname" )
2050 if( dbi_result_first_row( result )) {
2051 jsonObject* return_val = oilsMakeJSONFromResult( result );
2052 const char* has_perm = jsonObjectGetString(
2053 jsonObjectGetKeyConst( return_val, "has_perm" ));
2057 "Status of object permission [%s] for user %d "
2058 "on object %s (class %s) is %s",
2062 osrfHashGet(class, "classname"),
2066 if( *has_perm == 't' )
2068 jsonObjectFree( return_val );
2071 dbi_result_free( result );
2076 int errnum = dbi_conn_error( writehandle, &msg );
2077 osrfLogWarning( OSRF_LOG_MARK,
2078 "Unable to call check object permissions: %d, %s",
2079 errnum, msg ? msg : "(No description available)" );
2080 if( !oilsIsDBConnected( writehandle ))
2081 osrfAppSessionPanic( ctx->session );
2086 // For every combination of permission and context org unit: call a stored procedure
2087 // to determine if the user has this permission in the context of this org unit.
2088 // If the answer is yes at any point, then we're done, and the user has permission.
2089 // In other words permissions are additive.
2091 while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
2094 osrfStringArray* pcache = NULL;
2095 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
2096 pcache = getPermLocationCache(ctx, perm);
2099 pcache = osrfNewStringArray(0);
2101 result = dbi_conn_queryf(
2103 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
2111 "Received a result for permission [%s] for user %d",
2116 if( dbi_result_first_row( result )) {
2118 jsonObject* return_val = oilsMakeJSONFromResult( result );
2119 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
2120 jsonObjectFree( return_val );
2121 } while( dbi_result_next_row( result ));
2123 setPermLocationCache(ctx, perm, pcache);
2126 dbi_result_free( result );
2132 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
2134 if (rs_size > perm_at_threshold) {
2135 if (osrfStringArrayContains( pcache, context_org )) {
2143 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
2145 !str_is_true( osrfHashGet(pcrud, "global_required") ) ||
2146 osrfHashGet(pcrud, "owning_user")
2151 "Checking object permission [%s] for user %d "
2152 "on object %s (class %s) at org %d",
2156 osrfHashGet( class, "classname" ),
2160 result = dbi_conn_queryf(
2162 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
2165 osrfHashGet( class, "classname" ),
2173 "Received a result for object permission [%s] "
2174 "for user %d on object %s (class %s) at org %d",
2178 osrfHashGet( class, "classname" ),
2182 if( dbi_result_first_row( result )) {
2183 jsonObject* return_val = oilsMakeJSONFromResult( result );
2184 const char* has_perm = jsonObjectGetString(
2185 jsonObjectGetKeyConst( return_val, "has_perm" ));
2189 "Status of object permission [%s] for user %d "
2190 "on object %s (class %s) at org %d is %s",
2194 osrfHashGet(class, "classname"),
2199 if( *has_perm == 't' )
2201 jsonObjectFree( return_val );
2204 dbi_result_free( result );
2209 int errnum = dbi_conn_error( writehandle, &msg );
2210 osrfLogWarning( OSRF_LOG_MARK,
2211 "Unable to call check object permissions: %d, %s",
2212 errnum, msg ? msg : "(No description available)" );
2213 if( !oilsIsDBConnected( writehandle ))
2214 osrfAppSessionPanic( ctx->session );
2218 if (rs_size > perm_at_threshold) break;
2220 osrfLogDebug( OSRF_LOG_MARK,
2221 "Checking non-object permission [%s] for user %d at org %d",
2222 perm, userid, atoi(context_org) );
2223 result = dbi_conn_queryf(
2225 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
2232 osrfLogDebug( OSRF_LOG_MARK,
2233 "Received a result for permission [%s] for user %d at org %d",
2234 perm, userid, atoi( context_org ));
2235 if( dbi_result_first_row( result )) {
2236 jsonObject* return_val = oilsMakeJSONFromResult( result );
2237 const char* has_perm = jsonObjectGetString(
2238 jsonObjectGetKeyConst( return_val, "has_perm" ));
2239 osrfLogDebug( OSRF_LOG_MARK,
2240 "Status of permission [%s] for user %d at org %d is [%s]",
2241 perm, userid, atoi( context_org ), has_perm );
2242 if( *has_perm == 't' )
2244 jsonObjectFree( return_val );
2247 dbi_result_free( result );
2252 int errnum = dbi_conn_error( writehandle, &msg );
2253 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
2254 errnum, msg ? msg : "(No description available)" );
2255 if( !oilsIsDBConnected( writehandle ))
2256 osrfAppSessionPanic( ctx->session );
2265 osrfStringArrayFree( context_org_array );
2271 @brief Look up the root of the org_unit tree.
2272 @param ctx Pointer to the method context.
2273 @return The id of the root org unit, as a character string.
2275 Query actor.org_unit where parent_ou is null, and return the id as a string.
2277 This function assumes that there is only one root org unit, i.e. that we
2278 have a single tree, not a forest.
2280 The calling code is responsible for freeing the returned string.
2282 static const char* org_tree_root( osrfMethodContext* ctx ) {
2284 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
2285 static time_t last_lookup_time = 0;
2286 time_t current_time = time( NULL );
2288 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2289 // We successfully looked this up less than an hour ago.
2290 // It's not likely to have changed since then.
2291 return strdup( cached_root_id );
2293 last_lookup_time = current_time;
2296 jsonObject* where_clause = single_hash( "parent_ou", NULL );
2297 jsonObject* result = doFieldmapperSearch(
2298 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2299 jsonObjectFree( where_clause );
2301 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2304 jsonObjectFree( result );
2306 growing_buffer* msg = osrf_buffer_init( 128 );
2307 OSRF_BUFFER_ADD( msg, modulename );
2308 OSRF_BUFFER_ADD( msg,
2309 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2311 char* m = osrf_buffer_release( msg );
2312 osrfAppSessionStatus( ctx->session,
2313 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2316 cached_root_id[ 0 ] = '\0';
2320 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2321 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2323 strcpy( cached_root_id, root_org_unit_id );
2324 jsonObjectFree( result );
2325 return cached_root_id;
2329 @brief Create a JSON_HASH with a single key/value pair.
2330 @param key The key of the key/value pair.
2331 @param value the value of the key/value pair.
2332 @return Pointer to a newly created jsonObject of type JSON_HASH.
2334 The value of the key/value is either a string or (if @a value is NULL) a null.
2336 static jsonObject* single_hash( const char* key, const char* value ) {
2338 if( ! key ) key = "";
2340 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2341 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2346 int doCreate( osrfMethodContext* ctx ) {
2347 if(osrfMethodVerifyContext( ctx )) {
2348 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2353 timeout_needs_resetting = 1;
2355 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2356 jsonObject* target = NULL;
2357 jsonObject* options = NULL;
2359 if( enforce_pcrud ) {
2360 target = jsonObjectGetIndex( ctx->params, 1 );
2361 options = jsonObjectGetIndex( ctx->params, 2 );
2363 target = jsonObjectGetIndex( ctx->params, 0 );
2364 options = jsonObjectGetIndex( ctx->params, 1 );
2367 if( !verifyObjectClass( ctx, target )) {
2368 osrfAppSessionStatus(ctx->session, OSRF_STATUS_BADREQUEST,
2369 "osrfMethodException", ctx->request,
2370 "Invalid object or insufficient permissions"
2372 osrfAppRespondComplete( ctx, NULL );
2376 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2378 const char* trans_id = getXactId( ctx );
2380 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2382 osrfAppSessionStatus(
2384 OSRF_STATUS_BADREQUEST,
2385 "osrfMethodException",
2387 "No active transaction -- required for CREATE"
2389 osrfAppRespondComplete( ctx, NULL );
2393 // The following test is harmless but redundant. If a class is
2394 // readonly, we don't register a create method for it.
2395 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2396 osrfAppSessionStatus(
2398 OSRF_STATUS_BADREQUEST,
2399 "osrfMethodException",
2401 "Cannot INSERT readonly class"
2403 osrfAppRespondComplete( ctx, NULL );
2407 // Set the last_xact_id
2408 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2410 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2411 trans_id, target->classname, index);
2412 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2415 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2417 dbhandle = writehandle;
2419 osrfHash* fields = osrfHashGet( meta, "fields" );
2420 char* pkey = osrfHashGet( meta, "primarykey" );
2421 char* seq = osrfHashGet( meta, "sequence" );
2423 growing_buffer* table_buf = osrf_buffer_init( 128 );
2424 growing_buffer* col_buf = osrf_buffer_init( 128 );
2425 growing_buffer* val_buf = osrf_buffer_init( 128 );
2427 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2428 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2429 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2430 osrf_buffer_add( val_buf,"VALUES (" );
2434 osrfHash* field = NULL;
2435 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2436 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2438 const char* field_name = osrfHashIteratorKey( field_itr );
2440 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2443 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2446 if( field_object && field_object->classname ) {
2447 value = oilsFMGetString(
2449 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2451 } else if( field_object && JSON_BOOL == field_object->type ) {
2452 if( jsonBoolIsTrue( field_object ) )
2453 value = strdup( "t" );
2455 value = strdup( "f" );
2457 value = jsonObjectToSimpleString( field_object );
2463 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2464 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2467 osrf_buffer_add( col_buf, field_name );
2469 if( !field_object || field_object->type == JSON_NULL ) {
2470 osrf_buffer_add( val_buf, "DEFAULT" );
2472 } else if( !strcmp( get_primitive( field ), "number" )) {
2473 const char* numtype = get_datatype( field );
2474 if( !strcmp( numtype, "INT8" )) {
2475 osrf_buffer_fadd( val_buf, "%lld", atoll( value ));
2477 } else if( !strcmp( numtype, "INT" )) {
2478 osrf_buffer_fadd( val_buf, "%d", atoi( value ));
2480 } else if( !strcmp( numtype, "NUMERIC" )) {
2481 osrf_buffer_fadd( val_buf, "%f", atof( value ));
2484 if( dbi_conn_quote_string( writehandle, &value )) {
2485 OSRF_BUFFER_ADD( val_buf, value );
2488 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2489 osrfAppSessionStatus(
2491 OSRF_STATUS_INTERNALSERVERERROR,
2492 "osrfMethodException",
2494 "Error quoting string -- please see the error log for more details"
2497 osrf_buffer_free( table_buf );
2498 osrf_buffer_free( col_buf );
2499 osrf_buffer_free( val_buf );
2500 osrfAppRespondComplete( ctx, NULL );
2508 osrfHashIteratorFree( field_itr );
2510 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2511 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2513 char* table_str = osrf_buffer_release( table_buf );
2514 char* col_str = osrf_buffer_release( col_buf );
2515 char* val_str = osrf_buffer_release( val_buf );
2516 growing_buffer* sql = osrf_buffer_init( 128 );
2517 osrf_buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2522 char* query = osrf_buffer_release( sql );
2524 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2526 jsonObject* obj = NULL;
2529 dbi_result result = dbi_conn_query( writehandle, query );
2531 obj = jsonNewObject( NULL );
2533 int errnum = dbi_conn_error( writehandle, &msg );
2536 "%s ERROR inserting %s object using query [%s]: %d %s",
2538 osrfHashGet(meta, "fieldmapper"),
2541 msg ? msg : "(No description available)"
2543 osrfAppSessionStatus(
2545 OSRF_STATUS_INTERNALSERVERERROR,
2546 "osrfMethodException",
2548 "INSERT error -- please see the error log for more details"
2550 if( !oilsIsDBConnected( writehandle ))
2551 osrfAppSessionPanic( ctx->session );
2554 dbi_result_free( result );
2556 char* id = oilsFMGetString( target, pkey );
2558 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2559 growing_buffer* _id = osrf_buffer_init( 10 );
2560 osrf_buffer_fadd( _id, "%lld", new_id );
2561 id = osrf_buffer_release( _id );
2564 // Find quietness specification, if present
2565 const char* quiet_str = NULL;
2567 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2569 quiet_str = jsonObjectGetString( quiet_obj );
2572 if( str_is_true( quiet_str )) { // if quietness is specified
2573 obj = jsonNewObject( id );
2577 // Fetch the row that we just inserted, so that we can return it to the client
2578 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2579 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2582 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2586 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2588 jsonObjectFree( list );
2589 jsonObjectFree( where_clause );
2596 osrfAppRespondComplete( ctx, obj );
2597 jsonObjectFree( obj );
2602 @brief Implement the retrieve method.
2603 @param ctx Pointer to the method context.
2604 @param err Pointer through which to return an error code.
2605 @return If successful, a pointer to the result to be returned to the client;
2608 From the method's class, fetch a row with a specified value in the primary key. This
2609 method relies on the database design convention that a primary key consists of a single
2613 - authkey (PCRUD only)
2614 - value of the primary key for the desired row, for building the WHERE clause
2615 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2617 Return to client: One row from the query.
2619 int doRetrieve( osrfMethodContext* ctx ) {
2620 if(osrfMethodVerifyContext( ctx )) {
2621 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2626 timeout_needs_resetting = 1;
2631 if( enforce_pcrud ) {
2636 // Get the class metadata
2637 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2639 // Get the value of the primary key, from a method parameter
2640 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2644 "%s retrieving %s object with primary key value of %s",
2646 osrfHashGet( class_def, "fieldmapper" ),
2647 jsonObjectGetString( id_obj )
2650 // Build a WHERE clause based on the key value
2651 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2654 osrfHashGet( class_def, "primarykey" ), // name of key column
2655 jsonObjectClone( id_obj ) // value of key column
2658 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2662 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2664 jsonObjectFree( where_clause );
2666 osrfAppRespondComplete( ctx, NULL );
2670 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2671 jsonObjectFree( list );
2673 if( enforce_pcrud ) {
2674 // no result, skip this entirely
2675 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2676 jsonObjectFree( obj );
2678 growing_buffer* msg = osrf_buffer_init( 128 );
2679 OSRF_BUFFER_ADD( msg, modulename );
2680 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2682 char* m = osrf_buffer_release( msg );
2683 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2687 osrfAppRespondComplete( ctx, NULL );
2692 // doFieldmapperSearch() now does the responding for us
2693 //osrfAppRespondComplete( ctx, obj );
2694 osrfAppRespondComplete( ctx, NULL );
2696 jsonObjectFree( obj );
2701 @brief Translate a numeric value to a string representation for the database.
2702 @param field Pointer to the IDL field definition.
2703 @param value Pointer to a jsonObject holding the value of a field.
2704 @return Pointer to a newly allocated string.
2706 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2707 its contents are numeric. A non-numeric string is likely to result in invalid SQL.
2709 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2711 The calling code is responsible for freeing the resulting string by calling free().
2713 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2714 growing_buffer* val_buf = osrf_buffer_init( 32 );
2716 // If the value is a number and the DB field is numeric, no quotes needed
2717 if( value->type == JSON_NUMBER && !strcmp( get_primitive( field ), "number") ) {
2718 osrf_buffer_fadd( val_buf, jsonObjectGetString( value ) );
2720 // Presumably this was really intended to be a string, so quote it
2721 char* str = jsonObjectToSimpleString( value );
2722 if( dbi_conn_quote_string( dbhandle, &str )) {
2723 OSRF_BUFFER_ADD( val_buf, str );
2726 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2728 osrf_buffer_free( val_buf );
2733 return osrf_buffer_release( val_buf );
2736 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2737 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2739 char* field_transform = searchFieldTransform( class_alias, field, node );
2740 if( ! field_transform )
2744 char* in_list = searchINList(field, node, op, ctx);
2748 growing_buffer* sql_buf = osrf_buffer_init( 32 );
2749 osrf_buffer_add( sql_buf, field_transform );
2752 osrf_buffer_add( sql_buf, " IN (" );
2753 } else if( !strcasecmp( op,"not in" )) {
2754 osrf_buffer_add( sql_buf, " NOT IN (" );
2756 osrf_buffer_add( sql_buf, " IN (" );
2759 osrf_buffer_add( sql_buf, in_list);
2760 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2762 free(field_transform);
2765 return osrf_buffer_release( sql_buf );
2768 static char* searchINList( osrfHash* field,
2769 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2770 growing_buffer* sql_buf = osrf_buffer_init( 32 );
2772 const jsonObject* local_node = node;
2773 if( local_node->type == JSON_HASH ) { // may be the case that the node tranforms the field
2774 // if so, grab the "value" property
2775 local_node = jsonObjectGetKeyConst( node, "value" );
2776 if (!local_node) local_node = node;
2779 if( local_node->type == JSON_HASH ) {
2780 // subquery predicate
2782 char* subpred = buildQuery( ctx, (jsonObject*) local_node, SUBSELECT );
2784 osrf_buffer_free( sql_buf );
2788 osrf_buffer_add( sql_buf, subpred );
2791 } else if( local_node->type == JSON_ARRAY ) {
2792 // literal value list
2793 int in_item_index = 0;
2794 int in_item_first = 1;
2795 const jsonObject* in_item;
2796 while( (in_item = jsonObjectGetIndex( local_node, in_item_index++ )) ) {
2801 osrf_buffer_add( sql_buf, ", " );
2804 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2805 osrfLogError( OSRF_LOG_MARK,
2806 "%s: Expected string or number within IN list; found %s",
2807 modulename, json_type( in_item->type ) );
2808 osrf_buffer_free( sql_buf );
2812 // Append the literal value -- quoted if not a number
2813 if( JSON_NUMBER == in_item->type ) {
2814 char* val = jsonNumberToDBString( field, in_item );
2815 OSRF_BUFFER_ADD( sql_buf, val );
2818 } else if( !strcmp( get_primitive( field ), "number" )) {
2819 char* val = jsonNumberToDBString( field, in_item );
2820 OSRF_BUFFER_ADD( sql_buf, val );
2824 char* key_string = jsonObjectToSimpleString( in_item );
2825 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2826 OSRF_BUFFER_ADD( sql_buf, key_string );
2829 osrfLogError( OSRF_LOG_MARK,
2830 "%s: Error quoting key string [%s]", modulename, key_string );
2832 osrf_buffer_free( sql_buf );
2838 if( in_item_first ) {
2839 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2840 osrf_buffer_free( sql_buf );
2844 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2845 modulename, json_type( local_node->type ));
2846 osrf_buffer_free( sql_buf );
2850 return osrf_buffer_release( sql_buf );
2853 // Receive a JSON_ARRAY representing a function call. The first
2854 // entry in the array is the function name. The rest are parameters.
2855 static char* searchValueTransform( const jsonObject* array ) {
2857 if( array->size < 1 ) {
2858 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2862 // Get the function name
2863 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2864 if( func_item->type != JSON_STRING ) {
2865 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2866 modulename, json_type( func_item->type ));
2870 growing_buffer* sql_buf = osrf_buffer_init( 32 );
2872 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2873 OSRF_BUFFER_ADD( sql_buf, "( " );
2875 // Get the parameters
2876 int func_item_index = 1; // We already grabbed the zeroth entry
2877 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2879 // Add a separator comma, if we need one
2880 if( func_item_index > 2 )
2881 osrf_buffer_add( sql_buf, ", " );
2883 // Add the current parameter
2884 if( func_item->type == JSON_NULL ) {
2885 osrf_buffer_add( sql_buf, "NULL" );
2887 if( func_item->type == JSON_BOOL ) {
2888 if( jsonBoolIsTrue(func_item) ) {
2889 osrf_buffer_add( sql_buf, "TRUE" );
2891 osrf_buffer_add( sql_buf, "FALSE" );
2894 char* val = jsonObjectToSimpleString( func_item );
2895 if( dbi_conn_quote_string( dbhandle, &val )) {
2896 OSRF_BUFFER_ADD( sql_buf, val );
2899 osrfLogError( OSRF_LOG_MARK,
2900 "%s: Error quoting key string [%s]", modulename, val );
2901 osrf_buffer_free( sql_buf );
2909 osrf_buffer_add( sql_buf, " )" );
2911 return osrf_buffer_release( sql_buf );
2914 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2915 const jsonObject* node, const char* op ) {
2917 if( ! is_good_operator( op ) ) {
2918 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2922 char* val = searchValueTransform( node );
2926 const char* right_percent = "";
2927 const char* real_op = op;
2929 if( !strcasecmp( op, "startwith") ) {
2931 right_percent = "|| '%'";
2934 growing_buffer* sql_buf = osrf_buffer_init( 32 );
2937 "\"%s\".%s %s %s%s",
2939 osrfHashGet( field, "name" ),
2947 return osrf_buffer_release( sql_buf );
2950 // class_alias is a class name or other table alias
2951 // field is a field definition as stored in the IDL
2952 // node comes from the method parameter, and may represent an entry in the SELECT list
2953 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2954 const jsonObject* node ) {
2955 growing_buffer* sql_buf = osrf_buffer_init( 32 );
2957 if( node->type == JSON_HASH ) {
2958 const char* field_transform = jsonObjectGetString(
2959 jsonObjectGetKeyConst( node, "transform" ) );
2960 const char* transform_subcolumn = jsonObjectGetString(
2961 jsonObjectGetKeyConst( node, "result_field" ) );
2963 if( field_transform && transform_subcolumn ) {
2964 if( ! is_identifier( transform_subcolumn ) ) {
2965 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2966 modulename, transform_subcolumn );
2967 osrf_buffer_free( sql_buf );
2970 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2973 if( field_transform ) {
2975 if( ! is_identifier( field_transform ) ) {
2976 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2977 modulename, field_transform );
2978 osrf_buffer_free( sql_buf );
2982 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2983 osrf_buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2984 field_transform, class_alias, osrfHashGet( field, "name" ));
2986 osrf_buffer_fadd( sql_buf, "%s(\"%s\".%s",
2987 field_transform, class_alias, osrfHashGet( field, "name" ));
2990 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2993 if( array->type != JSON_ARRAY ) {
2994 osrfLogError( OSRF_LOG_MARK,
2995 "%s: Expected JSON_ARRAY for function params; found %s",
2996 modulename, json_type( array->type ) );
2997 osrf_buffer_free( sql_buf );
3000 int func_item_index = 0;
3001 jsonObject* func_item;
3002 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
3004 char* val = jsonObjectToSimpleString( func_item );
3007 osrf_buffer_add( sql_buf, ",NULL" );
3008 } else if( dbi_conn_quote_string( dbhandle, &val )) {
3009 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
3010 OSRF_BUFFER_ADD( sql_buf, val );
3012 osrfLogError( OSRF_LOG_MARK,
3013 "%s: Error quoting key string [%s]", modulename, val );
3015 osrf_buffer_free( sql_buf );
3022 osrf_buffer_add( sql_buf, ")" );
3024 if( transform_subcolumn )
3025 osrf_buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
3028 osrf_buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
3031 osrf_buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
3035 return osrf_buffer_release( sql_buf );
3038 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
3039 const jsonObject* node, const char* op ) {
3041 if( ! is_good_operator( op ) ) {
3042 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
3046 char* field_transform = searchFieldTransform( class_info->alias, field, node );
3047 if( ! field_transform )
3050 int extra_parens = 0; // boolean
3052 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
3054 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
3056 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
3058 free( field_transform );
3062 } else if( value_obj->type == JSON_ARRAY ) {
3063 value = searchValueTransform( value_obj );
3065 osrfLogError( OSRF_LOG_MARK,
3066 "%s: Error building value transform for field transform", modulename );
3067 free( field_transform );
3070 } else if( value_obj->type == JSON_HASH ) {
3071 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
3073 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
3075 free( field_transform );
3079 } else if( value_obj->type == JSON_NUMBER ) {
3080 value = jsonNumberToDBString( field, value_obj );
3081 } else if( value_obj->type == JSON_NULL ) {
3082 osrfLogError( OSRF_LOG_MARK,
3083 "%s: Error building predicate for field transform: null value", modulename );
3084 free( field_transform );
3086 } else if( value_obj->type == JSON_BOOL ) {
3087 osrfLogError( OSRF_LOG_MARK,
3088 "%s: Error building predicate for field transform: boolean value", modulename );
3089 free( field_transform );
3092 if( !strcmp( get_primitive( field ), "number") ) {
3093 value = jsonNumberToDBString( field, value_obj );
3095 value = jsonObjectToSimpleString( value_obj );
3096 if( !dbi_conn_quote_string( dbhandle, &value )) {
3097 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3098 modulename, value );
3100 free( field_transform );
3106 const char* left_parens = "";
3107 const char* right_parens = "";
3109 if( extra_parens ) {
3114 const char* right_percent = "";
3115 const char* real_op = op;
3117 if( !strcasecmp( op, "startwith") ) {
3119 right_percent = "|| '%'";
3122 growing_buffer* sql_buf = osrf_buffer_init( 32 );
3126 "%s%s %s %s %s%s %s%s",
3138 free( field_transform );
3140 return osrf_buffer_release( sql_buf );
3143 static char* searchSimplePredicate( const char* op, const char* class_alias,
3144 osrfHash* field, const jsonObject* node ) {
3146 if( ! is_good_operator( op ) ) {
3147 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
3153 // Get the value to which we are comparing the specified column
3154 if( node->type != JSON_NULL ) {
3155 if( node->type == JSON_NUMBER ) {
3156 val = jsonNumberToDBString( field, node );
3157 } else if( !strcmp( get_primitive( field ), "number" ) ) {
3158 val = jsonNumberToDBString( field, node );
3160 val = jsonObjectToSimpleString( node );
3165 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
3166 // Value is not numeric; enclose it in quotes
3167 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
3168 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3175 // Compare to a null value
3176 val = strdup( "NULL" );
3177 if( strcmp( op, "=" ))
3183 const char* right_percent = "";
3184 const char* real_op = op;
3186 if( !strcasecmp( op, "startwith") ) {
3188 right_percent = "|| '%'";
3191 growing_buffer* sql_buf = osrf_buffer_init( 32 );
3192 osrf_buffer_fadd( sql_buf, "\"%s\".%s %s %s%s", class_alias, osrfHashGet(field, "name"), real_op, val, right_percent );
3193 char* pred = osrf_buffer_release( sql_buf );
3200 static char* searchBETWEENRange( osrfHash* field, const jsonObject* node ) {
3202 const jsonObject* local_node = node;
3203 if( node->type == JSON_HASH ) { // will be the case if the node tranforms the field
3204 local_node = jsonObjectGetKeyConst( node, "value" );
3205 if (!local_node) local_node = node;
3208 const jsonObject* x_node = jsonObjectGetIndex( local_node, 0 );
3209 const jsonObject* y_node = jsonObjectGetIndex( local_node, 1 );
3211 if( NULL == y_node ) {
3212 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
3214 } else if( NULL != jsonObjectGetIndex( local_node, 2 ) ) {
3215 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
3222 if( !strcmp( get_primitive( field ), "number") ) {
3223 x_string = jsonNumberToDBString( field, x_node );
3224 y_string = jsonNumberToDBString( field, y_node );
3227 x_string = jsonObjectToSimpleString( x_node );
3228 y_string = jsonObjectToSimpleString( y_node );
3229 if( !(dbi_conn_quote_string( dbhandle, &x_string )
3230 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
3231 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
3232 modulename, x_string, y_string );
3239 growing_buffer* sql_buf = osrf_buffer_init( 32 );
3240 osrf_buffer_fadd( sql_buf, "%s AND %s", x_string, y_string );
3244 return osrf_buffer_release( sql_buf );
3247 static char* searchBETWEENPredicate( const char* class_alias,
3248 osrfHash* field, const jsonObject* node ) {
3250 char* field_transform = searchFieldTransform( class_alias, field, node );
3251 if( ! field_transform )
3254 char* between_range = searchBETWEENRange(field, node);
3256 if( NULL == between_range )
3259 growing_buffer* sql_buf = osrf_buffer_init( 32 );
3260 osrf_buffer_fadd( sql_buf, "%s BETWEEN %s", field_transform, between_range);
3262 free(field_transform);
3263 free(between_range);
3265 return osrf_buffer_release( sql_buf );
3268 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
3269 jsonObject* node, osrfMethodContext* ctx ) {
3272 if( node->type == JSON_ARRAY ) { // equality IN search
3273 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
3274 } else if( node->type == JSON_HASH ) { // other search
3275 jsonIterator* pred_itr = jsonNewIterator( node );
3276 if( !jsonIteratorHasNext( pred_itr ) ) {
3277 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
3278 modulename, osrfHashGet(field, "name" ));
3280 jsonObject* pred_node = jsonIteratorNext( pred_itr );
3282 // Verify that there are no additional predicates
3283 if( jsonIteratorHasNext( pred_itr ) ) {
3284 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
3285 modulename, osrfHashGet(field, "name" ));
3286 } else if( !(strcasecmp( pred_itr->key,"between" )) ) {
3287 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
3288 } else if( !(strcasecmp( pred_itr->key,"in" ))
3289 || !(strcasecmp( pred_itr->key,"not in" )) ) {
3290 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
3291 } else if( pred_node->type == JSON_ARRAY ) {
3292 pred = searchFunctionPredicate(
3293 class_info->alias, field, pred_node, pred_itr->key );
3294 } else if( pred_node->type == JSON_HASH ) {
3295 pred = searchFieldTransformPredicate(
3296 class_info, field, pred_node, pred_itr->key );
3298 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
3301 jsonIteratorFree( pred_itr );
3303 } else if( node->type == JSON_NULL ) { // IS NULL search
3304 growing_buffer* _p = osrf_buffer_init( 64 );
3307 "\"%s\".%s IS NULL",
3309 osrfHashGet( field, "name" )
3311 pred = osrf_buffer_release( _p );
3312 } else { // equality search
3313 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3332 field : call_number,
3346 Or, to specify join order:
3349 {mrd:{field:'record', type:'inner'}},
3350 {acn:{field:'record', type:'left'}}
3355 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3357 jsonObject* working_hash;
3358 jsonObject* freeable_hash = NULL;
3360 jsonObject* working_array;
3361 jsonObject* freeable_array = NULL;
3363 if( join_hash->type == JSON_ARRAY ) {
3364 working_array = (jsonObject*)join_hash;
3366 working_array = jsonNewObjectType( JSON_ARRAY );
3368 if( join_hash->type == JSON_HASH ) {
3369 working_hash = (jsonObject*)join_hash;
3370 } else if( join_hash->type == JSON_STRING ) {
3371 freeable_array = working_array;
3372 // turn it into a JSON_HASH by creating a wrapper
3373 // around a copy of the original
3374 const char* _tmp = jsonObjectGetString( join_hash );
3375 freeable_hash = jsonNewObjectType( JSON_HASH );
3376 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3377 working_hash = freeable_hash;
3381 "%s: JOIN failed; expected JSON object type not found",
3387 jsonObjectPush( working_array, working_hash );
3390 growing_buffer* join_buf = osrf_buffer_init( 128 );
3391 const char* leftclass = left_info->class_name;
3393 unsigned long order_idx = 0;
3394 while(( working_hash = jsonObjectGetIndex( working_array, order_idx++ ) )) {
3396 jsonObject* freeable_subhash = NULL;
3397 if( working_hash->type == JSON_STRING ) {
3398 // turn it into a JSON_HASH by creating a wrapper
3399 // around a copy of the original
3400 const char* _inner_tmp = jsonObjectGetString( working_hash );
3401 freeable_subhash = jsonNewObjectType( JSON_HASH );
3402 jsonObjectSetKey( freeable_subhash, _inner_tmp, NULL );
3403 working_hash = freeable_subhash;
3406 jsonObject* snode = NULL;
3407 jsonIterator* search_itr = jsonNewIterator( working_hash );
3409 while ( (snode = jsonIteratorNext( search_itr )) ) {
3410 const char* right_alias = search_itr->key;
3412 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3414 class = right_alias;
3416 const ClassInfo* right_info = add_joined_class( right_alias, class );
3420 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3424 jsonIteratorFree( search_itr );
3425 osrf_buffer_free( join_buf );
3426 if( freeable_subhash )
3427 jsonObjectFree( freeable_subhash );
3429 jsonObjectFree( freeable_hash );
3430 if( freeable_array )
3431 jsonObjectFree( freeable_array );
3434 osrfHash* links = right_info->links;
3435 const char* table = right_info->source_def;
3437 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3438 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3440 if( field && !fkey ) {
3441 // Look up the corresponding join column in the IDL.
3442 // The link must be defined in the child table,
3443 // and point to the right parent table.
3444 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3445 const char* reltype = NULL;
3446 const char* other_class = NULL;
3447 reltype = osrfHashGet( idl_link, "reltype" );
3448 if( reltype && strcmp( reltype, "has_many" ) )
3449 other_class = osrfHashGet( idl_link, "class" );
3450 if( other_class && !strcmp( other_class, leftclass ) )
3451 fkey = osrfHashGet( idl_link, "key" );
3455 "%s: JOIN failed. No link defined from %s.%s to %s",
3461 osrf_buffer_free( join_buf );
3462 if( freeable_subhash )
3463 jsonObjectFree( freeable_subhash );
3465 jsonObjectFree( freeable_hash );
3466 if( freeable_array )
3467 jsonObjectFree( freeable_array );
3468 jsonIteratorFree( search_itr );
3472 } else if( !field && fkey ) {
3473 // Look up the corresponding join column in the IDL.
3474 // The link must be defined in the child table,
3475 // and point to the right parent table.
3476 osrfHash* left_links = left_info->links;
3477 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3478 const char* reltype = NULL;
3479 const char* other_class = NULL;
3480 reltype = osrfHashGet( idl_link, "reltype" );
3481 if( reltype && strcmp( reltype, "has_many" ) )
3482 other_class = osrfHashGet( idl_link, "class" );
3483 if( other_class && !strcmp( other_class, class ) )
3484 field = osrfHashGet( idl_link, "key" );
3488 "%s: JOIN failed. No link defined from %s.%s to %s",
3494 osrf_buffer_free( join_buf );
3495 if( freeable_subhash )
3496 jsonObjectFree( freeable_subhash );
3498 jsonObjectFree( freeable_hash );
3499 if( freeable_array )
3500 jsonObjectFree( freeable_array );
3501 jsonIteratorFree( search_itr );
3505 } else if( !field && !fkey ) {
3506 osrfHash* left_links = left_info->links;
3508 // For each link defined for the left class:
3509 // see if the link references the joined class
3510 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3511 osrfHash* curr_link = NULL;
3512 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3513 const char* other_class = osrfHashGet( curr_link, "class" );
3514 if( other_class && !strcmp( other_class, class ) ) {
3516 // In the IDL, the parent class doesn't always know then names of the child
3517 // columns that are pointing to it, so don't use that end of the link
3518 const char* reltype = osrfHashGet( curr_link, "reltype" );
3519 if( reltype && strcmp( reltype, "has_many" ) ) {
3520 // Found a link between the classes
3521 fkey = osrfHashIteratorKey( itr );
3522 field = osrfHashGet( curr_link, "key" );
3527 osrfHashIteratorFree( itr );
3529 if( !field || !fkey ) {
3530 // Do another such search, with the classes reversed
3532 // For each link defined for the joined class:
3533 // see if the link references the left class
3534 osrfHashIterator* itr = osrfNewHashIterator( links );
3535 osrfHash* curr_link = NULL;
3536 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3537 const char* other_class = osrfHashGet( curr_link, "class" );
3538 if( other_class && !strcmp( other_class, leftclass ) ) {
3540 // In the IDL, the parent class doesn't know then names of the child
3541 // columns that are pointing to it, so don't use that end of the link
3542 const char* reltype = osrfHashGet( curr_link, "reltype" );
3543 if( reltype && strcmp( reltype, "has_many" ) ) {
3544 // Found a link between the classes
3545 field = osrfHashIteratorKey( itr );
3546 fkey = osrfHashGet( curr_link, "key" );
3551 osrfHashIteratorFree( itr );
3554 if( !field || !fkey ) {
3557 "%s: JOIN failed. No link defined between %s and %s",
3562 osrf_buffer_free( join_buf );
3563 if( freeable_subhash )
3564 jsonObjectFree( freeable_subhash );
3566 jsonObjectFree( freeable_hash );
3567 if( freeable_array )
3568 jsonObjectFree( freeable_array );
3569 jsonIteratorFree( search_itr );
3574 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3576 if( !strcasecmp( type,"left" )) {
3577 osrf_buffer_add( join_buf, " LEFT JOIN" );
3578 } else if( !strcasecmp( type,"right" )) {
3579 osrf_buffer_add( join_buf, " RIGHT JOIN" );
3580 } else if( !strcasecmp( type,"full" )) {
3581 osrf_buffer_add( join_buf, " FULL JOIN" );
3583 osrf_buffer_add( join_buf, " INNER JOIN" );
3586 osrf_buffer_add( join_buf, " INNER JOIN" );
3589 osrf_buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3590 table, right_alias, right_alias, field, left_info->alias, fkey );
3592 // Add any other join conditions as specified by "filter"
3593 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3595 const char* filter_op = jsonObjectGetString(
3596 jsonObjectGetKeyConst( snode, "filter_op" ) );
3597 if( filter_op && !strcasecmp( "or",filter_op )) {
3598 osrf_buffer_add( join_buf, " OR " );
3600 osrf_buffer_add( join_buf, " AND " );
3603 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3605 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3606 OSRF_BUFFER_ADD( join_buf, jpred );
3611 "%s: JOIN failed. Invalid conditional expression.",
3614 jsonIteratorFree( search_itr );
3615 osrf_buffer_free( join_buf );
3616 if( freeable_subhash )
3617 jsonObjectFree( freeable_subhash );
3619 jsonObjectFree( freeable_hash );
3620 if( freeable_array )
3621 jsonObjectFree( freeable_array );
3626 osrf_buffer_add( join_buf, " ) " );
3628 // Recursively add a nested join, if one is present
3629 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3631 char* jpred = searchJOIN( join_filter, right_info );
3633 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3634 OSRF_BUFFER_ADD( join_buf, jpred );
3637 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3638 jsonIteratorFree( search_itr );
3639 osrf_buffer_free( join_buf );
3640 if( freeable_subhash )
3641 jsonObjectFree( freeable_subhash );
3643 jsonObjectFree( freeable_hash );
3644 if( freeable_array )
3645 jsonObjectFree( freeable_array );
3651 if( freeable_subhash )
3652 jsonObjectFree( freeable_subhash );
3654 jsonIteratorFree( search_itr );
3658 jsonObjectFree( freeable_hash );
3660 if( freeable_array )
3661 jsonObjectFree( freeable_array );
3664 return osrf_buffer_release( join_buf );
3669 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3670 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3671 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3673 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3675 search_hash is the JSON expression of the conditions.
3676 meta is the class definition from the IDL, for the relevant table.
3677 opjoin_type indicates whether multiple conditions, if present, should be
3678 connected by AND or OR.
3679 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3680 to pass it to other functions -- and all they do with it is to use the session
3681 and request members to send error messages back to the client.
3685 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3686 int opjoin_type, osrfMethodContext* ctx ) {
3690 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3691 "opjoin_type = %d, ctx addr = %p",
3694 class_info->class_def,
3699 growing_buffer* sql_buf = osrf_buffer_init( 128 );
3701 jsonObject* node = NULL;
3704 if( search_hash->type == JSON_ARRAY ) {
3705 if( 0 == search_hash->size ) {
3708 "%s: Invalid predicate structure: empty JSON array",
3711 osrf_buffer_free( sql_buf );
3715 unsigned long i = 0;
3716 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3720 if( opjoin_type == OR_OP_JOIN )
3721 osrf_buffer_add( sql_buf, " OR " );
3723 osrf_buffer_add( sql_buf, " AND " );
3726 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3728 osrf_buffer_free( sql_buf );
3732 osrf_buffer_fadd( sql_buf, "( %s )", subpred );
3736 } else if( search_hash->type == JSON_HASH ) {
3737 osrfLogDebug( OSRF_LOG_MARK,
3738 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3739 jsonIterator* search_itr = jsonNewIterator( search_hash );
3740 if( !jsonIteratorHasNext( search_itr ) ) {
3743 "%s: Invalid predicate structure: empty JSON object",
3746 jsonIteratorFree( search_itr );
3747 osrf_buffer_free( sql_buf );
3751 while( (node = jsonIteratorNext( search_itr )) ) {
3756 if( opjoin_type == OR_OP_JOIN )
3757 osrf_buffer_add( sql_buf, " OR " );
3759 osrf_buffer_add( sql_buf, " AND " );
3762 if( '+' == search_itr->key[ 0 ] ) {
3764 // This plus sign prefixes a class name or other table alias;
3765 // make sure the table alias is in scope
3766 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3767 if( ! alias_info ) {
3770 "%s: Invalid table alias \"%s\" in WHERE clause",
3774 jsonIteratorFree( search_itr );
3775 osrf_buffer_free( sql_buf );
3779 if( node->type == JSON_STRING ) {
3780 // It's the name of a column; make sure it belongs to the class
3781 const char* fieldname = jsonObjectGetString( node );
3782 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3785 "%s: Invalid column name \"%s\" in WHERE clause "
3786 "for table alias \"%s\"",
3791 jsonIteratorFree( search_itr );
3792 osrf_buffer_free( sql_buf );
3796 osrf_buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3798 // It's something more complicated
3799 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3801 jsonIteratorFree( search_itr );
3802 osrf_buffer_free( sql_buf );
3806 osrf_buffer_fadd( sql_buf, "( %s )", subpred );
3809 } else if( '-' == search_itr->key[ 0 ] ) {
3810 if( !strcasecmp( "-or", search_itr->key )) {
3811 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3813 jsonIteratorFree( search_itr );
3814 osrf_buffer_free( sql_buf );
3818 osrf_buffer_fadd( sql_buf, "( %s )", subpred );
3820 } else if( !strcasecmp( "-and", search_itr->key )) {
3821 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3823 jsonIteratorFree( search_itr );
3824 osrf_buffer_free( sql_buf );
3828 osrf_buffer_fadd( sql_buf, "( %s )", subpred );
3830 } else if( !strcasecmp("-not",search_itr->key) ) {
3831 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3833 jsonIteratorFree( search_itr );
3834 osrf_buffer_free( sql_buf );
3838 osrf_buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3840 } else if( !strcasecmp( "-exists", search_itr->key )) {
3841 char* subpred = buildQuery( ctx, node, SUBSELECT );
3843 jsonIteratorFree( search_itr );
3844 osrf_buffer_free( sql_buf );
3848 osrf_buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3850 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3851 char* subpred = buildQuery( ctx, node, SUBSELECT );
3853 jsonIteratorFree( search_itr );
3854 osrf_buffer_free( sql_buf );
3858 osrf_buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3860 } else { // Invalid "minus" operator
3863 "%s: Invalid operator \"%s\" in WHERE clause",
3867 jsonIteratorFree( search_itr );
3868 osrf_buffer_free( sql_buf );
3874 const char* class = class_info->class_name;
3875 osrfHash* fields = class_info->fields;
3876 osrfHash* field = osrfHashGet( fields, search_itr->key );
3879 const char* table = class_info->source_def;
3882 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3885 table ? table : "?",
3888 jsonIteratorFree( search_itr );
3889 osrf_buffer_free( sql_buf );
3893 char* subpred = searchPredicate( class_info, field, node, ctx );
3895 osrf_buffer_free( sql_buf );
3896 jsonIteratorFree( search_itr );
3900 osrf_buffer_add( sql_buf, subpred );
3904 jsonIteratorFree( search_itr );
3907 // ERROR ... only hash and array allowed at this level
3908 char* predicate_string = jsonObjectToJSON( search_hash );
3911 "%s: Invalid predicate structure: %s",
3915 osrf_buffer_free( sql_buf );
3916 free( predicate_string );
3920 return osrf_buffer_release( sql_buf );
3923 /* Build a JSON_ARRAY of field names for a given table alias
3925 static jsonObject* defaultSelectList( const char* table_alias ) {
3930 ClassInfo* class_info = search_all_alias( table_alias );
3931 if( ! class_info ) {
3934 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3941 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3942 osrfHash* field_def = NULL;
3943 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3944 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3945 const char* field_name = osrfHashIteratorKey( field_itr );
3946 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3947 jsonObjectPush( array, jsonNewObject( field_name ) );
3950 osrfHashIteratorFree( field_itr );
3955 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3956 // The jsonObject must be a JSON_HASH with an single entry for "union",
3957 // "intersect", or "except". The data associated with this key must be an
3958 // array of hashes, each hash being a query.
3959 // Also allowed but currently ignored: entries for "order_by" and "alias".
3960 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3962 if( ! combo || combo->type != JSON_HASH )
3963 return NULL; // should be impossible; validated by caller
3965 const jsonObject* query_array = NULL; // array of subordinate queries
3966 const char* op = NULL; // name of operator, e.g. UNION
3967 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3968 int op_count = 0; // for detecting conflicting operators
3969 int excepting = 0; // boolean
3970 int all = 0; // boolean
3971 jsonObject* order_obj = NULL;
3973 // Identify the elements in the hash
3974 jsonIterator* query_itr = jsonNewIterator( combo );
3975 jsonObject* curr_obj = NULL;
3976 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3977 if( ! strcmp( "union", query_itr->key ) ) {
3980 query_array = curr_obj;
3981 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3984 query_array = curr_obj;
3985 } else if( ! strcmp( "except", query_itr->key ) ) {
3989 query_array = curr_obj;
3990 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3993 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3996 order_obj = curr_obj;
3997 } else if( ! strcmp( "alias", query_itr->key ) ) {
3998 if( curr_obj->type != JSON_STRING ) {
3999 jsonIteratorFree( query_itr );
4002 alias = jsonObjectGetString( curr_obj );
4003 } else if( ! strcmp( "all", query_itr->key ) ) {
4004 if( obj_is_true( curr_obj ) )
4008 osrfAppSessionStatus(
4010 OSRF_STATUS_INTERNALSERVERERROR,
4011 "osrfMethodException",
4013 "Malformed query; unexpected entry in query object"
4017 "%s: Unexpected entry for \"%s\" in%squery",
4022 jsonIteratorFree( query_itr );
4026 jsonIteratorFree( query_itr );
4028 // More sanity checks
4029 if( ! query_array ) {
4031 osrfAppSessionStatus(
4033 OSRF_STATUS_INTERNALSERVERERROR,
4034 "osrfMethodException",
4036 "Expected UNION, INTERSECT, or EXCEPT operator not found"
4040 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
4043 return NULL; // should be impossible...
4044 } else if( op_count > 1 ) {
4046 osrfAppSessionStatus(
4048 OSRF_STATUS_INTERNALSERVERERROR,
4049 "osrfMethodException",
4051 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
4055 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
4059 } if( query_array->type != JSON_ARRAY ) {
4061 osrfAppSessionStatus(
4063 OSRF_STATUS_INTERNALSERVERERROR,
4064 "osrfMethodException",
4066 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
4070 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
4073 json_type( query_array->type )
4076 } if( query_array->size < 2 ) {
4078 osrfAppSessionStatus(
4080 OSRF_STATUS_INTERNALSERVERERROR,
4081 "osrfMethodException",
4083 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
4087 "%s:%srequires multiple queries as operands",
4092 } else if( excepting && query_array->size > 2 ) {
4094 osrfAppSessionStatus(
4096 OSRF_STATUS_INTERNALSERVERERROR,
4097 "osrfMethodException",
4099 "EXCEPT operator has too many queries as operands"
4103 "%s:EXCEPT operator has too many queries as operands",
4107 } else if( order_obj && ! alias ) {
4109 osrfAppSessionStatus(
4111 OSRF_STATUS_INTERNALSERVERERROR,
4112 "osrfMethodException",
4114 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
4118 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
4124 // So far so good. Now build the SQL.
4125 growing_buffer* sql = osrf_buffer_init( 256 );
4127 // If we nested inside another UNION, INTERSECT, or EXCEPT,
4128 // Add a layer of parentheses
4129 if( flags & SUBCOMBO )
4130 OSRF_BUFFER_ADD( sql, "( " );
4132 // Traverse the query array. Each entry should be a hash.
4133 int first = 1; // boolean
4135 jsonObject* query = NULL;
4136 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
4137 if( query->type != JSON_HASH ) {
4139 osrfAppSessionStatus(
4141 OSRF_STATUS_INTERNALSERVERERROR,
4142 "osrfMethodException",
4144 "Malformed query under UNION, INTERSECT or EXCEPT"
4148 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
4151 json_type( query->type )
4153 osrf_buffer_free( sql );
4160 OSRF_BUFFER_ADD( sql, op );
4162 OSRF_BUFFER_ADD( sql, "ALL " );
4165 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
4169 "%s: Error building query under%s",
4173 osrf_buffer_free( sql );
4177 OSRF_BUFFER_ADD( sql, query_str );
4180 if( flags & SUBCOMBO )
4181 OSRF_BUFFER_ADD_CHAR( sql, ')' );
4183 if( !(flags & SUBSELECT) )
4184 OSRF_BUFFER_ADD_CHAR( sql, ';' );
4186 return osrf_buffer_release( sql );
4189 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
4190 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
4191 // or "except" to indicate the type of query.
4192 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
4196 osrfAppSessionStatus(
4198 OSRF_STATUS_INTERNALSERVERERROR,
4199 "osrfMethodException",
4201 "Malformed query; no query object"
4203 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
4205 } else if( query->type != JSON_HASH ) {
4207 osrfAppSessionStatus(
4209 OSRF_STATUS_INTERNALSERVERERROR,
4210 "osrfMethodException",
4212 "Malformed query object"
4216 "%s: Query object is %s instead of JSON_HASH",
4218 json_type( query->type )
4223 // Determine what kind of query it purports to be, and dispatch accordingly.
4224 if( jsonObjectGetKeyConst( query, "union" ) ||
4225 jsonObjectGetKeyConst( query, "intersect" ) ||
4226 jsonObjectGetKeyConst( query, "except" )) {
4227 return doCombo( ctx, query, flags );
4229 // It is presumably a SELECT query
4231 // Push a node onto the stack for the current query. Every level of
4232 // subquery gets its own QueryFrame on the Stack.
4235 // Build an SQL SELECT statement
4238 jsonObjectGetKey( query, "select" ),
4239 jsonObjectGetKeyConst( query, "from" ),
4240 jsonObjectGetKeyConst( query, "where" ),
4241 jsonObjectGetKeyConst( query, "having" ),
4242 jsonObjectGetKeyConst( query, "order_by" ),
4243 jsonObjectGetKeyConst( query, "limit" ),
4244 jsonObjectGetKeyConst( query, "offset" ),
4253 /* method context */ osrfMethodContext* ctx,
4255 /* SELECT */ jsonObject* selhash,
4256 /* FROM */ const jsonObject* join_hash,
4257 /* WHERE */ const jsonObject* search_hash,
4258 /* HAVING */ const jsonObject* having_hash,
4259 /* ORDER BY */ const jsonObject* order_hash,
4260 /* LIMIT */ const jsonObject* limit,
4261 /* OFFSET */ const jsonObject* offset,
4262 /* flags */ int flags
4264 const char* locale = osrf_message_get_last_locale();
4266 // general tmp objects
4267 const jsonObject* tmp_const;
4268 jsonObject* selclass = NULL;
4269 jsonObject* snode = NULL;
4270 jsonObject* onode = NULL;
4272 char* string = NULL;
4273 int from_function = 0;
4278 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
4280 // punt if there's no FROM clause
4281 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
4284 "%s: FROM clause is missing or empty",
4288 osrfAppSessionStatus(
4290 OSRF_STATUS_INTERNALSERVERERROR,
4291 "osrfMethodException",
4293 "FROM clause is missing or empty in JSON query"
4298 // the core search class
4299 const char* core_class = NULL;
4301 // get the core class -- the only key of the top level FROM clause, or a string
4302 if( join_hash->type == JSON_HASH ) {
4303 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
4304 snode = jsonIteratorNext( tmp_itr );
4306 // Populate the current QueryFrame with information
4307 // about the core class
4308 if( add_query_core( NULL, tmp_itr->key ) ) {
4310 osrfAppSessionStatus(
4312 OSRF_STATUS_INTERNALSERVERERROR,
4313 "osrfMethodException",
4315 "Unable to look up core class"
4319 core_class = curr_query->core.class_name;
4322 jsonObject* extra = jsonIteratorNext( tmp_itr );
4324 jsonIteratorFree( tmp_itr );
4327 // There shouldn't be more than one entry in join_hash
4331 "%s: Malformed FROM clause: extra entry in JSON_HASH",
4335 osrfAppSessionStatus(
4337 OSRF_STATUS_INTERNALSERVERERROR,
4338 "osrfMethodException",
4340 "Malformed FROM clause in JSON query"
4342 return NULL; // Malformed join_hash; extra entry
4344 } else if( join_hash->type == JSON_ARRAY ) {
4345 // We're selecting from a function, not from a table
4347 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
4350 } else if( join_hash->type == JSON_STRING ) {
4351 // Populate the current QueryFrame with information
4352 // about the core class
4353 core_class = jsonObjectGetString( join_hash );
4355 if( add_query_core( NULL, core_class ) ) {
4357 osrfAppSessionStatus(
4359 OSRF_STATUS_INTERNALSERVERERROR,
4360 "osrfMethodException",
4362 "Unable to look up core class"
4370 "%s: FROM clause is unexpected JSON type: %s",
4372 json_type( join_hash->type )
4375 osrfAppSessionStatus(
4377 OSRF_STATUS_INTERNALSERVERERROR,
4378 "osrfMethodException",
4380 "Ill-formed FROM clause in JSON query"
4385 // Build the join clause, if any, while filling out the list
4386 // of joined classes in the current QueryFrame.
4387 char* join_clause = NULL;
4388 if( join_hash && ! from_function ) {
4390 join_clause = searchJOIN( join_hash, &curr_query->core );
4391 if( ! join_clause ) {
4393 osrfAppSessionStatus(
4395 OSRF_STATUS_INTERNALSERVERERROR,
4396 "osrfMethodException",
4398 "Unable to construct JOIN clause(s)"
4404 // For in case we don't get a select list
4405 jsonObject* defaultselhash = NULL;
4407 // if there is no select list, build a default select list ...
4408 if( !selhash && !from_function ) {
4409 jsonObject* default_list = defaultSelectList( core_class );
4410 if( ! default_list ) {
4412 osrfAppSessionStatus(
4414 OSRF_STATUS_INTERNALSERVERERROR,
4415 "osrfMethodException",
4417 "Unable to build default SELECT clause in JSON query"
4419 free( join_clause );
4424 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4425 jsonObjectSetKey( selhash, core_class, default_list );
4428 // The SELECT clause can be encoded only by a hash
4429 if( !from_function && selhash->type != JSON_HASH ) {
4432 "%s: Expected JSON_HASH for SELECT clause; found %s",
4434 json_type( selhash->type )
4438 osrfAppSessionStatus(
4440 OSRF_STATUS_INTERNALSERVERERROR,
4441 "osrfMethodException",
4443 "Malformed SELECT clause in JSON query"
4445 free( join_clause );
4449 // If you see a null or wild card specifier for the core class, or an
4450 // empty array, replace it with a default SELECT list
4451 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4453 int default_needed = 0; // boolean
4454 if( JSON_STRING == tmp_const->type
4455 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4457 else if( JSON_NULL == tmp_const->type )
4460 if( default_needed ) {
4461 // Build a default SELECT list
4462 jsonObject* default_list = defaultSelectList( core_class );
4463 if( ! default_list ) {
4465 osrfAppSessionStatus(
4467 OSRF_STATUS_INTERNALSERVERERROR,
4468 "osrfMethodException",
4470 "Can't build default SELECT clause in JSON query"
4472 free( join_clause );
4477 jsonObjectSetKey( selhash, core_class, default_list );
4481 // temp buffers for the SELECT list and GROUP BY clause
4482 growing_buffer* select_buf = osrf_buffer_init( 128 );
4483 growing_buffer* group_buf = osrf_buffer_init( 128 );
4485 int aggregate_found = 0; // boolean
4487 // Build a select list
4488 if( from_function ) // From a function we select everything
4489 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4492 // Build the SELECT list as SQL
4496 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4497 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4499 const char* cname = selclass_itr->key;
4501 // Make sure the target relation is in the FROM clause.
4503 // At this point join_hash is a step down from the join_hash we
4504 // received as a parameter. If the original was a JSON_STRING,
4505 // then json_hash is now NULL. If the original was a JSON_HASH,
4506 // then json_hash is now the first (and only) entry in it,
4507 // denoting the core class. We've already excluded the
4508 // possibility that the original was a JSON_ARRAY, because in
4509 // that case from_function would be non-NULL, and we wouldn't
4512 // If the current table alias isn't in scope, bail out
4513 ClassInfo* class_info = search_alias( cname );
4514 if( ! class_info ) {
4517 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4522 osrfAppSessionStatus(
4524 OSRF_STATUS_INTERNALSERVERERROR,
4525 "osrfMethodException",
4527 "Selected class not in FROM clause in JSON query"
4529 jsonIteratorFree( selclass_itr );
4530 osrf_buffer_free( select_buf );
4531 osrf_buffer_free( group_buf );
4532 if( defaultselhash )
4533 jsonObjectFree( defaultselhash );
4534 free( join_clause );
4538 if( selclass->type != JSON_ARRAY ) {
4541 "%s: Malformed SELECT list for class \"%s\"; not an array",
4546 osrfAppSessionStatus(
4548 OSRF_STATUS_INTERNALSERVERERROR,
4549 "osrfMethodException",
4551 "Selected class not in FROM clause in JSON query"
4554 jsonIteratorFree( selclass_itr );
4555 osrf_buffer_free( select_buf );
4556 osrf_buffer_free( group_buf );
4557 if( defaultselhash )
4558 jsonObjectFree( defaultselhash );
4559 free( join_clause );
4563 // Look up some attributes of the current class
4564 osrfHash* idlClass = class_info->class_def;
4565 osrfHash* class_field_set = class_info->fields;
4566 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4567 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4569 if( 0 == selclass->size ) {
4572 "%s: No columns selected from \"%s\"",
4578 // stitch together the column list for the current table alias...
4579 unsigned long field_idx = 0;
4580 jsonObject* selfield = NULL;
4581 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4583 // If we need a separator comma, add one
4587 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4590 // if the field specification is a string, add it to the list
4591 if( selfield->type == JSON_STRING ) {
4593 // Look up the field in the IDL
4594 const char* col_name = jsonObjectGetString( selfield );
4595 osrfHash* field_def = NULL;
4597 if (!osrfStringArrayContains(
4599 osrfHashGet( class_field_set, col_name ),
4600 "suppress_controller"),
4603 field_def = osrfHashGet( class_field_set, col_name );
4606 // No such field in current class
4609 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4615 osrfAppSessionStatus(
4617 OSRF_STATUS_INTERNALSERVERERROR,
4618 "osrfMethodException",
4620 "Selected column not defined in JSON query"
4622 jsonIteratorFree( selclass_itr );
4623 osrf_buffer_free( select_buf );
4624 osrf_buffer_free( group_buf );
4625 if( defaultselhash )
4626 jsonObjectFree( defaultselhash );
4627 free( join_clause );
4629 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4630 // Virtual field not allowed
4633 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4639 osrfAppSessionStatus(
4641 OSRF_STATUS_INTERNALSERVERERROR,
4642 "osrfMethodException",
4644 "Selected column may not be virtual in JSON query"
4646 jsonIteratorFree( selclass_itr );
4647 osrf_buffer_free( select_buf );
4648 osrf_buffer_free( group_buf );
4649 if( defaultselhash )
4650 jsonObjectFree( defaultselhash );
4651 free( join_clause );
4657 if( flags & DISABLE_I18N )
4660 i18n = osrfHashGet( field_def, "i18n" );
4662 if( str_is_true( i18n ) ) {
4663 osrf_buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4664 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4665 class_tname, cname, col_name, class_pkey,
4666 cname, class_pkey, locale, col_name );
4668 osrf_buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4669 cname, col_name, col_name );
4672 osrf_buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4673 cname, col_name, col_name );
4676 // ... but it could be an object, in which case we check for a Field Transform
4677 } else if( selfield->type == JSON_HASH ) {
4679 const char* col_name = jsonObjectGetString(
4680 jsonObjectGetKeyConst( selfield, "column" ) );
4682 // Get the field definition from the IDL
4683 osrfHash* field_def = NULL;
4684 if (!osrfStringArrayContains(
4686 osrfHashGet( class_field_set, col_name ),
4687 "suppress_controller"),
4690 field_def = osrfHashGet( class_field_set, col_name );
4694 // No such field in current class
4697 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4703 osrfAppSessionStatus(
4705 OSRF_STATUS_INTERNALSERVERERROR,
4706 "osrfMethodException",
4708 "Selected column is not defined in JSON query"
4710 jsonIteratorFree( selclass_itr );
4711 osrf_buffer_free( select_buf );
4712 osrf_buffer_free( group_buf );
4713 if( defaultselhash )
4714 jsonObjectFree( defaultselhash );
4715 free( join_clause );
4717 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4718 // No such field in current class
4721 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4727 osrfAppSessionStatus(
4729 OSRF_STATUS_INTERNALSERVERERROR,
4730 "osrfMethodException",
4732 "Selected column is virtual in JSON query"
4734 jsonIteratorFree( selclass_itr );
4735 osrf_buffer_free( select_buf );
4736 osrf_buffer_free( group_buf );
4737 if( defaultselhash )
4738 jsonObjectFree( defaultselhash );
4739 free( join_clause );
4743 // Decide what to use as a column alias
4745 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4746 _alias = jsonObjectGetString( tmp_const );
4747 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4748 _alias = jsonObjectGetString( tmp_const );
4749 } else { // Use field name as the alias
4753 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4754 char* transform_str = searchFieldTransform(
4755 class_info->alias, field_def, selfield );
4756 if( transform_str ) {
4757 osrf_buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4758 free( transform_str );
4761 osrfAppSessionStatus(
4763 OSRF_STATUS_INTERNALSERVERERROR,
4764 "osrfMethodException",
4766 "Unable to generate transform function in JSON query"
4768 jsonIteratorFree( selclass_itr );
4769 osrf_buffer_free( select_buf );
4770 osrf_buffer_free( group_buf );
4771 if( defaultselhash )
4772 jsonObjectFree( defaultselhash );
4773 free( join_clause );
4780 if( flags & DISABLE_I18N )
4783 i18n = osrfHashGet( field_def, "i18n" );
4785 if( str_is_true( i18n ) ) {
4786 osrf_buffer_fadd( select_buf,
4787 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4788 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4789 class_tname, cname, col_name, class_pkey, cname,
4790 class_pkey, locale, _alias );
4792 osrf_buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4793 cname, col_name, _alias );
4796 osrf_buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4797 cname, col_name, _alias );
4804 "%s: Selected item is unexpected JSON type: %s",
4806 json_type( selfield->type )
4809 osrfAppSessionStatus(
4811 OSRF_STATUS_INTERNALSERVERERROR,
4812 "osrfMethodException",
4814 "Ill-formed SELECT item in JSON query"
4816 jsonIteratorFree( selclass_itr );
4817 osrf_buffer_free( select_buf );
4818 osrf_buffer_free( group_buf );
4819 if( defaultselhash )
4820 jsonObjectFree( defaultselhash );
4821 free( join_clause );
4825 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4826 if( obj_is_true( agg_obj ) )
4827 aggregate_found = 1;
4829 // Append a comma (except for the first one)
4830 // and add the column to a GROUP BY clause
4834 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4836 osrf_buffer_fadd( group_buf, " %d", sel_pos );
4840 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4842 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( selfield, "aggregate");
4843 if ( ! obj_is_true( aggregate_obj ) ) {
4847 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4850 osrf_buffer_fadd(group_buf, " %d", sel_pos);
4853 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4857 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4860 _column = searchFieldTransform(class_info->alias, field, selfield);
4861 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4862 OSRF_BUFFER_ADD(group_buf, _column);
4863 _column = searchFieldTransform(class_info->alias, field, selfield);
4870 } // end while -- iterating across SELECT columns
4872 } // end while -- iterating across classes
4874 jsonIteratorFree( selclass_itr );
4877 char* col_list = osrf_buffer_release( select_buf );
4879 // Make sure the SELECT list isn't empty. This can happen, for example,
4880 // if we try to build a default SELECT clause from a non-core table.
4883 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4885 osrfAppSessionStatus(
4887 OSRF_STATUS_INTERNALSERVERERROR,
4888 "osrfMethodException",
4890 "SELECT list is empty"
4893 osrf_buffer_free( group_buf );
4894 if( defaultselhash )
4895 jsonObjectFree( defaultselhash );
4896 free( join_clause );
4902 table = searchValueTransform( join_hash );
4904 table = strdup( curr_query->core.source_def );
4908 osrfAppSessionStatus(
4910 OSRF_STATUS_INTERNALSERVERERROR,
4911 "osrfMethodException",
4913 "Unable to identify table for core class"
4916 osrf_buffer_free( group_buf );
4917 if( defaultselhash )
4918 jsonObjectFree( defaultselhash );
4919 free( join_clause );
4923 // Put it all together
4924 growing_buffer* sql_buf = osrf_buffer_init( 128 );
4925 osrf_buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4929 // Append the join clause, if any
4931 osrf_buffer_add(sql_buf, join_clause );
4932 free( join_clause );
4935 char* order_by_list = NULL;
4936 char* having_buf = NULL;
4938 if( !from_function ) {
4940 // Build a WHERE clause, if there is one
4942 osrf_buffer_add( sql_buf, " WHERE " );
4944 // and it's on the WHERE clause
4945 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4948 osrfAppSessionStatus(
4950 OSRF_STATUS_INTERNALSERVERERROR,
4951 "osrfMethodException",
4953 "Severe query error in WHERE predicate -- see error log for more details"
4956 osrf_buffer_free( group_buf );
4957 osrf_buffer_free( sql_buf );
4958 if( defaultselhash )
4959 jsonObjectFree( defaultselhash );
4963 osrf_buffer_add( sql_buf, pred );
4967 // Build a HAVING clause, if there is one
4970 // and it's on the the WHERE clause
4971 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4973 if( ! having_buf ) {
4975 osrfAppSessionStatus(
4977 OSRF_STATUS_INTERNALSERVERERROR,
4978 "osrfMethodException",
4980 "Severe query error in HAVING predicate -- see error log for more details"
4983 osrf_buffer_free( group_buf );
4984 osrf_buffer_free( sql_buf );
4985 if( defaultselhash )
4986 jsonObjectFree( defaultselhash );
4991 // Build an ORDER BY clause, if there is one
4992 if( NULL == order_hash )
4993 ; // No ORDER BY? do nothing
4994 else if( JSON_ARRAY == order_hash->type ) {
4995 order_by_list = buildOrderByFromArray( ctx, order_hash );
4996 if( !order_by_list ) {
4998 osrf_buffer_free( group_buf );
4999 osrf_buffer_free( sql_buf );
5000 if( defaultselhash )
5001 jsonObjectFree( defaultselhash );
5004 } else if( JSON_HASH == order_hash->type ) {
5005 // This hash is keyed on class alias. Each class has either
5006 // an array of field names or a hash keyed on field name.
5007 growing_buffer* order_buf = NULL; // to collect ORDER BY list
5008 jsonIterator* class_itr = jsonNewIterator( order_hash );
5009 while( (snode = jsonIteratorNext( class_itr )) ) {
5011 ClassInfo* order_class_info = search_alias( class_itr->key );
5012 if( ! order_class_info ) {
5013 osrfLogError( OSRF_LOG_MARK,
5014 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
5015 modulename, class_itr->key );
5017 osrfAppSessionStatus(
5019 OSRF_STATUS_INTERNALSERVERERROR,
5020 "osrfMethodException",
5022 "Invalid class referenced in ORDER BY clause -- "
5023 "see error log for more details"
5025 jsonIteratorFree( class_itr );
5026 osrf_buffer_free( order_buf );
5028 osrf_buffer_free( group_buf );
5029 osrf_buffer_free( sql_buf );
5030 if( defaultselhash )
5031 jsonObjectFree( defaultselhash );
5035 osrfHash* field_list_def = order_class_info->fields;
5037 if( snode->type == JSON_HASH ) {
5039 // Hash is keyed on field names from the current class. For each field
5040 // there is another layer of hash to define the sorting details, if any,
5041 // or a string to indicate direction of sorting.
5042 jsonIterator* order_itr = jsonNewIterator( snode );
5043 while( (onode = jsonIteratorNext( order_itr )) ) {
5045 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
5047 osrfLogError( OSRF_LOG_MARK,
5048 "%s: Invalid field \"%s\" in ORDER BY clause",
5049 modulename, order_itr->key );
5051 osrfAppSessionStatus(
5053 OSRF_STATUS_INTERNALSERVERERROR,
5054 "osrfMethodException",
5056 "Invalid field in ORDER BY clause -- "
5057 "see error log for more details"
5059 jsonIteratorFree( order_itr );
5060 jsonIteratorFree( class_itr );
5061 osrf_buffer_free( order_buf );
5063 osrf_buffer_free( group_buf );
5064 osrf_buffer_free( sql_buf );
5065 if( defaultselhash )
5066 jsonObjectFree( defaultselhash );
5068 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5069 osrfLogError( OSRF_LOG_MARK,
5070 "%s: Virtual field \"%s\" in ORDER BY clause",
5071 modulename, order_itr->key );
5073 osrfAppSessionStatus(
5075 OSRF_STATUS_INTERNALSERVERERROR,
5076 "osrfMethodException",
5078 "Virtual field in ORDER BY clause -- "
5079 "see error log for more details"
5081 jsonIteratorFree( order_itr );
5082 jsonIteratorFree( class_itr );
5083 osrf_buffer_free( order_buf );
5085 osrf_buffer_free( group_buf );
5086 osrf_buffer_free( sql_buf );
5087 if( defaultselhash )
5088 jsonObjectFree( defaultselhash );
5092 const char* direction = NULL;
5093 if( onode->type == JSON_HASH ) {
5094 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5095 string = searchFieldTransform(
5097 osrfHashGet( field_list_def, order_itr->key ),
5101 if( ctx ) osrfAppSessionStatus(
5103 OSRF_STATUS_INTERNALSERVERERROR,
5104 "osrfMethodException",
5106 "Severe query error in ORDER BY clause -- "
5107 "see error log for more details"
5109 jsonIteratorFree( order_itr );
5110 jsonIteratorFree( class_itr );
5112 osrf_buffer_free( group_buf );
5113 osrf_buffer_free( order_buf);
5114 osrf_buffer_free( sql_buf );
5115 if( defaultselhash )
5116 jsonObjectFree( defaultselhash );
5120 growing_buffer* field_buf = osrf_buffer_init( 16 );
5121 osrf_buffer_fadd( field_buf, "\"%s\".%s",
5122 class_itr->key, order_itr->key );
5123 string = osrf_buffer_release( field_buf );
5126 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
5127 const char* dir = jsonObjectGetString( tmp_const );
5128 if(!strncasecmp( dir, "d", 1 )) {
5129 direction = " DESC";
5135 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
5136 osrfLogError( OSRF_LOG_MARK,
5137 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
5138 modulename, json_type( onode->type ) );
5140 osrfAppSessionStatus(
5142 OSRF_STATUS_INTERNALSERVERERROR,
5143 "osrfMethodException",
5145 "Malformed ORDER BY clause -- see error log for more details"
5147 jsonIteratorFree( order_itr );
5148 jsonIteratorFree( class_itr );
5150 osrf_buffer_free( group_buf );
5151 osrf_buffer_free( order_buf );
5152 osrf_buffer_free( sql_buf );
5153 if( defaultselhash )
5154 jsonObjectFree( defaultselhash );
5158 string = strdup( order_itr->key );
5159 const char* dir = jsonObjectGetString( onode );
5160 if( !strncasecmp( dir, "d", 1 )) {
5161 direction = " DESC";
5168 OSRF_BUFFER_ADD( order_buf, ", " );
5170 order_buf = osrf_buffer_init( 128 );
5172 OSRF_BUFFER_ADD( order_buf, string );
5176 OSRF_BUFFER_ADD( order_buf, direction );
5180 jsonIteratorFree( order_itr );
5182 } else if( snode->type == JSON_ARRAY ) {
5184 // Array is a list of fields from the current class
5185 unsigned long order_idx = 0;
5186 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
5188 const char* _f = jsonObjectGetString( onode );
5190 osrfHash* field_def = osrfHashGet( field_list_def, _f );
5192 osrfLogError( OSRF_LOG_MARK,
5193 "%s: Invalid field \"%s\" in ORDER BY clause",
5196 osrfAppSessionStatus(
5198 OSRF_STATUS_INTERNALSERVERERROR,
5199 "osrfMethodException",
5201 "Invalid field in ORDER BY clause -- "
5202 "see error log for more details"
5204 jsonIteratorFree( class_itr );
5205 osrf_buffer_free( order_buf );
5207 osrf_buffer_free( group_buf );
5208 osrf_buffer_free( sql_buf );
5209 if( defaultselhash )
5210 jsonObjectFree( defaultselhash );
5212 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5213 osrfLogError( OSRF_LOG_MARK,
5214 "%s: Virtual field \"%s\" in ORDER BY clause",
5217 osrfAppSessionStatus(
5219 OSRF_STATUS_INTERNALSERVERERROR,
5220 "osrfMethodException",
5222 "Virtual field in ORDER BY clause -- "
5223 "see error log for more details"
5225 jsonIteratorFree( class_itr );
5226 osrf_buffer_free( order_buf );
5228 osrf_buffer_free( group_buf );
5229 osrf_buffer_free( sql_buf );
5230 if( defaultselhash )
5231 jsonObjectFree( defaultselhash );
5236 OSRF_BUFFER_ADD( order_buf, ", " );
5238 order_buf = osrf_buffer_init( 128 );
5240 osrf_buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
5244 // IT'S THE OOOOOOOOOOOLD STYLE!
5246 osrfLogError( OSRF_LOG_MARK,
5247 "%s: Possible SQL injection attempt; direct order by is not allowed",
5250 osrfAppSessionStatus(
5252 OSRF_STATUS_INTERNALSERVERERROR,
5253 "osrfMethodException",
5255 "Severe query error -- see error log for more details"
5260 osrf_buffer_free( group_buf );
5261 osrf_buffer_free( order_buf );
5262 osrf_buffer_free( sql_buf );
5263 if( defaultselhash )
5264 jsonObjectFree( defaultselhash );
5265 jsonIteratorFree( class_itr );
5269 jsonIteratorFree( class_itr );
5271 order_by_list = osrf_buffer_release( order_buf );
5273 osrfLogError( OSRF_LOG_MARK,
5274 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
5275 modulename, json_type( order_hash->type ) );
5277 osrfAppSessionStatus(
5279 OSRF_STATUS_INTERNALSERVERERROR,
5280 "osrfMethodException",
5282 "Malformed ORDER BY clause -- see error log for more details"
5285 osrf_buffer_free( group_buf );
5286 osrf_buffer_free( sql_buf );
5287 if( defaultselhash )
5288 jsonObjectFree( defaultselhash );
5293 string = osrf_buffer_release( group_buf );
5295 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
5296 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
5297 OSRF_BUFFER_ADD( sql_buf, string );
5302 if( having_buf && *having_buf ) {
5303 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
5304 OSRF_BUFFER_ADD( sql_buf, having_buf );
5308 if( order_by_list ) {
5310 if( *order_by_list ) {
5311 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5312 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5315 free( order_by_list );
5319 const char* str = jsonObjectGetString( limit );
5320 if (str) { // limit could be JSON_NULL, etc.
5321 osrf_buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
5326 const char* str = jsonObjectGetString( offset );
5328 osrf_buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
5332 if( !(flags & SUBSELECT) )
5333 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5335 if( defaultselhash )
5336 jsonObjectFree( defaultselhash );
5338 return osrf_buffer_release( sql_buf );
5340 } // end of SELECT()
5343 @brief Build a list of ORDER BY expressions.
5344 @param ctx Pointer to the method context.
5345 @param order_array Pointer to a JSON_ARRAY of field specifications.
5346 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
5347 Each expression may be either a column reference or a function call whose first parameter
5348 is a column reference.
5350 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
5351 It may optionally include entries for "direction" and/or "transform".
5353 The calling code is responsible for freeing the returned string.
5355 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
5356 if( ! order_array ) {
5357 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
5360 osrfAppSessionStatus(
5362 OSRF_STATUS_INTERNALSERVERERROR,
5363 "osrfMethodException",
5365 "Logic error: ORDER BY clause expected, not found; "
5366 "see error log for more details"
5369 } else if( order_array->type != JSON_ARRAY ) {
5370 osrfLogError( OSRF_LOG_MARK,
5371 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
5373 osrfAppSessionStatus(
5375 OSRF_STATUS_INTERNALSERVERERROR,
5376 "osrfMethodException",
5378 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
5382 growing_buffer* order_buf = osrf_buffer_init( 128 );
5383 int first = 1; // boolean
5385 jsonObject* order_spec;
5386 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
5388 if( JSON_HASH != order_spec->type ) {
5389 osrfLogError( OSRF_LOG_MARK,
5390 "%s: Malformed field specification in ORDER BY clause; "
5391 "expected JSON_HASH, found %s",
5392 modulename, json_type( order_spec->type ) );
5394 osrfAppSessionStatus(
5396 OSRF_STATUS_INTERNALSERVERERROR,
5397 "osrfMethodException",
5399 "Malformed ORDER BY clause -- see error log for more details"
5401 osrf_buffer_free( order_buf );
5405 const char* class_alias =
5406 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5408 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5410 jsonObject* compare_to = jsonObjectGetKey( order_spec, "compare" );
5412 if( !field || !class_alias ) {
5413 osrfLogError( OSRF_LOG_MARK,
5414 "%s: Missing class or field name in field specification of ORDER BY clause",
5417 osrfAppSessionStatus(
5419 OSRF_STATUS_INTERNALSERVERERROR,
5420 "osrfMethodException",
5422 "Malformed ORDER BY clause -- see error log for more details"
5424 osrf_buffer_free( order_buf );
5428 const ClassInfo* order_class_info = search_alias( class_alias );
5429 if( ! order_class_info ) {
5430 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5431 "not in FROM clause, skipping it", modulename, class_alias );
5435 // Add a separating comma, except at the beginning
5439 OSRF_BUFFER_ADD( order_buf, ", " );
5441 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5443 osrfLogError( OSRF_LOG_MARK,
5444 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5445 modulename, class_alias, field );
5447 osrfAppSessionStatus(
5449 OSRF_STATUS_INTERNALSERVERERROR,
5450 "osrfMethodException",
5452 "Invalid field referenced in ORDER BY clause -- "
5453 "see error log for more details"
5457 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5458 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5459 modulename, field );
5461 osrfAppSessionStatus(
5463 OSRF_STATUS_INTERNALSERVERERROR,
5464 "osrfMethodException",
5466 "Virtual field in ORDER BY clause -- see error log for more details"
5468 osrf_buffer_free( order_buf );
5472 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5473 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5474 if( ! transform_str ) {
5476 osrfAppSessionStatus(
5478 OSRF_STATUS_INTERNALSERVERERROR,
5479 "osrfMethodException",
5481 "Severe query error in ORDER BY clause -- "
5482 "see error log for more details"
5484 osrf_buffer_free( order_buf );
5488 OSRF_BUFFER_ADD( order_buf, transform_str );
5489 free( transform_str );
5490 } else if( compare_to ) {
5491 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5492 if( ! compare_str ) {
5494 osrfAppSessionStatus(
5496 OSRF_STATUS_INTERNALSERVERERROR,
5497 "osrfMethodException",
5499 "Severe query error in ORDER BY clause -- "
5500 "see error log for more details"
5502 osrf_buffer_free( order_buf );
5506 osrf_buffer_fadd( order_buf, "(%s)", compare_str );
5507 free( compare_str );
5510 osrf_buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5512 const char* direction =
5513 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5515 if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5516 OSRF_BUFFER_ADD( order_buf, " DESC" );
5518 OSRF_BUFFER_ADD( order_buf, " ASC" );
5522 return osrf_buffer_release( order_buf );
5526 @brief Build a SELECT statement.
5527 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5528 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5529 @param meta Pointer to the class metadata for the core class.
5530 @param ctx Pointer to the method context.
5531 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5533 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5534 "order_by", "limit", and "offset".
5536 The SELECT statements built here are distinct from those built for the json_query method.
5538 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5539 osrfHash* meta, osrfMethodContext* ctx ) {
5541 const char* locale = osrf_message_get_last_locale();
5543 osrfHash* fields = osrfHashGet( meta, "fields" );
5544 const char* core_class = osrfHashGet( meta, "classname" );
5546 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5548 jsonObject* selhash = NULL;
5549 jsonObject* defaultselhash = NULL;
5551 growing_buffer* sql_buf = osrf_buffer_init( 128 );
5552 growing_buffer* select_buf = osrf_buffer_init( 128 );
5554 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5555 defaultselhash = jsonNewObjectType( JSON_HASH );
5556 selhash = defaultselhash;
5559 // If there's no SELECT list for the core class, build one
5560 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5561 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5563 // Add every non-virtual field to the field list
5564 osrfHash* field_def = NULL;
5565 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5566 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5567 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5568 const char* field = osrfHashIteratorKey( field_itr );
5569 jsonObjectPush( field_list, jsonNewObject( field ) );
5572 osrfHashIteratorFree( field_itr );
5573 jsonObjectSetKey( selhash, core_class, field_list );
5576 // Build a list of columns for the SELECT clause
5578 const jsonObject* snode = NULL;
5579 jsonIterator* class_itr = jsonNewIterator( selhash );
5580 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5582 // If the class isn't in the IDL, ignore it
5583 const char* cname = class_itr->key;
5584 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5588 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5589 if( strcmp( core_class, class_itr->key )) {
5593 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5594 if( !found->size ) {
5595 jsonObjectFree( found );
5599 jsonObjectFree( found );
5602 const jsonObject* node = NULL;
5603 jsonIterator* select_itr = jsonNewIterator( snode );
5604 while( (node = jsonIteratorNext( select_itr )) ) {
5605 const char* item_str = jsonObjectGetString( node );
5606 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5607 char* fname = osrfHashGet( field, "name" );
5612 if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5618 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5623 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5624 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5627 i18n = osrfHashGet( field, "i18n" );
5629 if( str_is_true( i18n ) ) {
5630 char* pkey = osrfHashGet( idlClass, "primarykey" );
5631 char* tname = osrfHashGet( idlClass, "tablename" );
5633 osrf_buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5634 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5635 tname, cname, fname, pkey, cname, pkey, locale, fname );
5637 osrf_buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5640 osrf_buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5644 jsonIteratorFree( select_itr );
5647 jsonIteratorFree( class_itr );
5649 char* col_list = osrf_buffer_release( select_buf );
5650 char* table = oilsGetRelation( meta );
5652 table = strdup( "(null)" );
5654 osrf_buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5658 // Clear the query stack (as a fail-safe precaution against possible
5659 // leftover garbage); then push the first query frame onto the stack.
5660 clear_query_stack();
5662 if( add_query_core( NULL, core_class ) ) {
5664 osrfAppSessionStatus(
5666 OSRF_STATUS_INTERNALSERVERERROR,
5667 "osrfMethodException",
5669 "Unable to build query frame for core class"
5671 osrf_buffer_free( sql_buf );
5672 if( defaultselhash )
5673 jsonObjectFree( defaultselhash );
5677 // Add the JOIN clauses, if any
5679 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5680 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5681 OSRF_BUFFER_ADD( sql_buf, join_clause );
5682 free( join_clause );
5685 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5686 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5688 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5690 // Add the conditions in the WHERE clause
5691 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5693 osrfAppSessionStatus(
5695 OSRF_STATUS_INTERNALSERVERERROR,
5696 "osrfMethodException",
5698 "Severe query error -- see error log for more details"
5700 osrf_buffer_free( sql_buf );
5701 if( defaultselhash )
5702 jsonObjectFree( defaultselhash );
5703 clear_query_stack();
5706 osrf_buffer_add( sql_buf, pred );
5710 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5711 if( rest_of_query ) {
5712 const jsonObject* order_by = NULL;
5713 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5715 char* order_by_list = NULL;
5717 if( JSON_ARRAY == order_by->type ) {
5718 order_by_list = buildOrderByFromArray( ctx, order_by );
5719 if( !order_by_list ) {
5720 osrf_buffer_free( sql_buf );
5721 if( defaultselhash )
5722 jsonObjectFree( defaultselhash );
5723 clear_query_stack();
5726 } else if( JSON_HASH == order_by->type ) {
5727 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5728 // and build a list of ORDER BY expressions.
5729 growing_buffer* order_buf = osrf_buffer_init( 128 );
5731 jsonIterator* class_itr = jsonNewIterator( order_by );
5732 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5734 ClassInfo* order_class_info = search_alias( class_itr->key );
5735 if( ! order_class_info )
5736 continue; // class not referenced by FROM clause? Ignore it.
5738 if( JSON_HASH == snode->type ) {
5740 // If the data for the current class is a JSON_HASH, then it is
5741 // keyed on field name.
5743 const jsonObject* onode = NULL;
5744 jsonIterator* order_itr = jsonNewIterator( snode );
5745 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5747 osrfHash* field_def = osrfHashGet(
5748 order_class_info->fields, order_itr->key );
5750 continue; // Field not defined in IDL? Ignore it.
5751 if( str_is_true( osrfHashGet( field_def, "virtual")))
5752 continue; // Field is virtual? Ignore it.
5754 char* field_str = NULL;
5755 char* direction = NULL;
5756 if( onode->type == JSON_HASH ) {
5757 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5758 field_str = searchFieldTransform(
5759 class_itr->key, field_def, onode );
5761 osrfAppSessionStatus(
5763 OSRF_STATUS_INTERNALSERVERERROR,
5764 "osrfMethodException",
5766 "Severe query error in ORDER BY clause -- "
5767 "see error log for more details"
5769 jsonIteratorFree( order_itr );
5770 jsonIteratorFree( class_itr );
5771 osrf_buffer_free( order_buf );
5772 osrf_buffer_free( sql_buf );
5773 if( defaultselhash )
5774 jsonObjectFree( defaultselhash );
5775 clear_query_stack();
5779 growing_buffer* field_buf = osrf_buffer_init( 16 );
5780 osrf_buffer_fadd( field_buf, "\"%s\".%s",
5781 class_itr->key, order_itr->key );
5782 field_str = osrf_buffer_release( field_buf );
5785 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5786 const char* dir = jsonObjectGetString( order_by );
5787 if(!strncasecmp( dir, "d", 1 )) {
5788 direction = " DESC";
5792 field_str = strdup( order_itr->key );
5793 const char* dir = jsonObjectGetString( onode );
5794 if( !strncasecmp( dir, "d", 1 )) {
5795 direction = " DESC";
5804 osrf_buffer_add( order_buf, ", " );
5807 osrf_buffer_add( order_buf, field_str );
5811 osrf_buffer_add( order_buf, direction );
5813 } // end while; looping over ORDER BY expressions
5815 jsonIteratorFree( order_itr );
5817 } else if( JSON_STRING == snode->type ) {
5818 // We expect a comma-separated list of sort fields.
5819 const char* str = jsonObjectGetString( snode );
5820 if( strchr( str, ';' )) {
5821 // No semicolons allowed. It is theoretically possible for a
5822 // legitimate semicolon to occur within quotes, but it's not likely
5823 // to occur in practice in the context of an ORDER BY list.
5824 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5825 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5827 osrfAppSessionStatus(
5829 OSRF_STATUS_INTERNALSERVERERROR,
5830 "osrfMethodException",
5832 "Possible attempt at SOL injection -- "
5833 "semicolon found in ORDER BY list"
5836 jsonIteratorFree( class_itr );
5837 osrf_buffer_free( order_buf );
5838 osrf_buffer_free( sql_buf );
5839 if( defaultselhash )
5840 jsonObjectFree( defaultselhash );
5841 clear_query_stack();
5844 osrf_buffer_add( order_buf, str );
5848 } // end while; looping over order_by classes
5850 jsonIteratorFree( class_itr );
5851 order_by_list = osrf_buffer_release( order_buf );
5854 osrfLogWarning( OSRF_LOG_MARK,
5855 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5856 "no ORDER BY generated" );
5859 if( order_by_list && *order_by_list ) {
5860 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5861 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5864 free( order_by_list );
5867 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5869 const char* str = jsonObjectGetString( limit );
5879 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5881 const char* str = jsonObjectGetString( offset );
5892 if( defaultselhash )
5893 jsonObjectFree( defaultselhash );
5894 clear_query_stack();
5896 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5897 return osrf_buffer_release( sql_buf );
5900 int doJSONSearch ( osrfMethodContext* ctx ) {
5901 if(osrfMethodVerifyContext( ctx )) {
5902 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5906 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5910 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5914 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5915 flags |= SELECT_DISTINCT;
5917 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5918 flags |= DISABLE_I18N;
5920 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5921 clear_query_stack(); // a possibly needless precaution
5922 char* sql = buildQuery( ctx, hash, flags );
5923 clear_query_stack();
5930 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5933 dbhandle = writehandle;
5935 dbi_result result = dbi_conn_query( dbhandle, sql );
5938 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5940 if( dbi_result_first_row( result )) {
5941 /* JSONify the result */
5942 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5945 jsonObject* return_val = oilsMakeJSONFromResult( result );
5946 osrfAppRespond( ctx, return_val );
5947 jsonObjectFree( return_val );
5948 } while( dbi_result_next_row( result ));
5951 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5954 osrfAppRespondComplete( ctx, NULL );
5956 /* clean up the query */
5957 dbi_result_free( result );
5962 int errnum = dbi_conn_error( dbhandle, &msg );
5963 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5964 modulename, sql, errnum, msg ? msg : "(No description available)" );
5965 osrfAppSessionStatus(
5967 OSRF_STATUS_INTERNALSERVERERROR,
5968 "osrfMethodException",
5970 "Severe query error -- see error log for more details"
5972 if( !oilsIsDBConnected( dbhandle ))
5973 osrfAppSessionPanic( ctx->session );
5980 // The last parameter, err, is used to report an error condition by updating an int owned by
5981 // the calling code.
5983 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5984 // It is the responsibility of the calling code to initialize *err before the
5985 // call, so that it will be able to make sense of the result.
5987 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5988 // redundant anyway.
5989 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5990 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5992 const char* tz = _sanitize_tz_name(ctx->session->session_tz);
5995 dbhandle = writehandle;
5997 char* core_class = osrfHashGet( class_meta, "classname" );
5998 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
6000 char* pkey = osrfHashGet( class_meta, "primarykey" );
6002 if (!ctx->session->userData)
6003 (void) initSessionCache( ctx );
6005 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
6006 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
6007 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
6009 int i_respond_directly = 0;
6010 int flesh_depth = 0;
6012 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
6014 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
6019 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
6021 // Setting the timezone if requested and not in a transaction
6022 if (!getXactId(ctx)) {
6026 dbi_result tz_res = dbi_conn_queryf( writehandle, "SET timezone TO '%s'; -- cstore", tz );
6028 osrfLogError( OSRF_LOG_MARK, "%s: Error setting timezone %s", modulename, tz);
6029 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
6030 "osrfMethodException", ctx->request, "Error setting timezone" );
6031 if( !oilsIsDBConnected( writehandle )) {
6032 osrfAppSessionPanic( ctx->session );
6036 dbi_result_free( tz_res );
6041 dbi_result res = dbi_conn_queryf( writehandle, "SET timezone TO DEFAULT; -- cstore" );
6043 osrfLogError( OSRF_LOG_MARK, "%s: Error resetting timezone", modulename);
6044 if( !oilsIsDBConnected( writehandle )) {
6045 osrfAppSessionPanic( ctx->session );
6049 dbi_result_free( res );
6055 dbi_result result = dbi_conn_query( dbhandle, sql );
6057 if( NULL == result ) {
6059 int errnum = dbi_conn_error( dbhandle, &msg );
6060 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
6061 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
6062 msg ? msg : "(No description available)" );
6063 if( !oilsIsDBConnected( dbhandle ))
6064 osrfAppSessionPanic( ctx->session );
6065 osrfAppSessionStatus(
6067 OSRF_STATUS_INTERNALSERVERERROR,
6068 "osrfMethodException",
6070 "Severe query error -- see error log for more details"
6077 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
6081 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
6082 jsonObject* row_obj = NULL;
6084 // The following two steps are for verifyObjectPCRUD()'s benefit.
6085 // 1. get the flesh depth
6086 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
6088 flesh_depth = (int) jsonObjectGetNumber( _tmp );
6089 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
6090 flesh_depth = max_flesh_depth;
6093 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
6094 // over the whole life of this request. This means if we've already set
6095 // up a rs_size_req_%d, do nothing.
6096 // a. Incidentally, we can also use this opportunity to set i_respond_directly
6097 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
6098 if( !rs_size ) { // pointer null, so value not set in hash
6099 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
6100 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
6102 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
6103 unsigned long long result_count = dbi_result_get_numrows( result );
6104 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
6105 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
6108 if( dbi_result_first_row( result )) {
6110 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
6111 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
6112 // eliminate the duplicates.
6113 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
6114 osrfHash* dedup = osrfNewHash();
6116 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
6117 char* pkey_val = oilsFMGetString( row_obj, pkey );
6118 if( osrfHashGet( dedup, pkey_val ) ) {
6119 jsonObjectFree( row_obj );
6122 if( !enforce_pcrud || !need_to_verify ||
6123 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
6124 osrfHashSet( dedup, pkey_val, pkey_val );
6125 jsonObjectPush( res_list, row_obj );
6128 } while( dbi_result_next_row( result ));
6129 osrfHashFree( dedup );
6132 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
6136 /* clean up the query */
6137 dbi_result_free( result );
6140 // If we're asked to flesh, and there's anything to flesh, then flesh it
6141 // (formerly we would skip fleshing if in pcrud mode, but now we support
6142 // fleshing even in PCRUD).
6143 if( res_list->size ) {
6144 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
6145 jsonObject* flesh_fields;
6146 jsonObject* flesh_blob = NULL;
6147 osrfStringArray* link_fields = NULL;
6148 osrfHash* links = NULL;
6152 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
6153 if( temp_blob && flesh_depth > 0 ) {
6155 flesh_blob = jsonObjectClone( temp_blob );
6156 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
6158 links = osrfHashGet( class_meta, "links" );
6160 // Make an osrfStringArray of the names of fields to be fleshed
6161 if( flesh_fields ) {
6162 if( flesh_fields->size == 1 ) {
6163 const char* _t = jsonObjectGetString(
6164 jsonObjectGetIndex( flesh_fields, 0 ) );
6165 if( !strcmp( _t, "*" ))
6166 link_fields = osrfHashKeys( links );
6169 if( !link_fields ) {
6171 link_fields = osrfNewStringArray( 1 );
6172 jsonIterator* _i = jsonNewIterator( flesh_fields );
6173 while ((_f = jsonIteratorNext( _i ))) {
6174 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
6176 jsonIteratorFree( _i );
6179 want_flesh = link_fields ? 1 : 0;
6183 osrfHash* fields = osrfHashGet( class_meta, "fields" );
6185 // Iterate over the JSON_ARRAY of rows
6187 unsigned long res_idx = 0;
6188 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
6191 const char* link_field;
6193 // Iterate over the list of fleshable fields
6195 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
6197 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
6199 osrfHash* kid_link = osrfHashGet( links, link_field );
6201 continue; // Not a link field; skip it
6203 osrfHash* field = osrfHashGet( fields, link_field );
6205 continue; // Not a field at all; skip it (IDL is ill-formed)
6207 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
6208 osrfHashGet( kid_link, "class" ));
6210 continue; // The class it links to doesn't exist; skip it
6212 const char* reltype = osrfHashGet( kid_link, "reltype" );
6214 continue; // No reltype; skip it (IDL is ill-formed)
6216 osrfHash* value_field = field;
6218 if( !strcmp( reltype, "has_many" )
6219 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
6220 value_field = osrfHashGet(
6221 fields, osrfHashGet( class_meta, "primarykey" ) );
6224 int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
6225 // fleshing pcrud case: we require the controller in need_to_verify mode
6226 if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
6227 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
6231 (unsigned long) atoi( osrfHashGet(field, "array_position") ),
6233 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
6239 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
6241 if( link_map->size > 0 ) {
6242 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
6245 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
6250 osrfHashGet( kid_link, "class" ),
6257 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
6258 osrfHashGet( kid_link, "field" ),
6259 osrfHashGet( kid_link, "class" ),
6260 osrfHashGet( kid_link, "key" ),
6261 osrfHashGet( kid_link, "reltype" )
6264 const char* search_key = jsonObjectGetString(
6265 jsonObjectGetIndex( cur,
6266 atoi( osrfHashGet( value_field, "array_position" ) )
6271 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
6275 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
6277 // construct WHERE clause
6278 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
6281 osrfHashGet( kid_link, "key" ),
6282 jsonNewObject( search_key )
6285 // construct the rest of the query, mostly
6286 // by copying pieces of the previous level of query
6287 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
6288 jsonObjectSetKey( rest_of_query, "flesh",
6289 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
6293 jsonObjectSetKey( rest_of_query, "flesh_fields",
6294 jsonObjectClone( flesh_blob ));
6296 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
6297 jsonObjectSetKey( rest_of_query, "order_by",
6298 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
6302 if( jsonObjectGetKeyConst( query_hash, "select" )) {
6303 jsonObjectSetKey( rest_of_query, "select",
6304 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
6308 // do the query, recursively, to expand the fleshable field
6309 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
6310 where_clause, rest_of_query, err );
6312 jsonObjectFree( where_clause );
6313 jsonObjectFree( rest_of_query );
6316 osrfStringArrayFree( link_fields );
6317 jsonObjectFree( res_list );
6318 jsonObjectFree( flesh_blob );
6322 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
6323 osrfHashGet( kid_link, "class" ), kids->size );
6325 // Traverse the result set
6326 jsonObject* X = NULL;
6327 if( link_map->size > 0 && kids->size > 0 ) {
6329 kids = jsonNewObjectType( JSON_ARRAY );
6331 jsonObject* _k_node;
6332 unsigned long res_idx = 0;
6333 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
6339 (unsigned long) atoi(
6345 osrfHashGet( kid_link, "class" )
6349 osrfStringArrayGetString( link_map, 0 )
6357 } // end while loop traversing X
6360 if (kids->size > 0) {
6362 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
6363 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
6365 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
6366 osrfHashGet( kid_link, "field" ));
6369 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6370 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
6375 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
6377 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
6378 osrfHashGet( kid_link, "field" ) );
6381 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6382 jsonObjectClone( kids )
6387 jsonObjectFree( kids );
6391 jsonObjectFree( kids );
6393 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
6394 osrfHashGet( kid_link, "field" ) );
6395 } // end while loop traversing list of fleshable fields
6398 if( i_respond_directly ) {
6399 if ( *methodtype == 'i' ) {
6400 osrfAppRespond( ctx,
6401 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
6403 osrfAppRespond( ctx, cur );
6406 } // end while loop traversing res_list
6407 jsonObjectFree( flesh_blob );
6408 osrfStringArrayFree( link_fields );
6411 if( i_respond_directly ) {
6412 jsonObjectFree( res_list );
6413 return jsonNewObjectType( JSON_ARRAY );
6420 int doUpdate( osrfMethodContext* ctx ) {
6421 if( osrfMethodVerifyContext( ctx )) {
6422 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6427 timeout_needs_resetting = 1;
6429 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6431 jsonObject* target = NULL;
6433 target = jsonObjectGetIndex( ctx->params, 1 );
6435 target = jsonObjectGetIndex( ctx->params, 0 );
6437 if(!verifyObjectClass( ctx, target )) {
6438 osrfAppSessionStatus(ctx->session, OSRF_STATUS_BADREQUEST,
6439 "osrfMethodException", ctx->request,
6440 "Invalid object or insufficient permissions"
6442 osrfAppRespondComplete( ctx, NULL );
6446 if( getXactId( ctx ) == NULL ) {
6447 osrfAppSessionStatus(
6449 OSRF_STATUS_BADREQUEST,
6450 "osrfMethodException",
6452 "No active transaction -- required for UPDATE"
6454 osrfAppRespondComplete( ctx, NULL );
6458 // The following test is harmless but redundant. If a class is
6459 // readonly, we don't register an update method for it.
6460 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6461 osrfAppSessionStatus(
6463 OSRF_STATUS_BADREQUEST,
6464 "osrfMethodException",
6466 "Cannot UPDATE readonly class"
6468 osrfAppRespondComplete( ctx, NULL );
6472 const char* trans_id = getXactId( ctx );
6474 // Set the last_xact_id
6475 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6477 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6478 trans_id, target->classname, index );
6479 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6482 char* pkey = osrfHashGet( meta, "primarykey" );
6483 osrfHash* fields = osrfHashGet( meta, "fields" );
6485 char* id = oilsFMGetString( target, pkey );
6489 "%s updating %s object with %s = %s",
6491 osrfHashGet( meta, "fieldmapper" ),
6496 dbhandle = writehandle;
6497 growing_buffer* sql = osrf_buffer_init( 128 );
6498 osrf_buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6501 osrfHash* field_def = NULL;
6502 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6503 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6505 // Skip virtual fields, and the primary key
6506 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6509 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6513 const char* field_name = osrfHashIteratorKey( field_itr );
6514 if( ! strcmp( field_name, pkey ) )
6517 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6519 int value_is_numeric = 0; // boolean
6521 if( field_object && field_object->classname ) {
6522 value = oilsFMGetString(
6524 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6526 } else if( field_object && JSON_BOOL == field_object->type ) {
6527 if( jsonBoolIsTrue( field_object ) )
6528 value = strdup( "t" );
6530 value = strdup( "f" );
6532 value = jsonObjectToSimpleString( field_object );
6533 if( field_object && JSON_NUMBER == field_object->type )
6534 value_is_numeric = 1;
6537 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6538 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6540 if( !field_object || field_object->type == JSON_NULL ) {
6541 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6542 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6546 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6547 osrf_buffer_fadd( sql, " %s = NULL", field_name );
6550 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6554 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6556 const char* numtype = get_datatype( field_def );
6557 if( !strncmp( numtype, "INT", 3 ) ) {
6558 osrf_buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6559 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6560 osrf_buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6562 // Must really be intended as a string, so quote it
6563 if( dbi_conn_quote_string( dbhandle, &value )) {
6564 osrf_buffer_fadd( sql, " %s = %s", field_name, value );
6566 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6567 modulename, value );
6568 osrfAppSessionStatus(
6570 OSRF_STATUS_INTERNALSERVERERROR,
6571 "osrfMethodException",
6573 "Error quoting string -- please see the error log for more details"
6577 osrfHashIteratorFree( field_itr );
6578 osrf_buffer_free( sql );
6579 osrfAppRespondComplete( ctx, NULL );
6584 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6587 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6591 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6592 osrf_buffer_fadd( sql, " %s = %s", field_name, value );
6594 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6595 osrfAppSessionStatus(
6597 OSRF_STATUS_INTERNALSERVERERROR,
6598 "osrfMethodException",
6600 "Error quoting string -- please see the error log for more details"
6604 osrfHashIteratorFree( field_itr );
6605 osrf_buffer_free( sql );
6606 osrfAppRespondComplete( ctx, NULL );
6615 osrfHashIteratorFree( field_itr );
6617 jsonObject* obj = jsonNewObject( id );
6619 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6620 dbi_conn_quote_string( dbhandle, &id );
6622 osrf_buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6624 char* query = osrf_buffer_release( sql );
6625 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6627 dbi_result result = dbi_conn_query( dbhandle, query );
6632 jsonObjectFree( obj );
6633 obj = jsonNewObject( NULL );
6635 int errnum = dbi_conn_error( dbhandle, &msg );
6638 "%s ERROR updating %s object with %s = %s: %d %s",
6640 osrfHashGet( meta, "fieldmapper" ),
6644 msg ? msg : "(No description available)"
6646 osrfAppSessionStatus(
6648 OSRF_STATUS_INTERNALSERVERERROR,
6649 "osrfMethodException",
6651 "Error in updating a row -- please see the error log for more details"
6653 if( !oilsIsDBConnected( dbhandle ))
6654 osrfAppSessionPanic( ctx->session );
6657 dbi_result_free( result );
6660 osrfAppRespondComplete( ctx, obj );
6661 jsonObjectFree( obj );
6665 int doDelete( osrfMethodContext* ctx ) {
6666 if( osrfMethodVerifyContext( ctx )) {
6667 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6672 timeout_needs_resetting = 1;
6674 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6676 if( getXactId( ctx ) == NULL ) {
6677 osrfAppSessionStatus(
6679 OSRF_STATUS_BADREQUEST,
6680 "osrfMethodException",
6682 "No active transaction -- required for DELETE"
6684 osrfAppRespondComplete( ctx, NULL );
6688 // The following test is harmless but redundant. If a class is
6689 // readonly, we don't register a delete method for it.
6690 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6691 osrfAppSessionStatus(
6693 OSRF_STATUS_BADREQUEST,
6694 "osrfMethodException",
6696 "Cannot DELETE readonly class"
6698 osrfAppRespondComplete( ctx, NULL );
6702 dbhandle = writehandle;
6704 char* pkey = osrfHashGet( meta, "primarykey" );
6711 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6712 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6713 osrfAppSessionStatus(ctx->session, OSRF_STATUS_BADREQUEST,
6714 "osrfMethodException", ctx->request,
6715 "Invalid object or insufficient permissions"
6717 osrfAppRespondComplete( ctx, NULL );
6721 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6723 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6724 osrfAppRespondComplete( ctx, NULL );
6727 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6732 "%s deleting %s object with %s = %s",
6734 osrfHashGet( meta, "fieldmapper" ),
6739 jsonObject* obj = jsonNewObject( id );
6741 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6742 dbi_conn_quote_string( writehandle, &id );
6744 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6745 osrfHashGet( meta, "tablename" ), pkey, id );
6750 jsonObjectFree( obj );
6751 obj = jsonNewObject( NULL );
6753 int errnum = dbi_conn_error( writehandle, &msg );
6756 "%s ERROR deleting %s object with %s = %s: %d %s",
6758 osrfHashGet( meta, "fieldmapper" ),
6762 msg ? msg : "(No description available)"
6764 osrfAppSessionStatus(
6766 OSRF_STATUS_INTERNALSERVERERROR,
6767 "osrfMethodException",
6769 "Error in deleting a row -- please see the error log for more details"
6771 if( !oilsIsDBConnected( writehandle ))
6772 osrfAppSessionPanic( ctx->session );
6774 dbi_result_free( result );
6778 osrfAppRespondComplete( ctx, obj );
6779 jsonObjectFree( obj );
6784 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6785 @param result An iterator for a result set; we only look at the current row.
6786 @param @meta Pointer to the class metadata for the core class.
6787 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6789 If a column is not defined in the IDL, or if it has no array_position defined for it in
6790 the IDL, or if it is defined as virtual, ignore it.
6792 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6793 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6794 array_position in the IDL.
6796 A field defined in the IDL but not represented in the returned row will leave a hole
6797 in the JSON_ARRAY. In effect it will be treated as a null value.
6799 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6800 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6801 classname corresponding to the @a meta argument.
6803 The calling code is responsible for freeing the the resulting jsonObject by calling
6806 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6807 if( !( result && meta )) return NULL;
6809 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6810 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6811 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6813 osrfHash* fields = osrfHashGet( meta, "fields" );
6815 int columnIndex = 1;
6816 const char* columnName;
6818 /* cycle through the columns in the row returned from the database */
6819 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6821 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6823 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6825 /* determine the field type and storage attributes */
6826 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6827 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6829 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6830 // or if it has no sequence number there, or if it's virtual, skip it.
6831 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6834 if( str_is_true( osrfHashGet( _f, "virtual" )))
6835 continue; // skip this column: IDL says it's virtual
6837 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6838 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6839 continue; // since we assign sequence numbers dynamically as we load the IDL.
6841 fmIndex = atoi( pos );
6842 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6844 continue; // This field is not defined in the IDL
6847 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6848 // sequence number from the IDL (which is likely to be different from the sequence
6849 // of columns in the SELECT clause).
6850 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6851 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6856 case DBI_TYPE_INTEGER :
6858 if( attr & DBI_INTEGER_SIZE8 )
6859 jsonObjectSetIndex( object, fmIndex,
6860 jsonNewNumberObject(
6861 dbi_result_get_longlong_idx( result, columnIndex )));
6863 jsonObjectSetIndex( object, fmIndex,
6864 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6868 case DBI_TYPE_DECIMAL :
6869 jsonObjectSetIndex( object, fmIndex,
6870 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6873 case DBI_TYPE_STRING :
6878 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6883 case DBI_TYPE_DATETIME : {
6885 char dt_string[ 256 ] = "";
6888 // Fetch the date column as a time_t
6889 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6891 // Translate the time_t to a human-readable string
6892 if( !( attr & DBI_DATETIME_DATE )) {
6893 gmtime_r( &_tmp_dt, &gmdt );
6894 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6895 } else if( !( attr & DBI_DATETIME_TIME )) {
6896 gmtime_r( &_tmp_dt, &gmdt );
6897 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6899 localtime_r( &_tmp_dt, &gmdt );
6900 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6903 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6907 case DBI_TYPE_BINARY :
6908 osrfLogError( OSRF_LOG_MARK,
6909 "Can't do binary at column %s : index %d", columnName, columnIndex );
6918 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6919 if( !result ) return NULL;
6921 jsonObject* object = jsonNewObject( NULL );
6924 char dt_string[ 256 ];
6927 int columnIndex = 1;
6929 unsigned short type;
6930 const char* columnName;
6932 /* cycle through the column list */
6933 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6935 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6937 /* determine the field type and storage attributes */
6938 type = dbi_result_get_field_type_idx( result, columnIndex );
6939 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6941 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6942 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6947 case DBI_TYPE_INTEGER :
6949 if( attr & DBI_INTEGER_SIZE8 )
6950 jsonObjectSetKey( object, columnName,
6951 jsonNewNumberObject( dbi_result_get_longlong_idx(
6952 result, columnIndex )) );
6954 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6955 dbi_result_get_int_idx( result, columnIndex )) );
6958 case DBI_TYPE_DECIMAL :
6959 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6960 dbi_result_get_double_idx( result, columnIndex )) );
6963 case DBI_TYPE_STRING :
6964 jsonObjectSetKey( object, columnName,
6965 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6968 case DBI_TYPE_DATETIME :
6970 memset( dt_string, '\0', sizeof( dt_string ));
6971 memset( &gmdt, '\0', sizeof( gmdt ));
6973 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6975 if( !( attr & DBI_DATETIME_DATE )) {
6976 gmtime_r( &_tmp_dt, &gmdt );
6977 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6978 } else if( !( attr & DBI_DATETIME_TIME )) {
6979 gmtime_r( &_tmp_dt, &gmdt );
6980 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6982 localtime_r( &_tmp_dt, &gmdt );
6983 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6986 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6989 case DBI_TYPE_BINARY :
6990 osrfLogError( OSRF_LOG_MARK,
6991 "Can't do binary at column %s : index %d", columnName, columnIndex );
6995 } // end while loop traversing result
7000 // Interpret a string as true or false
7001 int str_is_true( const char* str ) {
7002 if( NULL == str || strcasecmp( str, "true" ) )
7008 // Interpret a jsonObject as true or false
7009 static int obj_is_true( const jsonObject* obj ) {
7012 else switch( obj->type )
7020 if( strcasecmp( obj->value.s, "true" ) )
7024 case JSON_NUMBER : // Support 1/0 for perl's sake
7025 if( jsonObjectGetNumber( obj ) == 1.0 )
7034 // Translate a numeric code into a text string identifying a type of
7035 // jsonObject. To be used for building error messages.
7036 static const char* json_type( int code ) {
7042 return "JSON_ARRAY";
7044 return "JSON_STRING";
7046 return "JSON_NUMBER";
7052 return "(unrecognized)";
7056 // Extract the "primitive" attribute from an IDL field definition.
7057 // If we haven't initialized the app, then we must be running in
7058 // some kind of testbed. In that case, default to "string".
7059 static const char* get_primitive( osrfHash* field ) {
7060 const char* s = osrfHashGet( field, "primitive" );
7062 if( child_initialized )
7065 "%s ERROR No \"datatype\" attribute for field \"%s\"",
7067 osrfHashGet( field, "name" )
7075 // Extract the "datatype" attribute from an IDL field definition.
7076 // If we haven't initialized the app, then we must be running in
7077 // some kind of testbed. In that case, default to to NUMERIC,
7078 // since we look at the datatype only for numbers.
7079 static const char* get_datatype( osrfHash* field ) {
7080 const char* s = osrfHashGet( field, "datatype" );
7082 if( child_initialized )
7085 "%s ERROR No \"datatype\" attribute for field \"%s\"",
7087 osrfHashGet( field, "name" )
7096 @brief Determine whether a string is potentially a valid SQL identifier.
7097 @param s The identifier to be tested.
7098 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
7100 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
7101 need to follow all the rules exactly, such as requiring that the first character not
7104 We allow leading and trailing white space. In between, we do not allow punctuation
7105 (except for underscores and dollar signs), control characters, or embedded white space.
7107 More pedantically we should allow quoted identifiers containing arbitrary characters, but
7108 for the foreseeable future such quoted identifiers are not likely to be an issue.
7110 int is_identifier( const char* s) {
7114 // Skip leading white space
7115 while( isspace( (unsigned char) *s ) )
7119 return 0; // Nothing but white space? Not okay.
7121 // Check each character until we reach white space or
7122 // end-of-string. Letters, digits, underscores, and
7123 // dollar signs are okay. With the exception of periods
7124 // (as in schema.identifier), control characters and other
7125 // punctuation characters are not okay. Anything else
7126 // is okay -- it could for example be part of a multibyte
7127 // UTF8 character such as a letter with diacritical marks,
7128 // and those are allowed.
7130 if( isalnum( (unsigned char) *s )
7134 ; // Fine; keep going
7135 else if( ispunct( (unsigned char) *s )
7136 || iscntrl( (unsigned char) *s ) )
7139 } while( *s && ! isspace( (unsigned char) *s ) );
7141 // If we found any white space in the above loop,
7142 // the rest had better be all white space.
7144 while( isspace( (unsigned char) *s ) )
7148 return 0; // White space was embedded within non-white space
7154 @brief Determine whether to accept a character string as a comparison operator.
7155 @param op The candidate comparison operator.
7156 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
7158 We don't validate the operator for real. We just make sure that it doesn't contain
7159 any semicolons or white space (with special exceptions for a few specific operators).
7160 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
7161 space but it's still not a valid operator, then the database will complain.
7163 Another approach would be to compare the string against a short list of approved operators.
7164 We don't do that because we want to allow custom operators like ">100*", which at this
7165 writing would be difficult or impossible to express otherwise in a JSON query.
7167 int is_good_operator( const char* op ) {
7168 if( !op ) return 0; // Sanity check
7172 if( isspace( (unsigned char) *s ) ) {
7173 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
7174 // and IS NOT DISTINCT FROM.
7175 if( !strcasecmp( op, "similar to" ) )
7177 else if( !strcasecmp( op, "is distinct from" ) )
7179 else if( !strcasecmp( op, "is not distinct from" ) )
7184 else if( ';' == *s )
7192 @name Query Frame Management
7194 The following machinery supports a stack of query frames for use by SELECT().
7196 A query frame caches information about one level of a SELECT query. When we enter
7197 a subquery, we push another query frame onto the stack, and pop it off when we leave.
7199 The query frame stores information about the core class, and about any joined classes
7202 The main purpose is to map table aliases to classes and tables, so that a query can
7203 join to the same table more than once. A secondary goal is to reduce the number of
7204 lookups in the IDL by caching the results.
7208 #define STATIC_CLASS_INFO_COUNT 3
7210 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
7213 @brief Allocate a ClassInfo as raw memory.
7214 @return Pointer to the newly allocated ClassInfo.
7216 Except for the in_use flag, which is used only by the allocation and deallocation
7217 logic, we don't initialize the ClassInfo here.
7219 static ClassInfo* allocate_class_info( void ) {
7220 // In order to reduce the number of mallocs and frees, we return a static
7221 // instance of ClassInfo, if we can find one that we're not already using.
7222 // We rely on the fact that the compiler will implicitly initialize the
7223 // static instances so that in_use == 0.
7226 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7227 if( ! static_class_info[ i ].in_use ) {
7228 static_class_info[ i ].in_use = 1;
7229 return static_class_info + i;
7233 // The static ones are all in use. Malloc one.
7235 return safe_malloc( sizeof( ClassInfo ) );
7239 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
7240 @param info Pointer to the ClassInfo to be cleared.
7242 static void clear_class_info( ClassInfo* info ) {
7247 // Free any malloc'd strings
7249 if( info->alias != info->alias_store )
7250 free( info->alias );
7252 if( info->class_name != info->class_name_store )
7253 free( info->class_name );
7255 free( info->source_def );
7257 info->alias = info->class_name = info->source_def = NULL;
7262 @brief Free a ClassInfo and everything it owns.
7263 @param info Pointer to the ClassInfo to be freed.
7265 static void free_class_info( ClassInfo* info ) {
7270 clear_class_info( info );
7272 // If it's one of the static instances, just mark it as not in use
7275 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7276 if( info == static_class_info + i ) {
7277 static_class_info[ i ].in_use = 0;
7282 // Otherwise it must have been malloc'd, so free it
7288 @brief Populate an already-allocated ClassInfo.
7289 @param info Pointer to the ClassInfo to be populated.
7290 @param alias Alias for the class. If it is NULL, or an empty string, use the class
7292 @param class Name of the class.
7293 @return Zero if successful, or 1 if not.
7295 Populate the ClassInfo with copies of the alias and class name, and with pointers to
7296 the relevant portions of the IDL for the specified class.
7298 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
7301 osrfLogError( OSRF_LOG_MARK,
7302 "%s ERROR: No ClassInfo available to populate", modulename );
7303 info->alias = info->class_name = info->source_def = NULL;
7304 info->class_def = info->fields = info->links = NULL;
7309 osrfLogError( OSRF_LOG_MARK,
7310 "%s ERROR: No class name provided for lookup", modulename );
7311 info->alias = info->class_name = info->source_def = NULL;
7312 info->class_def = info->fields = info->links = NULL;
7316 // Alias defaults to class name if not supplied
7317 if( ! alias || ! alias[ 0 ] )
7320 // Look up class info in the IDL
7321 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
7323 osrfLogError( OSRF_LOG_MARK,
7324 "%s ERROR: Class %s not defined in IDL", modulename, class );
7325 info->alias = info->class_name = info->source_def = NULL;
7326 info->class_def = info->fields = info->links = NULL;
7328 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
7329 osrfLogError( OSRF_LOG_MARK,
7330 "%s ERROR: Class %s is defined as virtual", modulename, class );
7331 info->alias = info->class_name = info->source_def = NULL;
7332 info->class_def = info->fields = info->links = NULL;
7336 osrfHash* links = osrfHashGet( class_def, "links" );
7338 osrfLogError( OSRF_LOG_MARK,
7339 "%s ERROR: No links defined in IDL for class %s", modulename, class );
7340 info->alias = info->class_name = info->source_def = NULL;
7341 info->class_def = info->fields = info->links = NULL;
7345 osrfHash* fields = osrfHashGet( class_def, "fields" );
7347 osrfLogError( OSRF_LOG_MARK,
7348 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
7349 info->alias = info->class_name = info->source_def = NULL;
7350 info->class_def = info->fields = info->links = NULL;
7354 char* source_def = oilsGetRelation( class_def );
7358 // We got everything we need, so populate the ClassInfo
7359 if( strlen( alias ) > ALIAS_STORE_SIZE )
7360 info->alias = strdup( alias );
7362 strcpy( info->alias_store, alias );
7363 info->alias = info->alias_store;
7366 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
7367 info->class_name = strdup( class );
7369 strcpy( info->class_name_store, class );
7370 info->class_name = info->class_name_store;
7373 info->source_def = source_def;
7375 info->class_def = class_def;
7376 info->links = links;
7377 info->fields = fields;
7382 #define STATIC_FRAME_COUNT 3
7384 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
7387 @brief Allocate a QueryFrame as raw memory.
7388 @return Pointer to the newly allocated QueryFrame.
7390 Except for the in_use flag, which is used only by the allocation and deallocation
7391 logic, we don't initialize the QueryFrame here.
7393 static QueryFrame* allocate_frame( void ) {
7394 // In order to reduce the number of mallocs and frees, we return a static
7395 // instance of QueryFrame, if we can find one that we're not already using.
7396 // We rely on the fact that the compiler will implicitly initialize the
7397 // static instances so that in_use == 0.
7400 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7401 if( ! static_frame[ i ].in_use ) {
7402 static_frame[ i ].in_use = 1;
7403 return static_frame + i;
7407 // The static ones are all in use. Malloc one.
7409 return safe_malloc( sizeof( QueryFrame ) );
7413 @brief Free a QueryFrame, and all the memory it owns.
7414 @param frame Pointer to the QueryFrame to be freed.
7416 static void free_query_frame( QueryFrame* frame ) {
7421 clear_class_info( &frame->core );
7423 // Free the join list
7425 ClassInfo* info = frame->join_list;
7428 free_class_info( info );
7432 frame->join_list = NULL;
7435 // If the frame is a static instance, just mark it as unused
7437 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7438 if( frame == static_frame + i ) {
7439 static_frame[ i ].in_use = 0;
7444 // Otherwise it must have been malloc'd, so free it
7450 @brief Search a given QueryFrame for a specified alias.
7451 @param frame Pointer to the QueryFrame to be searched.
7452 @param target The alias for which to search.
7453 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7455 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7456 if( ! frame || ! target ) {
7460 ClassInfo* found_class = NULL;
7462 if( !strcmp( target, frame->core.alias ) )
7463 return &(frame->core);
7465 ClassInfo* curr_class = frame->join_list;
7466 while( curr_class ) {
7467 if( strcmp( target, curr_class->alias ) )
7468 curr_class = curr_class->next;
7470 found_class = curr_class;
7480 @brief Push a new (blank) QueryFrame onto the stack.
7482 static void push_query_frame( void ) {
7483 QueryFrame* frame = allocate_frame();
7484 frame->join_list = NULL;
7485 frame->next = curr_query;
7487 // Initialize the ClassInfo for the core class
7488 ClassInfo* core = &frame->core;
7489 core->alias = core->class_name = core->source_def = NULL;
7490 core->class_def = core->fields = core->links = NULL;
7496 @brief Pop a QueryFrame off the stack and destroy it.
7498 static void pop_query_frame( void ) {
7503 QueryFrame* popped = curr_query;
7504 curr_query = popped->next;
7506 free_query_frame( popped );
7510 @brief Populate the ClassInfo for the core class.
7511 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7512 class name as an alias.
7513 @param class_name Name of the core class.
7514 @return Zero if successful, or 1 if not.
7516 Populate the ClassInfo of the core class with copies of the alias and class name, and
7517 with pointers to the relevant portions of the IDL for the core class.
7519 static int add_query_core( const char* alias, const char* class_name ) {
7522 if( ! curr_query ) {
7523 osrfLogError( OSRF_LOG_MARK,
7524 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7526 } else if( curr_query->core.alias ) {
7527 osrfLogError( OSRF_LOG_MARK,
7528 "%s ERROR: Core class %s already populated as %s",
7529 modulename, curr_query->core.class_name, curr_query->core.alias );
7533 build_class_info( &curr_query->core, alias, class_name );
7534 if( curr_query->core.alias )
7537 osrfLogError( OSRF_LOG_MARK,
7538 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7544 @brief Search the current QueryFrame for a specified alias.
7545 @param target The alias for which to search.
7546 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7548 static inline ClassInfo* search_alias( const char* target ) {
7549 return search_alias_in_frame( curr_query, target );
7553 @brief Search all levels of query for a specified alias, starting with the current query.
7554 @param target The alias for which to search.
7555 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7557 static ClassInfo* search_all_alias( const char* target ) {
7558 ClassInfo* found_class = NULL;
7559 QueryFrame* curr_frame = curr_query;
7561 while( curr_frame ) {
7562 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7565 curr_frame = curr_frame->next;
7572 @brief Add a class to the list of classes joined to the current query.
7573 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7574 the class name as an alias.
7575 @param classname The name of the class to be added.
7576 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7578 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7580 if( ! classname || ! *classname ) { // sanity check
7581 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7588 const ClassInfo* conflict = search_alias( alias );
7590 osrfLogError( OSRF_LOG_MARK,
7591 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7592 modulename, alias, conflict->class_name );
7596 ClassInfo* info = allocate_class_info();
7598 if( build_class_info( info, alias, classname ) ) {
7599 free_class_info( info );
7603 // Add the new ClassInfo to the join list of the current QueryFrame
7604 info->next = curr_query->join_list;
7605 curr_query->join_list = info;
7611 @brief Destroy all nodes on the query stack.
7613 static void clear_query_stack( void ) {
7619 @brief Implement the set_audit_info method.
7620 @param ctx Pointer to the method context.
7621 @return Zero if successful, or -1 if not.
7623 Issue a SAVEPOINT to the database server.
7628 - workstation id (int)
7630 If user id is not provided the authkey will be used.
7631 For PCRUD the authkey is always used, even if a user is provided.
7633 int setAuditInfo( osrfMethodContext* ctx ) {
7634 if(osrfMethodVerifyContext( ctx )) {
7635 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
7639 // Get the user id from the parameters
7640 const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7642 if( enforce_pcrud || !user_id ) {
7643 timeout_needs_resetting = 1;
7644 const jsonObject* user = verifyUserPCRUD( ctx );
7647 osrfAppRespondComplete( ctx, NULL );
7651 // Not PCRUD and have a user_id?
7652 int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7653 osrfAppRespondComplete( ctx, NULL );
7658 @brief Save a audit info
7659 @param ctx Pointer to the method context.
7660 @param user_id User ID to write as a string
7661 @param ws_id Workstation ID to write as a string
7663 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7664 if( ctx && ctx->session ) {
7665 osrfAppSession* session = ctx->session;
7667 osrfHash* cache = session->userData;
7669 // If the session doesn't already have a hash, create one. Make sure
7670 // that the application session frees the hash when it terminates.
7671 if( NULL == cache ) {
7672 session->userData = cache = osrfNewHash();
7673 osrfHashSetCallback( cache, &sessionDataFree );
7674 ctx->session->userDataFree = &userDataFree;
7677 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7679 osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7681 int errnum = dbi_conn_error( writehandle, &msg );
7684 "%s: Error setting auditor information: %d %s",
7687 msg ? msg : "(No description available)"
7689 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7690 "osrfMethodException", ctx->request, "Error setting auditor info" );
7691 if( !oilsIsDBConnected( writehandle ))
7692 osrfAppSessionPanic( ctx->session );
7695 dbi_result_free( result );
7702 @brief Remove all but safe character from savepoint name
7703 @param sp User-supplied savepoint name
7704 @return sanitized savepoint name, or NULL
7706 The caller is expected to free the returned string. Note that
7707 this function exists only because we can't use PQescapeLiteral
7708 without either forking libdbi or abandoning it.
7710 static char* _sanitize_savepoint_name( const char* sp ) {
7712 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_";
7714 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7715 // and the default value of NAMEDATALEN is 64; that should be long enough
7716 // for our purposes, and it's unlikely that anyone is going to recompile
7717 // PostgreSQL to have a smaller value, so cap the identifier name
7718 // accordingly to avoid the remote chance that someone manages to pass in a
7719 // 12GB savepoint name
7720 const int MAX_LITERAL_NAMELEN = 63;
7723 if (len > MAX_LITERAL_NAMELEN) {
7724 len = MAX_LITERAL_NAMELEN;
7727 char* safeSpName = safe_malloc( len + 1 );
7731 for (j = 0; j < len; j++) {
7732 found = strchr(safe_chars, sp[j]);
7734 safeSpName[ i++ ] = found[0];
7737 safeSpName[ i ] = '\0';
7742 @brief Remove all but safe character from TZ name
7743 @param tz User-supplied TZ name
7744 @return sanitized TZ name, or NULL
7746 The caller is expected to free the returned string. Note that
7747 this function exists only because we can't use PQescapeLiteral
7748 without either forking libdbi or abandoning it.
7750 static char* _sanitize_tz_name( const char* tz ) {
7752 if (NULL == tz) return NULL;
7754 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_/-+";
7756 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7757 // and the default value of NAMEDATALEN is 64; that should be long enough
7758 // for our purposes, and it's unlikely that anyone is going to recompile
7759 // PostgreSQL to have a smaller value, so cap the identifier name
7760 // accordingly to avoid the remote chance that someone manages to pass in a
7761 // 12GB savepoint name
7762 const int MAX_LITERAL_NAMELEN = 63;
7765 if (len > MAX_LITERAL_NAMELEN) {
7766 len = MAX_LITERAL_NAMELEN;
7769 char* safeSpName = safe_malloc( len + 1 );
7773 for (j = 0; j < len; j++) {
7774 found = strchr(safe_chars, tz[j]);
7776 safeSpName[ i++ ] = found[0];
7779 safeSpName[ i ] = '\0';