3 @brief Utility routines for translating JSON into SQL.
11 #include "opensrf/utils.h"
12 #include "opensrf/log.h"
13 #include "opensrf/osrf_application.h"
14 #include "openils/oils_utils.h"
15 #include "openils/oils_sql.h"
17 // The next four macros are OR'd together as needed to form a set
18 // of bitflags. SUBCOMBO enables an extra pair of parentheses when
19 // nesting one UNION, INTERSECT or EXCEPT inside another.
20 // SUBSELECT tells us we're in a subquery, so don't add the
21 // terminal semicolon yet.
24 #define DISABLE_I18N 2
25 #define SELECT_DISTINCT 1
30 struct ClassInfoStruct;
31 typedef struct ClassInfoStruct ClassInfo;
33 #define ALIAS_STORE_SIZE 16
34 #define CLASS_NAME_STORE_SIZE 16
36 struct ClassInfoStruct {
40 osrfHash* class_def; // Points into IDL
41 osrfHash* fields; // Points into IDL
42 osrfHash* links; // Points into IDL
44 // The remaining members are private and internal. Client code should not
45 // access them directly.
47 ClassInfo* next; // Supports linked list of joined classes
48 int in_use; // boolean
50 // We usually store the alias and class name in the following arrays, and
51 // point the corresponding pointers at them. When the string is too big
52 // for the array (which will probably never happen in practice), we strdup it.
54 char alias_store[ ALIAS_STORE_SIZE + 1 ];
55 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
58 struct QueryFrameStruct;
59 typedef struct QueryFrameStruct QueryFrame;
61 struct QueryFrameStruct {
63 ClassInfo* join_list; // linked list of classes joined to the core class
64 QueryFrame* next; // implements stack as linked list
65 int in_use; // boolean
68 static int timeout_needs_resetting;
69 static time_t time_next_reset;
71 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
73 static void setXactId( osrfMethodContext* ctx );
74 static inline const char* getXactId( osrfMethodContext* ctx );
75 static inline void clearXactId( osrfMethodContext* ctx );
77 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
78 jsonObject* where_hash, jsonObject* query_hash, int* err );
79 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
80 static jsonObject* oilsMakeJSONFromResult( dbi_result );
82 static char* searchSimplePredicate ( const char* op, const char* class_alias,
83 osrfHash* field, const jsonObject* node );
84 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
85 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject* );
86 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*,
88 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
89 static char* searchINPredicate ( const char*, osrfHash*,
90 jsonObject*, const char*, osrfMethodContext* );
91 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
92 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
93 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo*, int, osrfMethodContext* );
94 static char* buildSELECT( const jsonObject*, jsonObject* rest_of_query,
95 osrfHash* meta, osrfMethodContext* ctx );
96 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array );
98 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
100 char* SELECT ( osrfMethodContext*, jsonObject*, const jsonObject*, const jsonObject*,
101 const jsonObject*, const jsonObject*, const jsonObject*, const jsonObject*, int );
103 static osrfStringArray* getPermLocationCache( osrfMethodContext*, const char* );
104 static void setPermLocationCache( osrfMethodContext*, const char*, osrfStringArray* );
106 void userDataFree( void* );
107 static void sessionDataFree( char*, void* );
108 static void pcacheFree( char*, void* );
109 static int obj_is_true( const jsonObject* obj );
110 static const char* json_type( int code );
111 static const char* get_primitive( osrfHash* field );
112 static const char* get_datatype( osrfHash* field );
113 static void pop_query_frame( void );
114 static void push_query_frame( void );
115 static int add_query_core( const char* alias, const char* class_name );
116 static inline ClassInfo* search_alias( const char* target );
117 static ClassInfo* search_all_alias( const char* target );
118 static ClassInfo* add_joined_class( const char* alias, const char* classname );
119 static void clear_query_stack( void );
121 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
122 static const jsonObject* verifyUserPCRUDfull( osrfMethodContext*, int );
123 static int verifyObjectPCRUD( osrfMethodContext*, osrfHash*, const jsonObject*, int );
124 static const char* org_tree_root( osrfMethodContext* ctx );
125 static jsonObject* single_hash( const char* key, const char* value );
127 static int child_initialized = 0; /* boolean */
129 static dbi_conn writehandle; /* our MASTER db connection */
130 static dbi_conn dbhandle; /* our CURRENT db connection */
131 //static osrfHash * readHandles;
133 // The following points to the top of a stack of QueryFrames. It's a little
134 // confusing because the top level of the query is at the bottom of the stack.
135 static QueryFrame* curr_query = NULL;
137 static dbi_conn writehandle; /* our MASTER db connection */
138 static dbi_conn dbhandle; /* our CURRENT db connection */
139 //static osrfHash * readHandles;
141 static int max_flesh_depth = 100;
143 static int perm_at_threshold = 5;
144 static int enforce_pcrud = 0; // Boolean
145 static char* modulename = NULL;
147 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id);
149 static char* _sanitize_tz_name( const char* tz );
150 static char* _sanitize_savepoint_name( const char* sp );
153 @brief Connect to the database.
154 @return A database connection if successful, or NULL if not.
156 dbi_conn oilsConnectDB( const char* mod_name ) {
158 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
159 if( dbi_initialize( NULL ) == -1 ) {
160 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
163 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
165 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
166 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
167 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
168 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
169 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
170 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
171 char* pg_app = osrf_settings_host_value( "/apps/%s/app_settings/database/application_name", mod_name );
173 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
174 dbi_conn handle = dbi_conn_new( driver );
177 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
180 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
182 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
183 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
185 if( host ) dbi_conn_set_option( handle, "host", host );
186 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
187 if( user ) dbi_conn_set_option( handle, "username", user );
188 if( pw ) dbi_conn_set_option( handle, "password", pw );
189 if( db ) dbi_conn_set_option( handle, "dbname", db );
190 if( pg_app ) dbi_conn_set_option( handle, "pgsql_application_name", pg_app );
199 if( dbi_conn_connect( handle ) < 0 ) {
201 if( dbi_conn_connect( handle ) < 0 ) {
203 dbi_conn_error( handle, &msg );
204 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
205 msg ? msg : "(No description available)" );
210 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
216 @brief Select some options.
217 @param module_name: Name of the server.
218 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
220 This source file is used (at this writing) to implement three different servers:
221 - open-ils.reporter-store
225 These servers behave mostly the same, but they implement different combinations of
226 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
228 Here we use the server name in messages to identify which kind of server issued them.
229 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
231 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
233 module_name = "open-ils.cstore"; // bulletproofing with a default
238 modulename = strdup( module_name );
239 enforce_pcrud = do_pcrud;
240 max_flesh_depth = flesh_depth;
244 @brief Install a database connection.
245 @param conn Pointer to a database connection.
247 In some contexts, @a conn may merely provide a driver so that we can process strings
248 properly, without providing an open database connection.
250 void oilsSetDBConnection( dbi_conn conn ) {
251 dbhandle = writehandle = conn;
255 @brief Determine whether a database connection is alive.
256 @param handle Handle for a database connection.
257 @return 1 if the connection is alive, or zero if it isn't.
259 int oilsIsDBConnected( dbi_conn handle ) {
260 // Do an innocuous SELECT. If it succeeds, the database connection is still good.
261 dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
263 dbi_result_free( result );
266 // This is a terrible, horrible, no good, very bad kludge.
267 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
268 // but because (due to a previous error) the database is ignoring all commands,
269 // even innocuous SELECTs, until the current transaction is rolled back. The only
270 // known way to detect this condition via the dbi library is by looking at the error
271 // message. This approach will break if the language or wording of the message ever
273 // Note: the dbi_conn_ping function purports to determine whether the database
274 // connection is live, but at this writing this function is unreliable and useless.
275 static const char* ok_msg = "ERROR: current transaction is aborted, commands "
276 "ignored until end of transaction block\n";
278 dbi_conn_error( handle, &msg );
279 // Newer versions of dbi_conn_error return codes within the error msg.
280 // E.g. 3624914: ERROR: current transaction is aborted, commands ignored until end of transaction block
281 // Substring test should work regardless.
282 const char* substr = strstr(msg, ok_msg);
283 if( substr == NULL ) {
284 osrfLogError( OSRF_LOG_MARK, "Database connection isn't working : %s", msg );
287 return 1; // ignoring SELECT due to previous error; that's okay
292 @brief Get a table name, view name, or subquery for use in a FROM clause.
293 @param class Pointer to the IDL class entry.
294 @return A table name, a view name, or a subquery in parentheses.
296 In some cases the IDL defines a class, not with a table name or a view name, but with
297 a SELECT statement, which may be used as a subquery.
299 char* oilsGetRelation( osrfHash* classdef ) {
301 char* source_def = NULL;
302 const char* tabledef = osrfHashGet( classdef, "tablename" );
305 source_def = strdup( tabledef ); // Return the name of a table or view
307 tabledef = osrfHashGet( classdef, "source_definition" );
309 // Return a subquery, enclosed in parentheses
310 source_def = safe_malloc( strlen( tabledef ) + 3 );
311 source_def[ 0 ] = '(';
312 strcpy( source_def + 1, tabledef );
313 strcat( source_def, ")" );
315 // Not found: return an error
316 const char* classname = osrfHashGet( classdef, "classname" );
321 "%s ERROR No tablename or source_definition for class \"%s\"",
332 @brief Add datatypes from the database to the fields in the IDL.
333 @param handle Handle for a database connection
334 @return Zero if successful, or 1 upon error.
336 For each relevant class in the IDL: ask the database for the datatype of every field.
337 In particular, determine which fields are text fields and which fields are numeric
338 fields, so that we know whether to enclose their values in quotes.
340 int oilsExtendIDL( dbi_conn handle ) {
341 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
342 osrfHash* class = NULL;
343 growing_buffer* query_buf = buffer_init( 64 );
344 int results_found = 0; // boolean
346 // For each class in the IDL...
347 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
348 const char* classname = osrfHashIteratorKey( class_itr );
349 osrfHash* fields = osrfHashGet( class, "fields" );
351 // If the class is virtual, ignore it
352 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
353 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
357 char* tabledef = oilsGetRelation( class );
359 continue; // No such relation -- a query of it would be doomed to failure
361 buffer_reset( query_buf );
362 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
366 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
367 modulename, OSRF_BUFFER_C_STR( query_buf ) );
369 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
374 const char* columnName;
375 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
377 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
380 /* fetch the fieldmapper index */
381 osrfHash* _f = osrfHashGet(fields, columnName);
384 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
386 /* determine the field type and storage attributes */
388 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
390 case DBI_TYPE_INTEGER : {
392 if( !osrfHashGet(_f, "primitive") )
393 osrfHashSet(_f, "number", "primitive");
395 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
396 if( attr & DBI_INTEGER_SIZE8 )
397 osrfHashSet( _f, "INT8", "datatype" );
399 osrfHashSet( _f, "INT", "datatype" );
402 case DBI_TYPE_DECIMAL :
403 if( !osrfHashGet( _f, "primitive" ))
404 osrfHashSet( _f, "number", "primitive" );
406 osrfHashSet( _f, "NUMERIC", "datatype" );
409 case DBI_TYPE_STRING :
410 if( !osrfHashGet( _f, "primitive" ))
411 osrfHashSet( _f, "string", "primitive" );
413 osrfHashSet( _f,"TEXT", "datatype" );
416 case DBI_TYPE_DATETIME :
417 if( !osrfHashGet( _f, "primitive" ))
418 osrfHashSet( _f, "string", "primitive" );
420 osrfHashSet( _f, "TIMESTAMP", "datatype" );
423 case DBI_TYPE_BINARY :
424 if( !osrfHashGet( _f, "primitive" ))
425 osrfHashSet( _f, "string", "primitive" );
427 osrfHashSet( _f, "BYTEA", "datatype" );
432 "Setting [%s] to primitive [%s] and datatype [%s]...",
434 osrfHashGet( _f, "primitive" ),
435 osrfHashGet( _f, "datatype" )
439 } // end while loop for traversing columns of result
440 dbi_result_free( result );
443 int errnum = dbi_conn_error( handle, &msg );
444 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
445 errnum, msg ? msg : "(No description available)" );
446 // We don't check the database connection here. It's routine to get failures at
447 // this point; we routinely try to query tables that don't exist, because they
448 // are defined in the IDL but not in the database.
450 } // end for each class in IDL
452 buffer_free( query_buf );
453 osrfHashIteratorFree( class_itr );
454 child_initialized = 1;
456 if( !results_found ) {
457 osrfLogError( OSRF_LOG_MARK,
458 "No results found for any class -- bad database connection?" );
460 } else if( ! oilsIsDBConnected( handle )) {
461 osrfLogError( OSRF_LOG_MARK,
462 "Unable to extend IDL: database connection isn't working" );
470 @brief Free an osrfHash that stores a transaction ID.
471 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
473 This function is a callback, to be called by the application session when it ends.
474 The application session stores the osrfHash via an opaque pointer.
476 If the osrfHash contains an entry for the key "xact_id", it means that an
477 uncommitted transaction is pending. Roll it back.
479 void userDataFree( void* blob ) {
480 osrfHash* hash = (osrfHash*) blob;
481 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
482 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
484 int errnum = dbi_conn_error( writehandle, &msg );
485 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
486 errnum, msg ? msg : "(No description available)" );
490 if( !dbi_conn_query( writehandle, "SELECT auditor.clear_audit_info();" ) ) {
492 int errnum = dbi_conn_error( writehandle, &msg );
493 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform audit info clearing: %d %s",
494 errnum, msg ? msg : "(No description available)" );
498 osrfHashFree( hash );
502 @name Managing session data
503 @brief Maintain data stored via the userData pointer of the application session.
505 Currently, session-level data is stored in an osrfHash. Other arrangements are
506 possible, and some would be more efficient. The application session calls a
507 callback function to free userData before terminating.
509 Currently, the only data we store at the session level is the transaction id. By this
510 means we can ensure that any pending transactions are rolled back before the application
516 @brief Free an item in the application session's userData.
517 @param key The name of a key for an osrfHash.
518 @param item An opaque pointer to the item associated with the key.
520 We store an osrfHash as userData with the application session, and arrange (by
521 installing userDataFree() as a different callback) for the session to free that
522 osrfHash before terminating.
524 This function is a callback for freeing items in the osrfHash. Currently we store
526 - Transaction id of a pending transaction; a character string. Key: "xact_id".
527 - Authkey; a character string. Key: "authkey".
528 - User object from the authentication server; a jsonObject. Key: "user_login".
530 If we ever store anything else in userData, we will need to revisit this function so
531 that it will free whatever else needs freeing.
533 static void sessionDataFree( char* key, void* item ) {
534 if( !strcmp( key, "xact_id" ) || !strcmp( key, "authkey" ) || !strncmp( key, "rs_size_", 8) )
536 else if( !strcmp( key, "user_login" ) )
537 jsonObjectFree( (jsonObject*) item );
538 else if( !strcmp( key, "pcache" ) )
539 osrfHashFree( (osrfHash*) item );
542 static void pcacheFree( char* key, void* item ) {
543 osrfStringArrayFree( (osrfStringArray*) item );
547 @brief Initialize session cache.
548 @param ctx Pointer to the method context.
550 Create a cache for the session by making the session's userData member point
551 to an osrfHash instance.
553 static osrfHash* initSessionCache( osrfMethodContext* ctx ) {
554 ctx->session->userData = osrfNewHash();
555 osrfHashSetCallback( (osrfHash*) ctx->session->userData, &sessionDataFree );
556 ctx->session->userDataFree = &userDataFree;
557 return ctx->session->userData;
561 @brief Save a transaction id.
562 @param ctx Pointer to the method context.
564 Save the session_id of the current application session as a transaction id.
566 static void setXactId( osrfMethodContext* ctx ) {
567 if( ctx && ctx->session ) {
568 osrfAppSession* session = ctx->session;
570 osrfHash* cache = session->userData;
572 // If the session doesn't already have a hash, create one. Make sure
573 // that the application session frees the hash when it terminates.
575 cache = initSessionCache( ctx );
577 // Save the transaction id in the hash, with the key "xact_id"
578 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
583 @brief Get the transaction ID for the current transaction, if any.
584 @param ctx Pointer to the method context.
585 @return Pointer to the transaction ID.
587 The return value points to an internal buffer, and will become invalid upon issuing
588 a commit or rollback.
590 static inline const char* getXactId( osrfMethodContext* ctx ) {
591 if( ctx && ctx->session && ctx->session->userData )
592 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
598 @brief Clear the current transaction id.
599 @param ctx Pointer to the method context.
601 static inline void clearXactId( osrfMethodContext* ctx ) {
602 if( ctx && ctx->session && ctx->session->userData )
603 osrfHashRemove( ctx->session->userData, "xact_id" );
608 @brief Stash the location for a particular perm in the sessionData cache
609 @param ctx Pointer to the method context.
610 @param perm Name of the permission we're looking at
611 @param array StringArray of perm location ids
613 static void setPermLocationCache( osrfMethodContext* ctx, const char* perm, osrfStringArray* locations ) {
614 if( ctx && ctx->session ) {
615 osrfAppSession* session = ctx->session;
617 osrfHash* cache = session->userData;
619 // If the session doesn't already have a hash, create one. Make sure
620 // that the application session frees the hash when it terminates.
622 cache = initSessionCache( ctx );
624 osrfHash* pcache = osrfHashGet(cache, "pcache");
626 if( NULL == pcache ) {
627 pcache = osrfNewHash();
628 osrfHashSetCallback( pcache, &pcacheFree );
629 osrfHashSet( cache, pcache, "pcache" );
632 if( perm && locations )
633 osrfHashSet( pcache, locations, strdup(perm) );
638 @brief Grab stashed location for a particular perm in the sessionData cache
639 @param ctx Pointer to the method context.
640 @param perm Name of the permission we're looking at
642 static osrfStringArray* getPermLocationCache( osrfMethodContext* ctx, const char* perm ) {
643 if( ctx && ctx->session ) {
644 osrfAppSession* session = ctx->session;
645 osrfHash* cache = session->userData;
647 osrfHash* pcache = osrfHashGet(cache, "pcache");
649 return osrfHashGet( pcache, perm );
658 @brief Save the user's login in the userData for the current application session.
659 @param ctx Pointer to the method context.
660 @param user_login Pointer to the user login object to be cached (we cache the original,
663 If @a user_login is NULL, remove the user login if one is already cached.
665 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
666 if( ctx && ctx->session ) {
667 osrfAppSession* session = ctx->session;
669 osrfHash* cache = session->userData;
671 // If the session doesn't already have a hash, create one. Make sure
672 // that the application session frees the hash when it terminates.
674 cache = initSessionCache( ctx );
677 osrfHashSet( cache, user_login, "user_login" );
679 osrfHashRemove( cache, "user_login" );
684 @brief Get the user login object for the current application session, if any.
685 @param ctx Pointer to the method context.
686 @return Pointer to the user login object if found; otherwise NULL.
688 The user login object was returned from the authentication server, and then cached so
689 we don't have to call the authentication server again for the same user.
691 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
692 if( ctx && ctx->session && ctx->session->userData )
693 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
699 @brief Save a copy of an authkey in the userData of the current application session.
700 @param ctx Pointer to the method context.
701 @param authkey The authkey to be saved.
703 If @a authkey is NULL, remove the authkey if one is already cached.
705 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
706 if( ctx && ctx->session && authkey ) {
707 osrfAppSession* session = ctx->session;
708 osrfHash* cache = session->userData;
710 // If the session doesn't already have a hash, create one. Make sure
711 // that the application session frees the hash when it terminates.
713 cache = initSessionCache( ctx );
715 // Save the transaction id in the hash, with the key "xact_id"
716 if( authkey && *authkey )
717 osrfHashSet( cache, strdup( authkey ), "authkey" );
719 osrfHashRemove( cache, "authkey" );
724 @brief Reset the login timeout.
725 @param authkey The authentication key for the current login session.
726 @param now The current time.
727 @return Zero if successful, or 1 if not.
729 Tell the authentication server to reset the timeout so that the login session won't
730 expire for a while longer.
732 We could dispense with the @a now parameter by calling time(). But we just called
733 time() in order to decide whether to reset the timeout, so we might as well reuse
734 the result instead of calling time() again.
736 static int reset_timeout( const char* authkey, time_t now ) {
737 jsonObject* auth_object = jsonNewObject( authkey );
739 // Ask the authentication server to reset the timeout. It returns an event
740 // indicating success or failure.
741 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
742 "open-ils.auth.session.reset_timeout", auth_object );
743 jsonObjectFree( auth_object );
745 if( !result || result->type != JSON_HASH ) {
746 osrfLogError( OSRF_LOG_MARK,
747 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
748 jsonObjectFree( result );
749 return 1; // Not the right sort of object returned
752 const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
753 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
754 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
755 jsonObjectFree( result );
756 return 1; // Return code from method not available
759 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
760 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
762 desc = "(No reason available)"; // failsafe; shouldn't happen
763 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
764 jsonObjectFree( result );
768 // Revise our local proxy for the timeout deadline
769 // by a smallish fraction of the timeout interval
770 const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
772 timeout = "1"; // failsafe; shouldn't happen
773 time_next_reset = now + atoi( timeout ) / 15;
775 jsonObjectFree( result );
776 return 0; // Successfully reset timeout
780 @brief Get the authkey string for the current application session, if any.
781 @param ctx Pointer to the method context.
782 @return Pointer to the cached authkey if found; otherwise NULL.
784 If present, the authkey string was cached from a previous method call.
786 static const char* getAuthkey( osrfMethodContext* ctx ) {
787 if( ctx && ctx->session && ctx->session->userData ) {
788 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
789 // LFW recent changes mean the userData hash gets set up earlier, but
790 // doesn't necessarily have an authkey yet
794 // Possibly reset the authentication timeout to keep the login alive. We do so
795 // no more than once per method call, and not at all if it has been only a short
796 // time since the last reset.
798 // Here we reset explicitly, if at all. We also implicitly reset the timeout
799 // whenever we call the "open-ils.auth.session.retrieve" method.
800 if( timeout_needs_resetting ) {
801 time_t now = time( NULL );
802 if( now >= time_next_reset && reset_timeout( authkey, now ) )
803 authkey = NULL; // timeout has apparently expired already
806 timeout_needs_resetting = 0;
814 @brief Implement the transaction.begin method.
815 @param ctx Pointer to the method context.
816 @return Zero if successful, or -1 upon error.
818 Start a transaction. Save a transaction ID for future reference.
821 - authkey (PCRUD only)
823 Return to client: Transaction ID
825 int beginTransaction( osrfMethodContext* ctx ) {
826 if(osrfMethodVerifyContext( ctx )) {
827 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
831 const char* tz = _sanitize_tz_name(ctx->session->session_tz);
833 if( enforce_pcrud ) {
834 timeout_needs_resetting = 1;
835 const jsonObject* user = verifyUserPCRUD( ctx );
840 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
843 int errnum = dbi_conn_error( writehandle, &msg );
844 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
845 modulename, errnum, msg ? msg : "(No description available)" );
846 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
847 "osrfMethodException", ctx->request, "Error starting transaction" );
848 if( !oilsIsDBConnected( writehandle ))
849 osrfAppSessionPanic( ctx->session );
852 dbi_result_free( result );
854 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
855 osrfAppRespondComplete( ctx, ret );
856 jsonObjectFree( ret );
863 dbi_result tz_res = dbi_conn_queryf( writehandle, "SET LOCAL timezone TO '%s'; -- cstore", tz );
865 osrfLogError( OSRF_LOG_MARK, "%s: Error setting timezone %s", modulename, tz);
866 if( !oilsIsDBConnected( writehandle )) {
867 osrfAppSessionPanic( ctx->session );
871 dbi_result_free( tz_res );
876 dbi_result res = dbi_conn_queryf( writehandle, "SET timezone TO DEFAULT; -- no tz" );
878 osrfLogError( OSRF_LOG_MARK, "%s: Error resetting timezone", modulename);
879 if( !oilsIsDBConnected( writehandle )) {
880 osrfAppSessionPanic( ctx->session );
884 dbi_result_free( res );
892 @brief Implement the savepoint.set method.
893 @param ctx Pointer to the method context.
894 @return Zero if successful, or -1 if not.
896 Issue a SAVEPOINT to the database server.
899 - authkey (PCRUD only)
902 Return to client: Savepoint name
904 int setSavepoint( osrfMethodContext* ctx ) {
905 if(osrfMethodVerifyContext( ctx )) {
906 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
911 if( enforce_pcrud ) {
913 timeout_needs_resetting = 1;
914 const jsonObject* user = verifyUserPCRUD( ctx );
919 // Verify that a transaction is pending
920 const char* trans_id = getXactId( ctx );
921 if( NULL == trans_id ) {
922 osrfAppSessionStatus(
924 OSRF_STATUS_INTERNALSERVERERROR,
925 "osrfMethodException",
927 "No active transaction -- required for savepoints"
932 // Get the savepoint name from the method params
933 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
936 osrfLogWarning(OSRF_LOG_MARK, "savepoint.set called with no name");
940 char *safeSpName = _sanitize_savepoint_name( spName );
942 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", safeSpName );
946 int errnum = dbi_conn_error( writehandle, &msg );
949 "%s: Error creating savepoint %s in transaction %s: %d %s",
954 msg ? msg : "(No description available)"
956 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
957 "osrfMethodException", ctx->request, "Error creating savepoint" );
958 if( !oilsIsDBConnected( writehandle ))
959 osrfAppSessionPanic( ctx->session );
962 dbi_result_free( result );
963 jsonObject* ret = jsonNewObject( spName );
964 osrfAppRespondComplete( ctx, ret );
965 jsonObjectFree( ret );
971 @brief Implement the savepoint.release method.
972 @param ctx Pointer to the method context.
973 @return Zero if successful, or -1 if not.
975 Issue a RELEASE SAVEPOINT to the database server.
978 - authkey (PCRUD only)
981 Return to client: Savepoint name
983 int releaseSavepoint( osrfMethodContext* ctx ) {
984 if(osrfMethodVerifyContext( ctx )) {
985 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
990 if( enforce_pcrud ) {
992 timeout_needs_resetting = 1;
993 const jsonObject* user = verifyUserPCRUD( ctx );
998 // Verify that a transaction is pending
999 const char* trans_id = getXactId( ctx );
1000 if( NULL == trans_id ) {
1001 osrfAppSessionStatus(
1003 OSRF_STATUS_INTERNALSERVERERROR,
1004 "osrfMethodException",
1006 "No active transaction -- required for savepoints"
1011 // Get the savepoint name from the method params
1012 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1015 osrfLogWarning(OSRF_LOG_MARK, "savepoint.release called with no name");
1019 char *safeSpName = _sanitize_savepoint_name( spName );
1021 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", safeSpName );
1025 int errnum = dbi_conn_error( writehandle, &msg );
1028 "%s: Error releasing savepoint %s in transaction %s: %d %s",
1033 msg ? msg : "(No description available)"
1035 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1036 "osrfMethodException", ctx->request, "Error releasing savepoint" );
1037 if( !oilsIsDBConnected( writehandle ))
1038 osrfAppSessionPanic( ctx->session );
1041 dbi_result_free( result );
1042 jsonObject* ret = jsonNewObject( spName );
1043 osrfAppRespondComplete( ctx, ret );
1044 jsonObjectFree( ret );
1050 @brief Implement the savepoint.rollback method.
1051 @param ctx Pointer to the method context.
1052 @return Zero if successful, or -1 if not.
1054 Issue a ROLLBACK TO SAVEPOINT to the database server.
1057 - authkey (PCRUD only)
1060 Return to client: Savepoint name
1062 int rollbackSavepoint( osrfMethodContext* ctx ) {
1063 if(osrfMethodVerifyContext( ctx )) {
1064 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1069 if( enforce_pcrud ) {
1071 timeout_needs_resetting = 1;
1072 const jsonObject* user = verifyUserPCRUD( ctx );
1077 // Verify that a transaction is pending
1078 const char* trans_id = getXactId( ctx );
1079 if( NULL == trans_id ) {
1080 osrfAppSessionStatus(
1082 OSRF_STATUS_INTERNALSERVERERROR,
1083 "osrfMethodException",
1085 "No active transaction -- required for savepoints"
1090 // Get the savepoint name from the method params
1091 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1094 osrfLogWarning(OSRF_LOG_MARK, "savepoint.rollback called with no name");
1098 char *safeSpName = _sanitize_savepoint_name( spName );
1100 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", safeSpName );
1104 int errnum = dbi_conn_error( writehandle, &msg );
1107 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
1112 msg ? msg : "(No description available)"
1114 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1115 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1116 if( !oilsIsDBConnected( writehandle ))
1117 osrfAppSessionPanic( ctx->session );
1120 dbi_result_free( result );
1121 jsonObject* ret = jsonNewObject( spName );
1122 osrfAppRespondComplete( ctx, ret );
1123 jsonObjectFree( ret );
1129 @brief Implement the transaction.commit method.
1130 @param ctx Pointer to the method context.
1131 @return Zero if successful, or -1 if not.
1133 Issue a COMMIT to the database server.
1136 - authkey (PCRUD only)
1138 Return to client: Transaction ID.
1140 int commitTransaction( osrfMethodContext* ctx ) {
1141 if(osrfMethodVerifyContext( ctx )) {
1142 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1146 if( enforce_pcrud ) {
1147 timeout_needs_resetting = 1;
1148 const jsonObject* user = verifyUserPCRUD( ctx );
1153 // Verify that a transaction is pending
1154 const char* trans_id = getXactId( ctx );
1155 if( NULL == trans_id ) {
1156 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1157 "osrfMethodException", ctx->request, "No active transaction to commit" );
1161 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1164 int errnum = dbi_conn_error( writehandle, &msg );
1165 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1166 modulename, errnum, msg ? msg : "(No description available)" );
1167 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1168 "osrfMethodException", ctx->request, "Error committing transaction" );
1169 if( !oilsIsDBConnected( writehandle ))
1170 osrfAppSessionPanic( ctx->session );
1173 dbi_result_free( result );
1174 jsonObject* ret = jsonNewObject( trans_id );
1175 osrfAppRespondComplete( ctx, ret );
1176 jsonObjectFree( ret );
1183 @brief Implement the transaction.rollback method.
1184 @param ctx Pointer to the method context.
1185 @return Zero if successful, or -1 if not.
1187 Issue a ROLLBACK to the database server.
1190 - authkey (PCRUD only)
1192 Return to client: Transaction ID
1194 int rollbackTransaction( osrfMethodContext* ctx ) {
1195 if( osrfMethodVerifyContext( ctx )) {
1196 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1200 if( enforce_pcrud ) {
1201 timeout_needs_resetting = 1;
1202 const jsonObject* user = verifyUserPCRUD( ctx );
1207 // Verify that a transaction is pending
1208 const char* trans_id = getXactId( ctx );
1209 if( NULL == trans_id ) {
1210 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1211 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1215 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1218 int errnum = dbi_conn_error( writehandle, &msg );
1219 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1220 modulename, errnum, msg ? msg : "(No description available)" );
1221 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1222 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1223 if( !oilsIsDBConnected( writehandle ))
1224 osrfAppSessionPanic( ctx->session );
1227 dbi_result_free( result );
1228 jsonObject* ret = jsonNewObject( trans_id );
1229 osrfAppRespondComplete( ctx, ret );
1230 jsonObjectFree( ret );
1237 @brief Implement the "search" method.
1238 @param ctx Pointer to the method context.
1239 @return Zero if successful, or -1 if not.
1242 - authkey (PCRUD only)
1243 - WHERE clause, as jsonObject
1244 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1246 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1247 Optionally flesh linked fields.
1249 int doSearch( osrfMethodContext* ctx ) {
1250 if( osrfMethodVerifyContext( ctx )) {
1251 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1256 timeout_needs_resetting = 1;
1258 jsonObject* where_clause;
1259 jsonObject* rest_of_query;
1261 if( enforce_pcrud ) {
1262 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1263 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1265 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1266 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1269 if( !where_clause ) {
1270 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1274 // Get the class metadata
1275 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1276 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1280 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1282 osrfAppRespondComplete( ctx, NULL );
1286 // doFieldmapperSearch() now takes care of our responding for us
1287 // // Return each row to the client
1288 // jsonObject* cur = 0;
1289 // unsigned long res_idx = 0;
1291 // while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1292 // // We used to discard based on perms here, but now that's
1293 // // inside doFieldmapperSearch()
1294 // osrfAppRespond( ctx, cur );
1297 jsonObjectFree( obj );
1299 osrfAppRespondComplete( ctx, NULL );
1304 @brief Implement the "id_list" method.
1305 @param ctx Pointer to the method context.
1306 @param err Pointer through which to return an error code.
1307 @return Zero if successful, or -1 if not.
1310 - authkey (PCRUD only)
1311 - WHERE clause, as jsonObject
1312 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1314 Return to client: The primary key values for all rows of the relevant class that
1315 satisfy a specified WHERE clause.
1317 This method relies on the assumption that every class has a primary key consisting of
1320 int doIdList( osrfMethodContext* ctx ) {
1321 if( osrfMethodVerifyContext( ctx )) {
1322 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1327 timeout_needs_resetting = 1;
1329 jsonObject* where_clause;
1330 jsonObject* rest_of_query;
1332 // We use the where clause without change. But we need to massage the rest of the
1333 // query, so we work with a copy of it instead of modifying the original.
1335 if( enforce_pcrud ) {
1336 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1337 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1339 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1340 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1343 if( !where_clause ) {
1344 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1348 // Eliminate certain SQL clauses, if present.
1349 if( rest_of_query ) {
1350 jsonObjectRemoveKey( rest_of_query, "select" );
1351 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1352 jsonObjectRemoveKey( rest_of_query, "flesh" );
1353 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1355 rest_of_query = jsonNewObjectType( JSON_HASH );
1358 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1360 // Get the class metadata
1361 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1362 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1364 // Build a SELECT list containing just the primary key,
1365 // i.e. like { "classname":["keyname"] }
1366 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1368 // Load array with name of primary key
1369 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1370 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1371 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1373 jsonObjectSetKey( rest_of_query, "select", select_clause );
1378 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1380 jsonObjectFree( rest_of_query );
1382 osrfAppRespondComplete( ctx, NULL );
1386 // Return each primary key value to the client
1388 unsigned long res_idx = 0;
1389 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1390 // We used to discard based on perms here, but now that's
1391 // inside doFieldmapperSearch()
1392 osrfAppRespond( ctx,
1393 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1396 jsonObjectFree( obj );
1397 osrfAppRespondComplete( ctx, NULL );
1402 @brief Verify that we have a valid class reference.
1403 @param ctx Pointer to the method context.
1404 @param param Pointer to the method parameters.
1405 @return 1 if the class reference is valid, or zero if it isn't.
1407 The class of the method params must match the class to which the method id devoted.
1408 For PCRUD there are additional restrictions.
1410 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1412 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1413 osrfHash* class = osrfHashGet( method_meta, "class" );
1415 // Compare the method's class to the parameters' class
1416 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1418 // Oops -- they don't match. Complain.
1419 growing_buffer* msg = buffer_init( 128 );
1422 "%s: %s method for type %s was passed a %s",
1424 osrfHashGet( method_meta, "methodtype" ),
1425 osrfHashGet( class, "classname" ),
1426 param->classname ? param->classname : "(null)"
1429 char* m = buffer_release( msg );
1430 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1438 return verifyObjectPCRUD( ctx, class, param, 1 );
1444 @brief (PCRUD only) Verify that the user is properly logged in.
1445 @param ctx Pointer to the method context.
1446 @return If the user is logged in, a pointer to the user object from the authentication
1447 server; otherwise NULL.
1449 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1450 return verifyUserPCRUDfull( ctx, 0 );
1453 static const jsonObject* verifyUserPCRUDfull( osrfMethodContext* ctx, int anon_ok ) {
1455 // Get the authkey (the first method parameter)
1456 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1458 jsonObject* user = NULL;
1460 // If we are /not/ in anonymous mode
1461 if( strcmp( "ANONYMOUS", auth ) ) {
1462 // See if we have the same authkey, and a user object,
1463 // locally cached from a previous call
1464 const char* cached_authkey = getAuthkey( ctx );
1465 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1466 const jsonObject* cached_user = getUserLogin( ctx );
1471 // We have no matching authentication data in the cache. Authenticate from scratch.
1472 jsonObject* auth_object = jsonNewObject( auth );
1474 // Fetch the user object from the authentication server
1475 user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve", auth_object );
1476 jsonObjectFree( auth_object );
1478 if( !user->classname || strcmp(user->classname, "au" )) {
1480 growing_buffer* msg = buffer_init( 128 );
1483 "%s: permacrud received a bad auth token: %s",
1488 char* m = buffer_release( msg );
1489 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1493 jsonObjectFree( user );
1495 } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
1496 // Failed to set audit information - But note that write_audit_info already set error information.
1497 jsonObjectFree( user );
1502 } else if ( anon_ok ) { // we /are/ (attempting to be) anonymous
1503 user = jsonNewObjectType(JSON_ARRAY);
1504 jsonObjectSetClass( user, "aou" );
1505 oilsFMSetString(user, "id", "-1");
1508 setUserLogin( ctx, user );
1509 setAuthkey( ctx, auth );
1511 // Allow ourselves up to a second before we have to reset the login timeout.
1512 // It would be nice to use some fraction of the timeout interval enforced by the
1513 // authentication server, but that value is not readily available at this point.
1514 // Instead, we use a conservative default interval.
1515 time_next_reset = time( NULL ) + 1;
1521 @brief For PCRUD: Determine whether the current user may access the current row.
1522 @param ctx Pointer to the method context.
1523 @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1524 @param obj Pointer to the row being potentially accessed.
1525 @return 1 if access is permitted, or 0 if it isn't.
1527 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1529 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1531 dbhandle = writehandle;
1533 // Figure out what class and method are involved
1534 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1535 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1538 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1539 if (rs_size_from_hash) {
1540 rs_size = *rs_size_from_hash;
1541 osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1545 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1546 // contexts we will do another lookup of the current row, even if we already have a
1547 // previously fetched row image, because the row image in hand may not include the
1548 // foreign key(s) that we need.
1550 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1551 // but they aren't implemented yet.
1554 if( *method_type == 's' || *method_type == 'i' ) {
1555 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1557 } else if( *method_type == 'u' || *method_type == 'd' ) {
1558 fetch = 1; // MUST go to the db for the object for update and delete
1561 // In retrieve or search ONLY we allow anon. Later perm checks will fail as they should,
1562 // in the face of a fake user but required permissions.
1564 if( *method_type == 'r' )
1567 // Get the appropriate permacrud entry from the IDL, depending on method type
1568 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1570 // No permacrud for this method type on this class
1572 growing_buffer* msg = buffer_init( 128 );
1575 "%s: %s on class %s has no permacrud IDL entry",
1577 osrfHashGet( method_metadata, "methodtype" ),
1578 osrfHashGet( class, "classname" )
1581 char* m = buffer_release( msg );
1582 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1583 "osrfMethodException", ctx->request, m );
1590 // Get the user id, and make sure the user is logged in
1591 const jsonObject* user = verifyUserPCRUDfull( ctx, anon_ok );
1593 return 0; // Not logged in or anon? No access.
1595 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1597 // Get a list of permissions from the permacrud entry.
1598 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1599 if( permission->size == 0 ) {
1602 "No permissions required for this action (class %s), passing through",
1603 osrfHashGet(class, "classname")
1608 // But, if there are perms and the user is anonymous ... FAIL
1612 // Build a list of org units that own the row. This is fairly convoluted because there
1613 // are several different ways that an org unit may own the row, as defined by the
1616 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1617 // identifying an owning org_unit..
1618 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1620 // Foreign context adds a layer of indirection. The row points to some other row that
1621 // an org unit may own. The "jump" attribute, if present, adds another layer of
1623 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1625 // The following string array stores the list of org units. (We don't have a thingie
1626 // for storing lists of integers, so we fake it with a list of strings.)
1627 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1629 const char* context_org = NULL;
1630 const char* pkey = NULL;
1631 jsonObject *param = NULL;
1632 const char* perm = NULL;
1636 const char* pkey_value = NULL;
1637 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1638 // If the global_required attribute is present and true, then the only owning
1639 // org unit is the root org unit, i.e. the one with no parent.
1640 osrfLogDebug( OSRF_LOG_MARK,
1641 "global-level permissions required, fetching top of the org tree" );
1643 // no need to check perms for org tree root retrieval
1644 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1645 // check for perm at top of org tree
1646 const char* org_tree_root_id = org_tree_root( ctx );
1647 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1649 if( org_tree_root_id ) {
1650 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1651 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1653 osrfStringArrayFree( context_org_array );
1658 // If the global_required attribute is absent or false, then we look for
1659 // local and/or foreign context. In order to find the relevant foreign
1660 // keys, we must either read the relevant row from the database, or look at
1661 // the image of the row that we already have in memory.
1663 // Even if we have an image of the row in memory, that image may not include the
1664 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1665 // of the row to make sure that we have what we need.
1667 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1668 "fetching context org ids" );
1670 pkey = osrfHashGet( class, "primarykey" );
1673 // There is no primary key, so we can't do a fresh lookup. Use the row
1674 // image that we already have. If it doesn't have everything we need, too bad.
1676 param = jsonObjectClone( obj );
1677 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1678 } else if( obj->classname ) {
1679 pkey_value = oilsFMGetStringConst( obj, pkey );
1681 param = jsonObjectClone( obj );
1682 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1685 pkey_value = jsonObjectGetString( obj );
1687 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1688 "of %s and retrieving from the database", pkey_value );
1692 // Fetch the row so that we can look at the foreign key(s)
1693 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1694 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1695 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1696 jsonObjectFree( _tmp_params );
1697 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1699 param = jsonObjectExtractIndex( _list, 0 );
1700 jsonObjectFree( _list );
1706 // The row doesn't exist. Complain, and deny access.
1707 osrfLogDebug( OSRF_LOG_MARK,
1708 "Object not found in the database with primary key %s of %s",
1711 growing_buffer* msg = buffer_init( 128 );
1714 "%s: no object found with primary key %s of %s",
1720 char* m = buffer_release( msg );
1721 osrfAppSessionStatus(
1723 OSRF_STATUS_INTERNALSERVERERROR,
1724 "osrfMethodException",
1733 if( local_context && local_context->size > 0 ) {
1734 // The IDL provides a list of column names for the foreign keys denoting
1735 // local context, i.e. columns identifying owing org units directly. Look up
1736 // the value of each one, and if it isn't null, add it to the list of org units.
1737 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1738 local_context->size );
1740 const char* lcontext = NULL;
1741 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1742 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1743 if( fkey_value ) { // if not null
1744 osrfStringArrayAdd( context_org_array, fkey_value );
1747 "adding class-local field %s (value: %s) to the context org list",
1749 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1755 if( foreign_context ) {
1756 unsigned long class_count = osrfHashGetCount( foreign_context );
1757 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1759 if( class_count > 0 ) {
1761 // The IDL provides a list of foreign key columns pointing to rows that
1762 // an org unit may own. Follow each link, identify the owning org unit,
1763 // and add it to the list.
1764 osrfHash* fcontext = NULL;
1765 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1766 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1767 // For each class to which a foreign key points:
1768 const char* class_name = osrfHashIteratorKey( class_itr );
1769 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1773 "%d foreign context fields(s) specified for class %s",
1774 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1778 // Get the name of the key field in the foreign table
1779 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1781 // Get the value of the foreign key pointing to the foreign table
1782 char* foreign_pkey_value =
1783 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1784 if( !foreign_pkey_value )
1785 continue; // Foreign key value is null; skip it
1787 // Look up the row to which the foreign key points
1788 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1790 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1791 jsonObject* _list = doFieldmapperSearch(
1792 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1793 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1795 jsonObject* _fparam = NULL;
1796 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1797 _fparam = jsonObjectExtractIndex( _list, 0 );
1799 jsonObjectFree( _tmp_params );
1800 jsonObjectFree( _list );
1802 // At this point _fparam either points to the row identified by the
1803 // foreign key, or it's NULL (no such row found).
1805 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1807 const char* bad_class = NULL; // For noting failed lookups
1809 bad_class = class_name; // Referenced row not found
1810 else if( jump_list ) {
1811 // Follow a chain of rows, linked by foreign keys, to find an owner
1812 const char* flink = NULL;
1814 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1815 // For each entry in the jump list. Each entry (i.e. flink) is
1816 // the name of a foreign key column in the current row.
1818 // From the IDL, get the linkage information for the next jump
1819 osrfHash* foreign_link_hash =
1820 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1822 // Get the class metadata for the class
1823 // to which the foreign key points
1824 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1825 osrfHashGet( foreign_link_hash, "class" ));
1827 // Get the name of the referenced key of that class
1828 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1830 // Get the value of the foreign key pointing to that class
1831 free( foreign_pkey_value );
1832 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1833 if( !foreign_pkey_value )
1834 break; // Foreign key is null; quit looking
1836 // Build a WHERE clause for the lookup
1837 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1840 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1841 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1842 _tmp_params, NULL, &err );
1843 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1845 // Get the resulting row
1846 jsonObjectFree( _fparam );
1847 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1848 _fparam = jsonObjectExtractIndex( _list, 0 );
1850 // Referenced row not found
1852 bad_class = osrfHashGet( foreign_link_hash, "class" );
1855 jsonObjectFree( _tmp_params );
1856 jsonObjectFree( _list );
1862 // We had a foreign key pointing to such-and-such a row, but then
1863 // we couldn't fetch that row. The data in the database are in an
1864 // inconsistent state; the database itself may even be corrupted.
1865 growing_buffer* msg = buffer_init( 128 );
1868 "%s: no object of class %s found with primary key %s of %s",
1872 foreign_pkey_value ? foreign_pkey_value : "(null)"
1875 char* m = buffer_release( msg );
1876 osrfAppSessionStatus(
1878 OSRF_STATUS_INTERNALSERVERERROR,
1879 "osrfMethodException",
1885 osrfHashIteratorFree( class_itr );
1886 free( foreign_pkey_value );
1887 jsonObjectFree( param );
1892 free( foreign_pkey_value );
1895 // Examine each context column of the foreign row,
1896 // and add its value to the list of org units.
1898 const char* foreign_field = NULL;
1899 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1900 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1901 osrfStringArrayAdd( context_org_array,
1902 oilsFMGetStringConst( _fparam, foreign_field ));
1903 osrfLogDebug( OSRF_LOG_MARK,
1904 "adding foreign class %s field %s (value: %s) "
1905 "to the context org list",
1908 osrfStringArrayGetString(
1909 context_org_array, context_org_array->size - 1 )
1913 jsonObjectFree( _fparam );
1917 osrfHashIteratorFree( class_itr );
1922 // If there is an owning_user attached to the action, we allow that user and users with
1923 // object perms on the object. CREATE can't use this. We only do this when we're not
1924 // ignoring object perms.
1925 char* owning_user_field = osrfHashGet( pcrud, "owning_user" );
1927 *method_type != 'c' &&
1928 (!str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) || // Always honor
1931 if (owning_user_field) { // see if we can short-cut by comparing the owner to the requestor
1933 if (!param) { // We didn't get it during the context lookup
1934 pkey = osrfHashGet( class, "primarykey" );
1937 // There is no primary key, so we can't do a fresh lookup. Use the row
1938 // image that we already have. If it doesn't have everything we need, too bad.
1940 param = jsonObjectClone( obj );
1941 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1942 } else if( obj->classname ) {
1943 pkey_value = oilsFMGetStringConst( obj, pkey );
1945 param = jsonObjectClone( obj );
1946 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1949 pkey_value = jsonObjectGetString( obj );
1951 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1952 "of %s and retrieving from the database", pkey_value );
1956 // Fetch the row so that we can look at the foreign key(s)
1957 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1958 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1959 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1960 jsonObjectFree( _tmp_params );
1961 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1963 param = jsonObjectExtractIndex( _list, 0 );
1964 jsonObjectFree( _list );
1969 // The row doesn't exist. Complain, and deny access.
1970 osrfLogDebug( OSRF_LOG_MARK,
1971 "Object not found in the database with primary key %s of %s",
1974 growing_buffer* msg = buffer_init( 128 );
1977 "%s: no object found with primary key %s of %s",
1983 char* m = buffer_release( msg );
1984 osrfAppSessionStatus(
1986 OSRF_STATUS_INTERNALSERVERERROR,
1987 "osrfMethodException",
1996 int ownerid = atoi( oilsFMGetStringConst( param, owning_user_field ) );
1998 // Allow the owner to do whatever
1999 if (ownerid == userid)
2006 (perm = osrfStringArrayGetString(permission, i++)) &&
2007 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms"))
2013 "Checking object permission [%s] for user %d "
2014 "on object %s (class %s)",
2018 osrfHashGet( class, "classname" )
2021 result = dbi_conn_queryf(
2023 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s') AS has_perm;",
2026 osrfHashGet( class, "classname" ),
2033 "Received a result for object permission [%s] "
2034 "for user %d on object %s (class %s)",
2038 osrfHashGet( class, "classname" )
2041 if( dbi_result_first_row( result )) {
2042 jsonObject* return_val = oilsMakeJSONFromResult( result );
2043 const char* has_perm = jsonObjectGetString(
2044 jsonObjectGetKeyConst( return_val, "has_perm" ));
2048 "Status of object permission [%s] for user %d "
2049 "on object %s (class %s) is %s",
2053 osrfHashGet(class, "classname"),
2057 if( *has_perm == 't' )
2059 jsonObjectFree( return_val );
2062 dbi_result_free( result );
2067 int errnum = dbi_conn_error( writehandle, &msg );
2068 osrfLogWarning( OSRF_LOG_MARK,
2069 "Unable to call check object permissions: %d, %s",
2070 errnum, msg ? msg : "(No description available)" );
2071 if( !oilsIsDBConnected( writehandle ))
2072 osrfAppSessionPanic( ctx->session );
2077 // For every combination of permission and context org unit: call a stored procedure
2078 // to determine if the user has this permission in the context of this org unit.
2079 // If the answer is yes at any point, then we're done, and the user has permission.
2080 // In other words permissions are additive.
2082 while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
2085 osrfStringArray* pcache = NULL;
2086 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
2087 pcache = getPermLocationCache(ctx, perm);
2090 pcache = osrfNewStringArray(0);
2092 result = dbi_conn_queryf(
2094 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
2102 "Received a result for permission [%s] for user %d",
2107 if( dbi_result_first_row( result )) {
2109 jsonObject* return_val = oilsMakeJSONFromResult( result );
2110 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
2111 jsonObjectFree( return_val );
2112 } while( dbi_result_next_row( result ));
2114 setPermLocationCache(ctx, perm, pcache);
2117 dbi_result_free( result );
2123 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
2125 if (rs_size > perm_at_threshold) {
2126 if (osrfStringArrayContains( pcache, context_org )) {
2134 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
2136 !str_is_true( osrfHashGet(pcrud, "global_required") ) ||
2137 osrfHashGet(pcrud, "owning_user")
2142 "Checking object permission [%s] for user %d "
2143 "on object %s (class %s) at org %d",
2147 osrfHashGet( class, "classname" ),
2151 result = dbi_conn_queryf(
2153 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
2156 osrfHashGet( class, "classname" ),
2164 "Received a result for object permission [%s] "
2165 "for user %d on object %s (class %s) at org %d",
2169 osrfHashGet( class, "classname" ),
2173 if( dbi_result_first_row( result )) {
2174 jsonObject* return_val = oilsMakeJSONFromResult( result );
2175 const char* has_perm = jsonObjectGetString(
2176 jsonObjectGetKeyConst( return_val, "has_perm" ));
2180 "Status of object permission [%s] for user %d "
2181 "on object %s (class %s) at org %d is %s",
2185 osrfHashGet(class, "classname"),
2190 if( *has_perm == 't' )
2192 jsonObjectFree( return_val );
2195 dbi_result_free( result );
2200 int errnum = dbi_conn_error( writehandle, &msg );
2201 osrfLogWarning( OSRF_LOG_MARK,
2202 "Unable to call check object permissions: %d, %s",
2203 errnum, msg ? msg : "(No description available)" );
2204 if( !oilsIsDBConnected( writehandle ))
2205 osrfAppSessionPanic( ctx->session );
2209 if (rs_size > perm_at_threshold) break;
2211 osrfLogDebug( OSRF_LOG_MARK,
2212 "Checking non-object permission [%s] for user %d at org %d",
2213 perm, userid, atoi(context_org) );
2214 result = dbi_conn_queryf(
2216 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
2223 osrfLogDebug( OSRF_LOG_MARK,
2224 "Received a result for permission [%s] for user %d at org %d",
2225 perm, userid, atoi( context_org ));
2226 if( dbi_result_first_row( result )) {
2227 jsonObject* return_val = oilsMakeJSONFromResult( result );
2228 const char* has_perm = jsonObjectGetString(
2229 jsonObjectGetKeyConst( return_val, "has_perm" ));
2230 osrfLogDebug( OSRF_LOG_MARK,
2231 "Status of permission [%s] for user %d at org %d is [%s]",
2232 perm, userid, atoi( context_org ), has_perm );
2233 if( *has_perm == 't' )
2235 jsonObjectFree( return_val );
2238 dbi_result_free( result );
2243 int errnum = dbi_conn_error( writehandle, &msg );
2244 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
2245 errnum, msg ? msg : "(No description available)" );
2246 if( !oilsIsDBConnected( writehandle ))
2247 osrfAppSessionPanic( ctx->session );
2256 osrfStringArrayFree( context_org_array );
2262 @brief Look up the root of the org_unit tree.
2263 @param ctx Pointer to the method context.
2264 @return The id of the root org unit, as a character string.
2266 Query actor.org_unit where parent_ou is null, and return the id as a string.
2268 This function assumes that there is only one root org unit, i.e. that we
2269 have a single tree, not a forest.
2271 The calling code is responsible for freeing the returned string.
2273 static const char* org_tree_root( osrfMethodContext* ctx ) {
2275 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
2276 static time_t last_lookup_time = 0;
2277 time_t current_time = time( NULL );
2279 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2280 // We successfully looked this up less than an hour ago.
2281 // It's not likely to have changed since then.
2282 return strdup( cached_root_id );
2284 last_lookup_time = current_time;
2287 jsonObject* where_clause = single_hash( "parent_ou", NULL );
2288 jsonObject* result = doFieldmapperSearch(
2289 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2290 jsonObjectFree( where_clause );
2292 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2295 jsonObjectFree( result );
2297 growing_buffer* msg = buffer_init( 128 );
2298 OSRF_BUFFER_ADD( msg, modulename );
2299 OSRF_BUFFER_ADD( msg,
2300 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2302 char* m = buffer_release( msg );
2303 osrfAppSessionStatus( ctx->session,
2304 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2307 cached_root_id[ 0 ] = '\0';
2311 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2312 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2314 strcpy( cached_root_id, root_org_unit_id );
2315 jsonObjectFree( result );
2316 return cached_root_id;
2320 @brief Create a JSON_HASH with a single key/value pair.
2321 @param key The key of the key/value pair.
2322 @param value the value of the key/value pair.
2323 @return Pointer to a newly created jsonObject of type JSON_HASH.
2325 The value of the key/value is either a string or (if @a value is NULL) a null.
2327 static jsonObject* single_hash( const char* key, const char* value ) {
2329 if( ! key ) key = "";
2331 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2332 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2337 int doCreate( osrfMethodContext* ctx ) {
2338 if(osrfMethodVerifyContext( ctx )) {
2339 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2344 timeout_needs_resetting = 1;
2346 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2347 jsonObject* target = NULL;
2348 jsonObject* options = NULL;
2350 if( enforce_pcrud ) {
2351 target = jsonObjectGetIndex( ctx->params, 1 );
2352 options = jsonObjectGetIndex( ctx->params, 2 );
2354 target = jsonObjectGetIndex( ctx->params, 0 );
2355 options = jsonObjectGetIndex( ctx->params, 1 );
2358 if( !verifyObjectClass( ctx, target )) {
2359 osrfAppRespondComplete( ctx, NULL );
2363 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2365 const char* trans_id = getXactId( ctx );
2367 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2369 osrfAppSessionStatus(
2371 OSRF_STATUS_BADREQUEST,
2372 "osrfMethodException",
2374 "No active transaction -- required for CREATE"
2376 osrfAppRespondComplete( ctx, NULL );
2380 // The following test is harmless but redundant. If a class is
2381 // readonly, we don't register a create method for it.
2382 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2383 osrfAppSessionStatus(
2385 OSRF_STATUS_BADREQUEST,
2386 "osrfMethodException",
2388 "Cannot INSERT readonly class"
2390 osrfAppRespondComplete( ctx, NULL );
2394 // Set the last_xact_id
2395 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2397 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2398 trans_id, target->classname, index);
2399 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2402 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2404 dbhandle = writehandle;
2406 osrfHash* fields = osrfHashGet( meta, "fields" );
2407 char* pkey = osrfHashGet( meta, "primarykey" );
2408 char* seq = osrfHashGet( meta, "sequence" );
2410 growing_buffer* table_buf = buffer_init( 128 );
2411 growing_buffer* col_buf = buffer_init( 128 );
2412 growing_buffer* val_buf = buffer_init( 128 );
2414 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2415 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2416 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2417 buffer_add( val_buf,"VALUES (" );
2421 osrfHash* field = NULL;
2422 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2423 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2425 const char* field_name = osrfHashIteratorKey( field_itr );
2427 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2430 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2433 if( field_object && field_object->classname ) {
2434 value = oilsFMGetString(
2436 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2438 } else if( field_object && JSON_BOOL == field_object->type ) {
2439 if( jsonBoolIsTrue( field_object ) )
2440 value = strdup( "t" );
2442 value = strdup( "f" );
2444 value = jsonObjectToSimpleString( field_object );
2450 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2451 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2454 buffer_add( col_buf, field_name );
2456 if( !field_object || field_object->type == JSON_NULL ) {
2457 buffer_add( val_buf, "DEFAULT" );
2459 } else if( !strcmp( get_primitive( field ), "number" )) {
2460 const char* numtype = get_datatype( field );
2461 if( !strcmp( numtype, "INT8" )) {
2462 buffer_fadd( val_buf, "%lld", atoll( value ));
2464 } else if( !strcmp( numtype, "INT" )) {
2465 buffer_fadd( val_buf, "%d", atoi( value ));
2467 } else if( !strcmp( numtype, "NUMERIC" )) {
2468 buffer_fadd( val_buf, "%f", atof( value ));
2471 if( dbi_conn_quote_string( writehandle, &value )) {
2472 OSRF_BUFFER_ADD( val_buf, value );
2475 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2476 osrfAppSessionStatus(
2478 OSRF_STATUS_INTERNALSERVERERROR,
2479 "osrfMethodException",
2481 "Error quoting string -- please see the error log for more details"
2484 buffer_free( table_buf );
2485 buffer_free( col_buf );
2486 buffer_free( val_buf );
2487 osrfAppRespondComplete( ctx, NULL );
2495 osrfHashIteratorFree( field_itr );
2497 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2498 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2500 char* table_str = buffer_release( table_buf );
2501 char* col_str = buffer_release( col_buf );
2502 char* val_str = buffer_release( val_buf );
2503 growing_buffer* sql = buffer_init( 128 );
2504 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2509 char* query = buffer_release( sql );
2511 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2513 jsonObject* obj = NULL;
2516 dbi_result result = dbi_conn_query( writehandle, query );
2518 obj = jsonNewObject( NULL );
2520 int errnum = dbi_conn_error( writehandle, &msg );
2523 "%s ERROR inserting %s object using query [%s]: %d %s",
2525 osrfHashGet(meta, "fieldmapper"),
2528 msg ? msg : "(No description available)"
2530 osrfAppSessionStatus(
2532 OSRF_STATUS_INTERNALSERVERERROR,
2533 "osrfMethodException",
2535 "INSERT error -- please see the error log for more details"
2537 if( !oilsIsDBConnected( writehandle ))
2538 osrfAppSessionPanic( ctx->session );
2541 dbi_result_free( result );
2543 char* id = oilsFMGetString( target, pkey );
2545 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2546 growing_buffer* _id = buffer_init( 10 );
2547 buffer_fadd( _id, "%lld", new_id );
2548 id = buffer_release( _id );
2551 // Find quietness specification, if present
2552 const char* quiet_str = NULL;
2554 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2556 quiet_str = jsonObjectGetString( quiet_obj );
2559 if( str_is_true( quiet_str )) { // if quietness is specified
2560 obj = jsonNewObject( id );
2564 // Fetch the row that we just inserted, so that we can return it to the client
2565 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2566 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2569 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2573 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2575 jsonObjectFree( list );
2576 jsonObjectFree( where_clause );
2583 osrfAppRespondComplete( ctx, obj );
2584 jsonObjectFree( obj );
2589 @brief Implement the retrieve method.
2590 @param ctx Pointer to the method context.
2591 @param err Pointer through which to return an error code.
2592 @return If successful, a pointer to the result to be returned to the client;
2595 From the method's class, fetch a row with a specified value in the primary key. This
2596 method relies on the database design convention that a primary key consists of a single
2600 - authkey (PCRUD only)
2601 - value of the primary key for the desired row, for building the WHERE clause
2602 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2604 Return to client: One row from the query.
2606 int doRetrieve( osrfMethodContext* ctx ) {
2607 if(osrfMethodVerifyContext( ctx )) {
2608 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2613 timeout_needs_resetting = 1;
2618 if( enforce_pcrud ) {
2623 // Get the class metadata
2624 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2626 // Get the value of the primary key, from a method parameter
2627 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2631 "%s retrieving %s object with primary key value of %s",
2633 osrfHashGet( class_def, "fieldmapper" ),
2634 jsonObjectGetString( id_obj )
2637 // Build a WHERE clause based on the key value
2638 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2641 osrfHashGet( class_def, "primarykey" ), // name of key column
2642 jsonObjectClone( id_obj ) // value of key column
2645 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2649 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2651 jsonObjectFree( where_clause );
2653 osrfAppRespondComplete( ctx, NULL );
2657 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2658 jsonObjectFree( list );
2660 if( enforce_pcrud ) {
2661 // no result, skip this entirely
2662 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2663 jsonObjectFree( obj );
2665 growing_buffer* msg = buffer_init( 128 );
2666 OSRF_BUFFER_ADD( msg, modulename );
2667 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2669 char* m = buffer_release( msg );
2670 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2674 osrfAppRespondComplete( ctx, NULL );
2679 // doFieldmapperSearch() now does the responding for us
2680 //osrfAppRespondComplete( ctx, obj );
2681 osrfAppRespondComplete( ctx, NULL );
2683 jsonObjectFree( obj );
2688 @brief Translate a numeric value to a string representation for the database.
2689 @param field Pointer to the IDL field definition.
2690 @param value Pointer to a jsonObject holding the value of a field.
2691 @return Pointer to a newly allocated string.
2693 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2694 its contents are numeric. A non-numeric string is likely to result in invalid SQL.
2696 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2698 The calling code is responsible for freeing the resulting string by calling free().
2700 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2701 growing_buffer* val_buf = buffer_init( 32 );
2703 // If the value is a number and the DB field is numeric, no quotes needed
2704 if( value->type == JSON_NUMBER && !strcmp( get_primitive( field ), "number") ) {
2705 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2707 // Presumably this was really intended to be a string, so quote it
2708 char* str = jsonObjectToSimpleString( value );
2709 if( dbi_conn_quote_string( dbhandle, &str )) {
2710 OSRF_BUFFER_ADD( val_buf, str );
2713 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2715 buffer_free( val_buf );
2720 return buffer_release( val_buf );
2723 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2724 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2725 growing_buffer* sql_buf = buffer_init( 32 );
2731 osrfHashGet( field, "name" )
2735 buffer_add( sql_buf, "IN (" );
2736 } else if( !strcasecmp( op,"not in" )) {
2737 buffer_add( sql_buf, "NOT IN (" );
2739 buffer_add( sql_buf, "IN (" );
2742 if( node->type == JSON_HASH ) {
2743 // subquery predicate
2744 char* subpred = buildQuery( ctx, node, SUBSELECT );
2746 buffer_free( sql_buf );
2750 buffer_add( sql_buf, subpred );
2753 } else if( node->type == JSON_ARRAY ) {
2754 // literal value list
2755 int in_item_index = 0;
2756 int in_item_first = 1;
2757 const jsonObject* in_item;
2758 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2763 buffer_add( sql_buf, ", " );
2766 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2767 osrfLogError( OSRF_LOG_MARK,
2768 "%s: Expected string or number within IN list; found %s",
2769 modulename, json_type( in_item->type ) );
2770 buffer_free( sql_buf );
2774 // Append the literal value -- quoted if not a number
2775 if( JSON_NUMBER == in_item->type ) {
2776 char* val = jsonNumberToDBString( field, in_item );
2777 OSRF_BUFFER_ADD( sql_buf, val );
2780 } else if( !strcmp( get_primitive( field ), "number" )) {
2781 char* val = jsonNumberToDBString( field, in_item );
2782 OSRF_BUFFER_ADD( sql_buf, val );
2786 char* key_string = jsonObjectToSimpleString( in_item );
2787 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2788 OSRF_BUFFER_ADD( sql_buf, key_string );
2791 osrfLogError( OSRF_LOG_MARK,
2792 "%s: Error quoting key string [%s]", modulename, key_string );
2794 buffer_free( sql_buf );
2800 if( in_item_first ) {
2801 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2802 buffer_free( sql_buf );
2806 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2807 modulename, json_type( node->type ));
2808 buffer_free( sql_buf );
2812 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2814 return buffer_release( sql_buf );
2817 // Receive a JSON_ARRAY representing a function call. The first
2818 // entry in the array is the function name. The rest are parameters.
2819 static char* searchValueTransform( const jsonObject* array ) {
2821 if( array->size < 1 ) {
2822 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2826 // Get the function name
2827 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2828 if( func_item->type != JSON_STRING ) {
2829 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2830 modulename, json_type( func_item->type ));
2834 growing_buffer* sql_buf = buffer_init( 32 );
2836 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2837 OSRF_BUFFER_ADD( sql_buf, "( " );
2839 // Get the parameters
2840 int func_item_index = 1; // We already grabbed the zeroth entry
2841 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2843 // Add a separator comma, if we need one
2844 if( func_item_index > 2 )
2845 buffer_add( sql_buf, ", " );
2847 // Add the current parameter
2848 if( func_item->type == JSON_NULL ) {
2849 buffer_add( sql_buf, "NULL" );
2851 if( func_item->type == JSON_BOOL ) {
2852 if( jsonBoolIsTrue(func_item) ) {
2853 buffer_add( sql_buf, "TRUE" );
2855 buffer_add( sql_buf, "FALSE" );
2858 char* val = jsonObjectToSimpleString( func_item );
2859 if( dbi_conn_quote_string( dbhandle, &val )) {
2860 OSRF_BUFFER_ADD( sql_buf, val );
2863 osrfLogError( OSRF_LOG_MARK,
2864 "%s: Error quoting key string [%s]", modulename, val );
2865 buffer_free( sql_buf );
2873 buffer_add( sql_buf, " )" );
2875 return buffer_release( sql_buf );
2878 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2879 const jsonObject* node, const char* op ) {
2881 if( ! is_good_operator( op ) ) {
2882 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2886 char* val = searchValueTransform( node );
2890 const char* right_percent = "";
2891 const char* real_op = op;
2893 if( !strcasecmp( op, "startwith") ) {
2895 right_percent = "|| '%'";
2898 growing_buffer* sql_buf = buffer_init( 32 );
2901 "\"%s\".%s %s %s%s",
2903 osrfHashGet( field, "name" ),
2911 return buffer_release( sql_buf );
2914 // class_alias is a class name or other table alias
2915 // field is a field definition as stored in the IDL
2916 // node comes from the method parameter, and may represent an entry in the SELECT list
2917 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2918 const jsonObject* node ) {
2919 growing_buffer* sql_buf = buffer_init( 32 );
2921 const char* field_transform = jsonObjectGetString(
2922 jsonObjectGetKeyConst( node, "transform" ) );
2923 const char* transform_subcolumn = jsonObjectGetString(
2924 jsonObjectGetKeyConst( node, "result_field" ) );
2926 if( transform_subcolumn ) {
2927 if( ! is_identifier( transform_subcolumn ) ) {
2928 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2929 modulename, transform_subcolumn );
2930 buffer_free( sql_buf );
2933 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2936 if( field_transform ) {
2938 if( ! is_identifier( field_transform ) ) {
2939 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2940 modulename, field_transform );
2941 buffer_free( sql_buf );
2945 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2946 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2947 field_transform, class_alias, osrfHashGet( field, "name" ));
2949 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2950 field_transform, class_alias, osrfHashGet( field, "name" ));
2953 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2956 if( array->type != JSON_ARRAY ) {
2957 osrfLogError( OSRF_LOG_MARK,
2958 "%s: Expected JSON_ARRAY for function params; found %s",
2959 modulename, json_type( array->type ) );
2960 buffer_free( sql_buf );
2963 int func_item_index = 0;
2964 jsonObject* func_item;
2965 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2967 char* val = jsonObjectToSimpleString( func_item );
2970 buffer_add( sql_buf, ",NULL" );
2971 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2972 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2973 OSRF_BUFFER_ADD( sql_buf, val );
2975 osrfLogError( OSRF_LOG_MARK,
2976 "%s: Error quoting key string [%s]", modulename, val );
2978 buffer_free( sql_buf );
2985 buffer_add( sql_buf, " )" );
2988 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2991 if( transform_subcolumn )
2992 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2994 return buffer_release( sql_buf );
2997 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2998 const jsonObject* node, const char* op ) {
3000 if( ! is_good_operator( op ) ) {
3001 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
3005 char* field_transform = searchFieldTransform( class_info->alias, field, node );
3006 if( ! field_transform )
3009 int extra_parens = 0; // boolean
3011 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
3013 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
3015 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
3017 free( field_transform );
3021 } else if( value_obj->type == JSON_ARRAY ) {
3022 value = searchValueTransform( value_obj );
3024 osrfLogError( OSRF_LOG_MARK,
3025 "%s: Error building value transform for field transform", modulename );
3026 free( field_transform );
3029 } else if( value_obj->type == JSON_HASH ) {
3030 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
3032 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
3034 free( field_transform );
3038 } else if( value_obj->type == JSON_NUMBER ) {
3039 value = jsonNumberToDBString( field, value_obj );
3040 } else if( value_obj->type == JSON_NULL ) {
3041 osrfLogError( OSRF_LOG_MARK,
3042 "%s: Error building predicate for field transform: null value", modulename );
3043 free( field_transform );
3045 } else if( value_obj->type == JSON_BOOL ) {
3046 osrfLogError( OSRF_LOG_MARK,
3047 "%s: Error building predicate for field transform: boolean value", modulename );
3048 free( field_transform );
3051 if( !strcmp( get_primitive( field ), "number") ) {
3052 value = jsonNumberToDBString( field, value_obj );
3054 value = jsonObjectToSimpleString( value_obj );
3055 if( !dbi_conn_quote_string( dbhandle, &value )) {
3056 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3057 modulename, value );
3059 free( field_transform );
3065 const char* left_parens = "";
3066 const char* right_parens = "";
3068 if( extra_parens ) {
3073 const char* right_percent = "";
3074 const char* real_op = op;
3076 if( !strcasecmp( op, "startwith") ) {
3078 right_percent = "|| '%'";
3081 growing_buffer* sql_buf = buffer_init( 32 );
3085 "%s%s %s %s %s%s %s%s",
3097 free( field_transform );
3099 return buffer_release( sql_buf );
3102 static char* searchSimplePredicate( const char* op, const char* class_alias,
3103 osrfHash* field, const jsonObject* node ) {
3105 if( ! is_good_operator( op ) ) {
3106 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
3112 // Get the value to which we are comparing the specified column
3113 if( node->type != JSON_NULL ) {
3114 if( node->type == JSON_NUMBER ) {
3115 val = jsonNumberToDBString( field, node );
3116 } else if( !strcmp( get_primitive( field ), "number" ) ) {
3117 val = jsonNumberToDBString( field, node );
3119 val = jsonObjectToSimpleString( node );
3124 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
3125 // Value is not numeric; enclose it in quotes
3126 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
3127 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
3134 // Compare to a null value
3135 val = strdup( "NULL" );
3136 if( strcmp( op, "=" ))
3142 const char* right_percent = "";
3143 const char* real_op = op;
3145 if( !strcasecmp( op, "startwith") ) {
3147 right_percent = "|| '%'";
3150 growing_buffer* sql_buf = buffer_init( 32 );
3151 buffer_fadd( sql_buf, "\"%s\".%s %s %s%s", class_alias, osrfHashGet(field, "name"), real_op, val, right_percent );
3152 char* pred = buffer_release( sql_buf );
3159 static char* searchBETWEENPredicate( const char* class_alias,
3160 osrfHash* field, const jsonObject* node ) {
3162 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
3163 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
3165 if( NULL == y_node ) {
3166 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
3169 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
3170 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
3177 if( !strcmp( get_primitive( field ), "number") ) {
3178 x_string = jsonNumberToDBString( field, x_node );
3179 y_string = jsonNumberToDBString( field, y_node );
3182 x_string = jsonObjectToSimpleString( x_node );
3183 y_string = jsonObjectToSimpleString( y_node );
3184 if( !(dbi_conn_quote_string( dbhandle, &x_string )
3185 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
3186 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
3187 modulename, x_string, y_string );
3194 growing_buffer* sql_buf = buffer_init( 32 );
3195 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
3196 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
3200 return buffer_release( sql_buf );
3203 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
3204 jsonObject* node, osrfMethodContext* ctx ) {
3207 if( node->type == JSON_ARRAY ) { // equality IN search
3208 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
3209 } else if( node->type == JSON_HASH ) { // other search
3210 jsonIterator* pred_itr = jsonNewIterator( node );
3211 if( !jsonIteratorHasNext( pred_itr ) ) {
3212 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
3213 modulename, osrfHashGet(field, "name" ));
3215 jsonObject* pred_node = jsonIteratorNext( pred_itr );
3217 // Verify that there are no additional predicates
3218 if( jsonIteratorHasNext( pred_itr ) ) {
3219 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
3220 modulename, osrfHashGet(field, "name" ));
3221 } else if( !(strcasecmp( pred_itr->key,"between" )) )
3222 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
3223 else if( !(strcasecmp( pred_itr->key,"in" ))
3224 || !(strcasecmp( pred_itr->key,"not in" )) )
3225 pred = searchINPredicate(
3226 class_info->alias, field, pred_node, pred_itr->key, ctx );
3227 else if( pred_node->type == JSON_ARRAY )
3228 pred = searchFunctionPredicate(
3229 class_info->alias, field, pred_node, pred_itr->key );
3230 else if( pred_node->type == JSON_HASH )
3231 pred = searchFieldTransformPredicate(
3232 class_info, field, pred_node, pred_itr->key );
3234 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
3236 jsonIteratorFree( pred_itr );
3238 } else if( node->type == JSON_NULL ) { // IS NULL search
3239 growing_buffer* _p = buffer_init( 64 );
3242 "\"%s\".%s IS NULL",
3244 osrfHashGet( field, "name" )
3246 pred = buffer_release( _p );
3247 } else { // equality search
3248 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3267 field : call_number,
3283 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3285 const jsonObject* working_hash;
3286 jsonObject* freeable_hash = NULL;
3288 if( join_hash->type == JSON_HASH ) {
3289 working_hash = join_hash;
3290 } else if( join_hash->type == JSON_STRING ) {
3291 // turn it into a JSON_HASH by creating a wrapper
3292 // around a copy of the original
3293 const char* _tmp = jsonObjectGetString( join_hash );
3294 freeable_hash = jsonNewObjectType( JSON_HASH );
3295 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3296 working_hash = freeable_hash;
3300 "%s: JOIN failed; expected JSON object type not found",
3306 growing_buffer* join_buf = buffer_init( 128 );
3307 const char* leftclass = left_info->class_name;
3309 jsonObject* snode = NULL;
3310 jsonIterator* search_itr = jsonNewIterator( working_hash );
3312 while ( (snode = jsonIteratorNext( search_itr )) ) {
3313 const char* right_alias = search_itr->key;
3315 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3317 class = right_alias;
3319 const ClassInfo* right_info = add_joined_class( right_alias, class );
3323 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3327 jsonIteratorFree( search_itr );
3328 buffer_free( join_buf );
3330 jsonObjectFree( freeable_hash );
3333 osrfHash* links = right_info->links;
3334 const char* table = right_info->source_def;
3336 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3337 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3339 if( field && !fkey ) {
3340 // Look up the corresponding join column in the IDL.
3341 // The link must be defined in the child table,
3342 // and point to the right parent table.
3343 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3344 const char* reltype = NULL;
3345 const char* other_class = NULL;
3346 reltype = osrfHashGet( idl_link, "reltype" );
3347 if( reltype && strcmp( reltype, "has_many" ) )
3348 other_class = osrfHashGet( idl_link, "class" );
3349 if( other_class && !strcmp( other_class, leftclass ) )
3350 fkey = osrfHashGet( idl_link, "key" );
3354 "%s: JOIN failed. No link defined from %s.%s to %s",
3360 buffer_free( join_buf );
3362 jsonObjectFree( freeable_hash );
3363 jsonIteratorFree( search_itr );
3367 } else if( !field && fkey ) {
3368 // Look up the corresponding join column in the IDL.
3369 // The link must be defined in the child table,
3370 // and point to the right parent table.
3371 osrfHash* left_links = left_info->links;
3372 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3373 const char* reltype = NULL;
3374 const char* other_class = NULL;
3375 reltype = osrfHashGet( idl_link, "reltype" );
3376 if( reltype && strcmp( reltype, "has_many" ) )
3377 other_class = osrfHashGet( idl_link, "class" );
3378 if( other_class && !strcmp( other_class, class ) )
3379 field = osrfHashGet( idl_link, "key" );
3383 "%s: JOIN failed. No link defined from %s.%s to %s",
3389 buffer_free( join_buf );
3391 jsonObjectFree( freeable_hash );
3392 jsonIteratorFree( search_itr );
3396 } else if( !field && !fkey ) {
3397 osrfHash* left_links = left_info->links;
3399 // For each link defined for the left class:
3400 // see if the link references the joined class
3401 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3402 osrfHash* curr_link = NULL;
3403 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3404 const char* other_class = osrfHashGet( curr_link, "class" );
3405 if( other_class && !strcmp( other_class, class ) ) {
3407 // In the IDL, the parent class doesn't always know then names of the child
3408 // columns that are pointing to it, so don't use that end of the link
3409 const char* reltype = osrfHashGet( curr_link, "reltype" );
3410 if( reltype && strcmp( reltype, "has_many" ) ) {
3411 // Found a link between the classes
3412 fkey = osrfHashIteratorKey( itr );
3413 field = osrfHashGet( curr_link, "key" );
3418 osrfHashIteratorFree( itr );
3420 if( !field || !fkey ) {
3421 // Do another such search, with the classes reversed
3423 // For each link defined for the joined class:
3424 // see if the link references the left class
3425 osrfHashIterator* itr = osrfNewHashIterator( links );
3426 osrfHash* curr_link = NULL;
3427 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3428 const char* other_class = osrfHashGet( curr_link, "class" );
3429 if( other_class && !strcmp( other_class, leftclass ) ) {
3431 // In the IDL, the parent class doesn't know then names of the child
3432 // columns that are pointing to it, so don't use that end of the link
3433 const char* reltype = osrfHashGet( curr_link, "reltype" );
3434 if( reltype && strcmp( reltype, "has_many" ) ) {
3435 // Found a link between the classes
3436 field = osrfHashIteratorKey( itr );
3437 fkey = osrfHashGet( curr_link, "key" );
3442 osrfHashIteratorFree( itr );
3445 if( !field || !fkey ) {
3448 "%s: JOIN failed. No link defined between %s and %s",
3453 buffer_free( join_buf );
3455 jsonObjectFree( freeable_hash );
3456 jsonIteratorFree( search_itr );
3461 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3463 if( !strcasecmp( type,"left" )) {
3464 buffer_add( join_buf, " LEFT JOIN" );
3465 } else if( !strcasecmp( type,"right" )) {
3466 buffer_add( join_buf, " RIGHT JOIN" );
3467 } else if( !strcasecmp( type,"full" )) {
3468 buffer_add( join_buf, " FULL JOIN" );
3470 buffer_add( join_buf, " INNER JOIN" );
3473 buffer_add( join_buf, " INNER JOIN" );
3476 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3477 table, right_alias, right_alias, field, left_info->alias, fkey );
3479 // Add any other join conditions as specified by "filter"
3480 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3482 const char* filter_op = jsonObjectGetString(
3483 jsonObjectGetKeyConst( snode, "filter_op" ) );
3484 if( filter_op && !strcasecmp( "or",filter_op )) {
3485 buffer_add( join_buf, " OR " );
3487 buffer_add( join_buf, " AND " );
3490 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3492 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3493 OSRF_BUFFER_ADD( join_buf, jpred );
3498 "%s: JOIN failed. Invalid conditional expression.",
3501 jsonIteratorFree( search_itr );
3502 buffer_free( join_buf );
3504 jsonObjectFree( freeable_hash );
3509 buffer_add( join_buf, " ) " );
3511 // Recursively add a nested join, if one is present
3512 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3514 char* jpred = searchJOIN( join_filter, right_info );
3516 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3517 OSRF_BUFFER_ADD( join_buf, jpred );
3520 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3521 jsonIteratorFree( search_itr );
3522 buffer_free( join_buf );
3524 jsonObjectFree( freeable_hash );
3531 jsonObjectFree( freeable_hash );
3532 jsonIteratorFree( search_itr );
3534 return buffer_release( join_buf );
3539 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3540 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3541 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3543 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3545 search_hash is the JSON expression of the conditions.
3546 meta is the class definition from the IDL, for the relevant table.
3547 opjoin_type indicates whether multiple conditions, if present, should be
3548 connected by AND or OR.
3549 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3550 to pass it to other functions -- and all they do with it is to use the session
3551 and request members to send error messages back to the client.
3555 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3556 int opjoin_type, osrfMethodContext* ctx ) {
3560 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3561 "opjoin_type = %d, ctx addr = %p",
3564 class_info->class_def,
3569 growing_buffer* sql_buf = buffer_init( 128 );
3571 jsonObject* node = NULL;
3574 if( search_hash->type == JSON_ARRAY ) {
3575 if( 0 == search_hash->size ) {
3578 "%s: Invalid predicate structure: empty JSON array",
3581 buffer_free( sql_buf );
3585 unsigned long i = 0;
3586 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3590 if( opjoin_type == OR_OP_JOIN )
3591 buffer_add( sql_buf, " OR " );
3593 buffer_add( sql_buf, " AND " );
3596 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3598 buffer_free( sql_buf );
3602 buffer_fadd( sql_buf, "( %s )", subpred );
3606 } else if( search_hash->type == JSON_HASH ) {
3607 osrfLogDebug( OSRF_LOG_MARK,
3608 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3609 jsonIterator* search_itr = jsonNewIterator( search_hash );
3610 if( !jsonIteratorHasNext( search_itr ) ) {
3613 "%s: Invalid predicate structure: empty JSON object",
3616 jsonIteratorFree( search_itr );
3617 buffer_free( sql_buf );
3621 while( (node = jsonIteratorNext( search_itr )) ) {
3626 if( opjoin_type == OR_OP_JOIN )
3627 buffer_add( sql_buf, " OR " );
3629 buffer_add( sql_buf, " AND " );
3632 if( '+' == search_itr->key[ 0 ] ) {
3634 // This plus sign prefixes a class name or other table alias;
3635 // make sure the table alias is in scope
3636 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3637 if( ! alias_info ) {
3640 "%s: Invalid table alias \"%s\" in WHERE clause",
3644 jsonIteratorFree( search_itr );
3645 buffer_free( sql_buf );
3649 if( node->type == JSON_STRING ) {
3650 // It's the name of a column; make sure it belongs to the class
3651 const char* fieldname = jsonObjectGetString( node );
3652 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3655 "%s: Invalid column name \"%s\" in WHERE clause "
3656 "for table alias \"%s\"",
3661 jsonIteratorFree( search_itr );
3662 buffer_free( sql_buf );
3666 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3668 // It's something more complicated
3669 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3671 jsonIteratorFree( search_itr );
3672 buffer_free( sql_buf );
3676 buffer_fadd( sql_buf, "( %s )", subpred );
3679 } else if( '-' == search_itr->key[ 0 ] ) {
3680 if( !strcasecmp( "-or", search_itr->key )) {
3681 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3683 jsonIteratorFree( search_itr );
3684 buffer_free( sql_buf );
3688 buffer_fadd( sql_buf, "( %s )", subpred );
3690 } else if( !strcasecmp( "-and", search_itr->key )) {
3691 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3693 jsonIteratorFree( search_itr );
3694 buffer_free( sql_buf );
3698 buffer_fadd( sql_buf, "( %s )", subpred );
3700 } else if( !strcasecmp("-not",search_itr->key) ) {
3701 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3703 jsonIteratorFree( search_itr );
3704 buffer_free( sql_buf );
3708 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3710 } else if( !strcasecmp( "-exists", search_itr->key )) {
3711 char* subpred = buildQuery( ctx, node, SUBSELECT );
3713 jsonIteratorFree( search_itr );
3714 buffer_free( sql_buf );
3718 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3720 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3721 char* subpred = buildQuery( ctx, node, SUBSELECT );
3723 jsonIteratorFree( search_itr );
3724 buffer_free( sql_buf );
3728 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3730 } else { // Invalid "minus" operator
3733 "%s: Invalid operator \"%s\" in WHERE clause",
3737 jsonIteratorFree( search_itr );
3738 buffer_free( sql_buf );
3744 const char* class = class_info->class_name;
3745 osrfHash* fields = class_info->fields;
3746 osrfHash* field = osrfHashGet( fields, search_itr->key );
3749 const char* table = class_info->source_def;
3752 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3755 table ? table : "?",
3758 jsonIteratorFree( search_itr );
3759 buffer_free( sql_buf );
3763 char* subpred = searchPredicate( class_info, field, node, ctx );
3765 buffer_free( sql_buf );
3766 jsonIteratorFree( search_itr );
3770 buffer_add( sql_buf, subpred );
3774 jsonIteratorFree( search_itr );
3777 // ERROR ... only hash and array allowed at this level
3778 char* predicate_string = jsonObjectToJSON( search_hash );
3781 "%s: Invalid predicate structure: %s",
3785 buffer_free( sql_buf );
3786 free( predicate_string );
3790 return buffer_release( sql_buf );
3793 /* Build a JSON_ARRAY of field names for a given table alias
3795 static jsonObject* defaultSelectList( const char* table_alias ) {
3800 ClassInfo* class_info = search_all_alias( table_alias );
3801 if( ! class_info ) {
3804 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3811 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3812 osrfHash* field_def = NULL;
3813 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3814 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3815 const char* field_name = osrfHashIteratorKey( field_itr );
3816 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3817 jsonObjectPush( array, jsonNewObject( field_name ) );
3820 osrfHashIteratorFree( field_itr );
3825 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3826 // The jsonObject must be a JSON_HASH with an single entry for "union",
3827 // "intersect", or "except". The data associated with this key must be an
3828 // array of hashes, each hash being a query.
3829 // Also allowed but currently ignored: entries for "order_by" and "alias".
3830 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3832 if( ! combo || combo->type != JSON_HASH )
3833 return NULL; // should be impossible; validated by caller
3835 const jsonObject* query_array = NULL; // array of subordinate queries
3836 const char* op = NULL; // name of operator, e.g. UNION
3837 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3838 int op_count = 0; // for detecting conflicting operators
3839 int excepting = 0; // boolean
3840 int all = 0; // boolean
3841 jsonObject* order_obj = NULL;
3843 // Identify the elements in the hash
3844 jsonIterator* query_itr = jsonNewIterator( combo );
3845 jsonObject* curr_obj = NULL;
3846 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3847 if( ! strcmp( "union", query_itr->key ) ) {
3850 query_array = curr_obj;
3851 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3854 query_array = curr_obj;
3855 } else if( ! strcmp( "except", query_itr->key ) ) {
3859 query_array = curr_obj;
3860 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3863 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3866 order_obj = curr_obj;
3867 } else if( ! strcmp( "alias", query_itr->key ) ) {
3868 if( curr_obj->type != JSON_STRING ) {
3869 jsonIteratorFree( query_itr );
3872 alias = jsonObjectGetString( curr_obj );
3873 } else if( ! strcmp( "all", query_itr->key ) ) {
3874 if( obj_is_true( curr_obj ) )
3878 osrfAppSessionStatus(
3880 OSRF_STATUS_INTERNALSERVERERROR,
3881 "osrfMethodException",
3883 "Malformed query; unexpected entry in query object"
3887 "%s: Unexpected entry for \"%s\" in%squery",
3892 jsonIteratorFree( query_itr );
3896 jsonIteratorFree( query_itr );
3898 // More sanity checks
3899 if( ! query_array ) {
3901 osrfAppSessionStatus(
3903 OSRF_STATUS_INTERNALSERVERERROR,
3904 "osrfMethodException",
3906 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3910 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3913 return NULL; // should be impossible...
3914 } else if( op_count > 1 ) {
3916 osrfAppSessionStatus(
3918 OSRF_STATUS_INTERNALSERVERERROR,
3919 "osrfMethodException",
3921 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3925 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3929 } if( query_array->type != JSON_ARRAY ) {
3931 osrfAppSessionStatus(
3933 OSRF_STATUS_INTERNALSERVERERROR,
3934 "osrfMethodException",
3936 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3940 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3943 json_type( query_array->type )
3946 } if( query_array->size < 2 ) {
3948 osrfAppSessionStatus(
3950 OSRF_STATUS_INTERNALSERVERERROR,
3951 "osrfMethodException",
3953 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3957 "%s:%srequires multiple queries as operands",
3962 } else if( excepting && query_array->size > 2 ) {
3964 osrfAppSessionStatus(
3966 OSRF_STATUS_INTERNALSERVERERROR,
3967 "osrfMethodException",
3969 "EXCEPT operator has too many queries as operands"
3973 "%s:EXCEPT operator has too many queries as operands",
3977 } else if( order_obj && ! alias ) {
3979 osrfAppSessionStatus(
3981 OSRF_STATUS_INTERNALSERVERERROR,
3982 "osrfMethodException",
3984 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3988 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3994 // So far so good. Now build the SQL.
3995 growing_buffer* sql = buffer_init( 256 );
3997 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3998 // Add a layer of parentheses
3999 if( flags & SUBCOMBO )
4000 OSRF_BUFFER_ADD( sql, "( " );
4002 // Traverse the query array. Each entry should be a hash.
4003 int first = 1; // boolean
4005 jsonObject* query = NULL;
4006 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
4007 if( query->type != JSON_HASH ) {
4009 osrfAppSessionStatus(
4011 OSRF_STATUS_INTERNALSERVERERROR,
4012 "osrfMethodException",
4014 "Malformed query under UNION, INTERSECT or EXCEPT"
4018 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
4021 json_type( query->type )
4030 OSRF_BUFFER_ADD( sql, op );
4032 OSRF_BUFFER_ADD( sql, "ALL " );
4035 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
4039 "%s: Error building query under%s",
4047 OSRF_BUFFER_ADD( sql, query_str );
4050 if( flags & SUBCOMBO )
4051 OSRF_BUFFER_ADD_CHAR( sql, ')' );
4053 if( !(flags & SUBSELECT) )
4054 OSRF_BUFFER_ADD_CHAR( sql, ';' );
4056 return buffer_release( sql );
4059 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
4060 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
4061 // or "except" to indicate the type of query.
4062 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
4066 osrfAppSessionStatus(
4068 OSRF_STATUS_INTERNALSERVERERROR,
4069 "osrfMethodException",
4071 "Malformed query; no query object"
4073 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
4075 } else if( query->type != JSON_HASH ) {
4077 osrfAppSessionStatus(
4079 OSRF_STATUS_INTERNALSERVERERROR,
4080 "osrfMethodException",
4082 "Malformed query object"
4086 "%s: Query object is %s instead of JSON_HASH",
4088 json_type( query->type )
4093 // Determine what kind of query it purports to be, and dispatch accordingly.
4094 if( jsonObjectGetKeyConst( query, "union" ) ||
4095 jsonObjectGetKeyConst( query, "intersect" ) ||
4096 jsonObjectGetKeyConst( query, "except" )) {
4097 return doCombo( ctx, query, flags );
4099 // It is presumably a SELECT query
4101 // Push a node onto the stack for the current query. Every level of
4102 // subquery gets its own QueryFrame on the Stack.
4105 // Build an SQL SELECT statement
4108 jsonObjectGetKey( query, "select" ),
4109 jsonObjectGetKeyConst( query, "from" ),
4110 jsonObjectGetKeyConst( query, "where" ),
4111 jsonObjectGetKeyConst( query, "having" ),
4112 jsonObjectGetKeyConst( query, "order_by" ),
4113 jsonObjectGetKeyConst( query, "limit" ),
4114 jsonObjectGetKeyConst( query, "offset" ),
4123 /* method context */ osrfMethodContext* ctx,
4125 /* SELECT */ jsonObject* selhash,
4126 /* FROM */ const jsonObject* join_hash,
4127 /* WHERE */ const jsonObject* search_hash,
4128 /* HAVING */ const jsonObject* having_hash,
4129 /* ORDER BY */ const jsonObject* order_hash,
4130 /* LIMIT */ const jsonObject* limit,
4131 /* OFFSET */ const jsonObject* offset,
4132 /* flags */ int flags
4134 const char* locale = osrf_message_get_last_locale();
4136 // general tmp objects
4137 const jsonObject* tmp_const;
4138 jsonObject* selclass = NULL;
4139 jsonObject* snode = NULL;
4140 jsonObject* onode = NULL;
4142 char* string = NULL;
4143 int from_function = 0;
4148 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
4150 // punt if there's no FROM clause
4151 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
4154 "%s: FROM clause is missing or empty",
4158 osrfAppSessionStatus(
4160 OSRF_STATUS_INTERNALSERVERERROR,
4161 "osrfMethodException",
4163 "FROM clause is missing or empty in JSON query"
4168 // the core search class
4169 const char* core_class = NULL;
4171 // get the core class -- the only key of the top level FROM clause, or a string
4172 if( join_hash->type == JSON_HASH ) {
4173 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
4174 snode = jsonIteratorNext( tmp_itr );
4176 // Populate the current QueryFrame with information
4177 // about the core class
4178 if( add_query_core( NULL, tmp_itr->key ) ) {
4180 osrfAppSessionStatus(
4182 OSRF_STATUS_INTERNALSERVERERROR,
4183 "osrfMethodException",
4185 "Unable to look up core class"
4189 core_class = curr_query->core.class_name;
4192 jsonObject* extra = jsonIteratorNext( tmp_itr );
4194 jsonIteratorFree( tmp_itr );
4197 // There shouldn't be more than one entry in join_hash
4201 "%s: Malformed FROM clause: extra entry in JSON_HASH",
4205 osrfAppSessionStatus(
4207 OSRF_STATUS_INTERNALSERVERERROR,
4208 "osrfMethodException",
4210 "Malformed FROM clause in JSON query"
4212 return NULL; // Malformed join_hash; extra entry
4214 } else if( join_hash->type == JSON_ARRAY ) {
4215 // We're selecting from a function, not from a table
4217 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
4220 } else if( join_hash->type == JSON_STRING ) {
4221 // Populate the current QueryFrame with information
4222 // about the core class
4223 core_class = jsonObjectGetString( join_hash );
4225 if( add_query_core( NULL, core_class ) ) {
4227 osrfAppSessionStatus(
4229 OSRF_STATUS_INTERNALSERVERERROR,
4230 "osrfMethodException",
4232 "Unable to look up core class"
4240 "%s: FROM clause is unexpected JSON type: %s",
4242 json_type( join_hash->type )
4245 osrfAppSessionStatus(
4247 OSRF_STATUS_INTERNALSERVERERROR,
4248 "osrfMethodException",
4250 "Ill-formed FROM clause in JSON query"
4255 // Build the join clause, if any, while filling out the list
4256 // of joined classes in the current QueryFrame.
4257 char* join_clause = NULL;
4258 if( join_hash && ! from_function ) {
4260 join_clause = searchJOIN( join_hash, &curr_query->core );
4261 if( ! join_clause ) {
4263 osrfAppSessionStatus(
4265 OSRF_STATUS_INTERNALSERVERERROR,
4266 "osrfMethodException",
4268 "Unable to construct JOIN clause(s)"
4274 // For in case we don't get a select list
4275 jsonObject* defaultselhash = NULL;
4277 // if there is no select list, build a default select list ...
4278 if( !selhash && !from_function ) {
4279 jsonObject* default_list = defaultSelectList( core_class );
4280 if( ! default_list ) {
4282 osrfAppSessionStatus(
4284 OSRF_STATUS_INTERNALSERVERERROR,
4285 "osrfMethodException",
4287 "Unable to build default SELECT clause in JSON query"
4289 free( join_clause );
4294 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4295 jsonObjectSetKey( selhash, core_class, default_list );
4298 // The SELECT clause can be encoded only by a hash
4299 if( !from_function && selhash->type != JSON_HASH ) {
4302 "%s: Expected JSON_HASH for SELECT clause; found %s",
4304 json_type( selhash->type )
4308 osrfAppSessionStatus(
4310 OSRF_STATUS_INTERNALSERVERERROR,
4311 "osrfMethodException",
4313 "Malformed SELECT clause in JSON query"
4315 free( join_clause );
4319 // If you see a null or wild card specifier for the core class, or an
4320 // empty array, replace it with a default SELECT list
4321 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4323 int default_needed = 0; // boolean
4324 if( JSON_STRING == tmp_const->type
4325 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4327 else if( JSON_NULL == tmp_const->type )
4330 if( default_needed ) {
4331 // Build a default SELECT list
4332 jsonObject* default_list = defaultSelectList( core_class );
4333 if( ! default_list ) {
4335 osrfAppSessionStatus(
4337 OSRF_STATUS_INTERNALSERVERERROR,
4338 "osrfMethodException",
4340 "Can't build default SELECT clause in JSON query"
4342 free( join_clause );
4347 jsonObjectSetKey( selhash, core_class, default_list );
4351 // temp buffers for the SELECT list and GROUP BY clause
4352 growing_buffer* select_buf = buffer_init( 128 );
4353 growing_buffer* group_buf = buffer_init( 128 );
4355 int aggregate_found = 0; // boolean
4357 // Build a select list
4358 if( from_function ) // From a function we select everything
4359 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4362 // Build the SELECT list as SQL
4366 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4367 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4369 const char* cname = selclass_itr->key;
4371 // Make sure the target relation is in the FROM clause.
4373 // At this point join_hash is a step down from the join_hash we
4374 // received as a parameter. If the original was a JSON_STRING,
4375 // then json_hash is now NULL. If the original was a JSON_HASH,
4376 // then json_hash is now the first (and only) entry in it,
4377 // denoting the core class. We've already excluded the
4378 // possibility that the original was a JSON_ARRAY, because in
4379 // that case from_function would be non-NULL, and we wouldn't
4382 // If the current table alias isn't in scope, bail out
4383 ClassInfo* class_info = search_alias( cname );
4384 if( ! class_info ) {
4387 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4392 osrfAppSessionStatus(
4394 OSRF_STATUS_INTERNALSERVERERROR,
4395 "osrfMethodException",
4397 "Selected class not in FROM clause in JSON query"
4399 jsonIteratorFree( selclass_itr );
4400 buffer_free( select_buf );
4401 buffer_free( group_buf );
4402 if( defaultselhash )
4403 jsonObjectFree( defaultselhash );
4404 free( join_clause );
4408 if( selclass->type != JSON_ARRAY ) {
4411 "%s: Malformed SELECT list for class \"%s\"; not an array",
4416 osrfAppSessionStatus(
4418 OSRF_STATUS_INTERNALSERVERERROR,
4419 "osrfMethodException",
4421 "Selected class not in FROM clause in JSON query"
4424 jsonIteratorFree( selclass_itr );
4425 buffer_free( select_buf );
4426 buffer_free( group_buf );
4427 if( defaultselhash )
4428 jsonObjectFree( defaultselhash );
4429 free( join_clause );
4433 // Look up some attributes of the current class
4434 osrfHash* idlClass = class_info->class_def;
4435 osrfHash* class_field_set = class_info->fields;
4436 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4437 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4439 if( 0 == selclass->size ) {
4442 "%s: No columns selected from \"%s\"",
4448 // stitch together the column list for the current table alias...
4449 unsigned long field_idx = 0;
4450 jsonObject* selfield = NULL;
4451 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4453 // If we need a separator comma, add one
4457 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4460 // if the field specification is a string, add it to the list
4461 if( selfield->type == JSON_STRING ) {
4463 // Look up the field in the IDL
4464 const char* col_name = jsonObjectGetString( selfield );
4465 osrfHash* field_def = NULL;
4467 if (!osrfStringArrayContains(
4469 osrfHashGet( class_field_set, col_name ),
4470 "suppress_controller"),
4473 field_def = osrfHashGet( class_field_set, col_name );
4476 // No such field in current class
4479 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4485 osrfAppSessionStatus(
4487 OSRF_STATUS_INTERNALSERVERERROR,
4488 "osrfMethodException",
4490 "Selected column not defined in JSON query"
4492 jsonIteratorFree( selclass_itr );
4493 buffer_free( select_buf );
4494 buffer_free( group_buf );
4495 if( defaultselhash )
4496 jsonObjectFree( defaultselhash );
4497 free( join_clause );
4499 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4500 // Virtual field not allowed
4503 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4509 osrfAppSessionStatus(
4511 OSRF_STATUS_INTERNALSERVERERROR,
4512 "osrfMethodException",
4514 "Selected column may not be virtual in JSON query"
4516 jsonIteratorFree( selclass_itr );
4517 buffer_free( select_buf );
4518 buffer_free( group_buf );
4519 if( defaultselhash )
4520 jsonObjectFree( defaultselhash );
4521 free( join_clause );
4527 if( flags & DISABLE_I18N )
4530 i18n = osrfHashGet( field_def, "i18n" );
4532 if( str_is_true( i18n ) ) {
4533 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4534 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4535 class_tname, cname, col_name, class_pkey,
4536 cname, class_pkey, locale, col_name );
4538 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4539 cname, col_name, col_name );
4542 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4543 cname, col_name, col_name );
4546 // ... but it could be an object, in which case we check for a Field Transform
4547 } else if( selfield->type == JSON_HASH ) {
4549 const char* col_name = jsonObjectGetString(
4550 jsonObjectGetKeyConst( selfield, "column" ) );
4552 // Get the field definition from the IDL
4553 osrfHash* field_def = NULL;
4554 if (!osrfStringArrayContains(
4556 osrfHashGet( class_field_set, col_name ),
4557 "suppress_controller"),
4560 field_def = osrfHashGet( class_field_set, col_name );
4564 // No such field in current class
4567 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4573 osrfAppSessionStatus(
4575 OSRF_STATUS_INTERNALSERVERERROR,
4576 "osrfMethodException",
4578 "Selected column is not defined in JSON query"
4580 jsonIteratorFree( selclass_itr );
4581 buffer_free( select_buf );
4582 buffer_free( group_buf );
4583 if( defaultselhash )
4584 jsonObjectFree( defaultselhash );
4585 free( join_clause );
4587 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4588 // No such field in current class
4591 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4597 osrfAppSessionStatus(
4599 OSRF_STATUS_INTERNALSERVERERROR,
4600 "osrfMethodException",
4602 "Selected column is virtual in JSON query"
4604 jsonIteratorFree( selclass_itr );
4605 buffer_free( select_buf );
4606 buffer_free( group_buf );
4607 if( defaultselhash )
4608 jsonObjectFree( defaultselhash );
4609 free( join_clause );
4613 // Decide what to use as a column alias
4615 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4616 _alias = jsonObjectGetString( tmp_const );
4617 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4618 _alias = jsonObjectGetString( tmp_const );
4619 } else { // Use field name as the alias
4623 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4624 char* transform_str = searchFieldTransform(
4625 class_info->alias, field_def, selfield );
4626 if( transform_str ) {
4627 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4628 free( transform_str );
4631 osrfAppSessionStatus(
4633 OSRF_STATUS_INTERNALSERVERERROR,
4634 "osrfMethodException",
4636 "Unable to generate transform function in JSON query"
4638 jsonIteratorFree( selclass_itr );
4639 buffer_free( select_buf );
4640 buffer_free( group_buf );
4641 if( defaultselhash )
4642 jsonObjectFree( defaultselhash );
4643 free( join_clause );
4650 if( flags & DISABLE_I18N )
4653 i18n = osrfHashGet( field_def, "i18n" );
4655 if( str_is_true( i18n ) ) {
4656 buffer_fadd( select_buf,
4657 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4658 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4659 class_tname, cname, col_name, class_pkey, cname,
4660 class_pkey, locale, _alias );
4662 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4663 cname, col_name, _alias );
4666 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4667 cname, col_name, _alias );
4674 "%s: Selected item is unexpected JSON type: %s",
4676 json_type( selfield->type )
4679 osrfAppSessionStatus(
4681 OSRF_STATUS_INTERNALSERVERERROR,
4682 "osrfMethodException",
4684 "Ill-formed SELECT item in JSON query"
4686 jsonIteratorFree( selclass_itr );
4687 buffer_free( select_buf );
4688 buffer_free( group_buf );
4689 if( defaultselhash )
4690 jsonObjectFree( defaultselhash );
4691 free( join_clause );
4695 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4696 if( obj_is_true( agg_obj ) )
4697 aggregate_found = 1;
4699 // Append a comma (except for the first one)
4700 // and add the column to a GROUP BY clause
4704 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4706 buffer_fadd( group_buf, " %d", sel_pos );
4710 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4712 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( selfield, "aggregate");
4713 if ( ! obj_is_true( aggregate_obj ) ) {
4717 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4720 buffer_fadd(group_buf, " %d", sel_pos);
4723 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4727 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4730 _column = searchFieldTransform(class_info->alias, field, selfield);
4731 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4732 OSRF_BUFFER_ADD(group_buf, _column);
4733 _column = searchFieldTransform(class_info->alias, field, selfield);
4740 } // end while -- iterating across SELECT columns
4742 } // end while -- iterating across classes
4744 jsonIteratorFree( selclass_itr );
4747 char* col_list = buffer_release( select_buf );
4749 // Make sure the SELECT list isn't empty. This can happen, for example,
4750 // if we try to build a default SELECT clause from a non-core table.
4753 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4755 osrfAppSessionStatus(
4757 OSRF_STATUS_INTERNALSERVERERROR,
4758 "osrfMethodException",
4760 "SELECT list is empty"
4763 buffer_free( group_buf );
4764 if( defaultselhash )
4765 jsonObjectFree( defaultselhash );
4766 free( join_clause );
4772 table = searchValueTransform( join_hash );
4774 table = strdup( curr_query->core.source_def );
4778 osrfAppSessionStatus(
4780 OSRF_STATUS_INTERNALSERVERERROR,
4781 "osrfMethodException",
4783 "Unable to identify table for core class"
4786 buffer_free( group_buf );
4787 if( defaultselhash )
4788 jsonObjectFree( defaultselhash );
4789 free( join_clause );
4793 // Put it all together
4794 growing_buffer* sql_buf = buffer_init( 128 );
4795 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4799 // Append the join clause, if any
4801 buffer_add(sql_buf, join_clause );
4802 free( join_clause );
4805 char* order_by_list = NULL;
4806 char* having_buf = NULL;
4808 if( !from_function ) {
4810 // Build a WHERE clause, if there is one
4812 buffer_add( sql_buf, " WHERE " );
4814 // and it's on the WHERE clause
4815 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4818 osrfAppSessionStatus(
4820 OSRF_STATUS_INTERNALSERVERERROR,
4821 "osrfMethodException",
4823 "Severe query error in WHERE predicate -- see error log for more details"
4826 buffer_free( group_buf );
4827 buffer_free( sql_buf );
4828 if( defaultselhash )
4829 jsonObjectFree( defaultselhash );
4833 buffer_add( sql_buf, pred );
4837 // Build a HAVING clause, if there is one
4840 // and it's on the the WHERE clause
4841 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4843 if( ! having_buf ) {
4845 osrfAppSessionStatus(
4847 OSRF_STATUS_INTERNALSERVERERROR,
4848 "osrfMethodException",
4850 "Severe query error in HAVING predicate -- see error log for more details"
4853 buffer_free( group_buf );
4854 buffer_free( sql_buf );
4855 if( defaultselhash )
4856 jsonObjectFree( defaultselhash );
4861 // Build an ORDER BY clause, if there is one
4862 if( NULL == order_hash )
4863 ; // No ORDER BY? do nothing
4864 else if( JSON_ARRAY == order_hash->type ) {
4865 order_by_list = buildOrderByFromArray( ctx, order_hash );
4866 if( !order_by_list ) {
4868 buffer_free( group_buf );
4869 buffer_free( sql_buf );
4870 if( defaultselhash )
4871 jsonObjectFree( defaultselhash );
4874 } else if( JSON_HASH == order_hash->type ) {
4875 // This hash is keyed on class alias. Each class has either
4876 // an array of field names or a hash keyed on field name.
4877 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4878 jsonIterator* class_itr = jsonNewIterator( order_hash );
4879 while( (snode = jsonIteratorNext( class_itr )) ) {
4881 ClassInfo* order_class_info = search_alias( class_itr->key );
4882 if( ! order_class_info ) {
4883 osrfLogError( OSRF_LOG_MARK,
4884 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4885 modulename, class_itr->key );
4887 osrfAppSessionStatus(
4889 OSRF_STATUS_INTERNALSERVERERROR,
4890 "osrfMethodException",
4892 "Invalid class referenced in ORDER BY clause -- "
4893 "see error log for more details"
4895 jsonIteratorFree( class_itr );
4896 buffer_free( order_buf );
4898 buffer_free( group_buf );
4899 buffer_free( sql_buf );
4900 if( defaultselhash )
4901 jsonObjectFree( defaultselhash );
4905 osrfHash* field_list_def = order_class_info->fields;
4907 if( snode->type == JSON_HASH ) {
4909 // Hash is keyed on field names from the current class. For each field
4910 // there is another layer of hash to define the sorting details, if any,
4911 // or a string to indicate direction of sorting.
4912 jsonIterator* order_itr = jsonNewIterator( snode );
4913 while( (onode = jsonIteratorNext( order_itr )) ) {
4915 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4917 osrfLogError( OSRF_LOG_MARK,
4918 "%s: Invalid field \"%s\" in ORDER BY clause",
4919 modulename, order_itr->key );
4921 osrfAppSessionStatus(
4923 OSRF_STATUS_INTERNALSERVERERROR,
4924 "osrfMethodException",
4926 "Invalid field in ORDER BY clause -- "
4927 "see error log for more details"
4929 jsonIteratorFree( order_itr );
4930 jsonIteratorFree( class_itr );
4931 buffer_free( order_buf );
4933 buffer_free( group_buf );
4934 buffer_free( sql_buf );
4935 if( defaultselhash )
4936 jsonObjectFree( defaultselhash );
4938 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4939 osrfLogError( OSRF_LOG_MARK,
4940 "%s: Virtual field \"%s\" in ORDER BY clause",
4941 modulename, order_itr->key );
4943 osrfAppSessionStatus(
4945 OSRF_STATUS_INTERNALSERVERERROR,
4946 "osrfMethodException",
4948 "Virtual field in ORDER BY clause -- "
4949 "see error log for more details"
4951 jsonIteratorFree( order_itr );
4952 jsonIteratorFree( class_itr );
4953 buffer_free( order_buf );
4955 buffer_free( group_buf );
4956 buffer_free( sql_buf );
4957 if( defaultselhash )
4958 jsonObjectFree( defaultselhash );
4962 const char* direction = NULL;
4963 if( onode->type == JSON_HASH ) {
4964 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4965 string = searchFieldTransform(
4967 osrfHashGet( field_list_def, order_itr->key ),
4971 if( ctx ) osrfAppSessionStatus(
4973 OSRF_STATUS_INTERNALSERVERERROR,
4974 "osrfMethodException",
4976 "Severe query error in ORDER BY clause -- "
4977 "see error log for more details"
4979 jsonIteratorFree( order_itr );
4980 jsonIteratorFree( class_itr );
4982 buffer_free( group_buf );
4983 buffer_free( order_buf);
4984 buffer_free( sql_buf );
4985 if( defaultselhash )
4986 jsonObjectFree( defaultselhash );
4990 growing_buffer* field_buf = buffer_init( 16 );
4991 buffer_fadd( field_buf, "\"%s\".%s",
4992 class_itr->key, order_itr->key );
4993 string = buffer_release( field_buf );
4996 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4997 const char* dir = jsonObjectGetString( tmp_const );
4998 if(!strncasecmp( dir, "d", 1 )) {
4999 direction = " DESC";
5005 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
5006 osrfLogError( OSRF_LOG_MARK,
5007 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
5008 modulename, json_type( onode->type ) );
5010 osrfAppSessionStatus(
5012 OSRF_STATUS_INTERNALSERVERERROR,
5013 "osrfMethodException",
5015 "Malformed ORDER BY clause -- see error log for more details"
5017 jsonIteratorFree( order_itr );
5018 jsonIteratorFree( class_itr );
5020 buffer_free( group_buf );
5021 buffer_free( order_buf );
5022 buffer_free( sql_buf );
5023 if( defaultselhash )
5024 jsonObjectFree( defaultselhash );
5028 string = strdup( order_itr->key );
5029 const char* dir = jsonObjectGetString( onode );
5030 if( !strncasecmp( dir, "d", 1 )) {
5031 direction = " DESC";
5038 OSRF_BUFFER_ADD( order_buf, ", " );
5040 order_buf = buffer_init( 128 );
5042 OSRF_BUFFER_ADD( order_buf, string );
5046 OSRF_BUFFER_ADD( order_buf, direction );
5050 jsonIteratorFree( order_itr );
5052 } else if( snode->type == JSON_ARRAY ) {
5054 // Array is a list of fields from the current class
5055 unsigned long order_idx = 0;
5056 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
5058 const char* _f = jsonObjectGetString( onode );
5060 osrfHash* field_def = osrfHashGet( field_list_def, _f );
5062 osrfLogError( OSRF_LOG_MARK,
5063 "%s: Invalid field \"%s\" in ORDER BY clause",
5066 osrfAppSessionStatus(
5068 OSRF_STATUS_INTERNALSERVERERROR,
5069 "osrfMethodException",
5071 "Invalid field in ORDER BY clause -- "
5072 "see error log for more details"
5074 jsonIteratorFree( class_itr );
5075 buffer_free( order_buf );
5077 buffer_free( group_buf );
5078 buffer_free( sql_buf );
5079 if( defaultselhash )
5080 jsonObjectFree( defaultselhash );
5082 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5083 osrfLogError( OSRF_LOG_MARK,
5084 "%s: Virtual field \"%s\" in ORDER BY clause",
5087 osrfAppSessionStatus(
5089 OSRF_STATUS_INTERNALSERVERERROR,
5090 "osrfMethodException",
5092 "Virtual field in ORDER BY clause -- "
5093 "see error log for more details"
5095 jsonIteratorFree( class_itr );
5096 buffer_free( order_buf );
5098 buffer_free( group_buf );
5099 buffer_free( sql_buf );
5100 if( defaultselhash )
5101 jsonObjectFree( defaultselhash );
5106 OSRF_BUFFER_ADD( order_buf, ", " );
5108 order_buf = buffer_init( 128 );
5110 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
5114 // IT'S THE OOOOOOOOOOOLD STYLE!
5116 osrfLogError( OSRF_LOG_MARK,
5117 "%s: Possible SQL injection attempt; direct order by is not allowed",
5120 osrfAppSessionStatus(
5122 OSRF_STATUS_INTERNALSERVERERROR,
5123 "osrfMethodException",
5125 "Severe query error -- see error log for more details"
5130 buffer_free( group_buf );
5131 buffer_free( order_buf );
5132 buffer_free( sql_buf );
5133 if( defaultselhash )
5134 jsonObjectFree( defaultselhash );
5135 jsonIteratorFree( class_itr );
5139 jsonIteratorFree( class_itr );
5141 order_by_list = buffer_release( order_buf );
5143 osrfLogError( OSRF_LOG_MARK,
5144 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
5145 modulename, json_type( order_hash->type ) );
5147 osrfAppSessionStatus(
5149 OSRF_STATUS_INTERNALSERVERERROR,
5150 "osrfMethodException",
5152 "Malformed ORDER BY clause -- see error log for more details"
5155 buffer_free( group_buf );
5156 buffer_free( sql_buf );
5157 if( defaultselhash )
5158 jsonObjectFree( defaultselhash );
5163 string = buffer_release( group_buf );
5165 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
5166 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
5167 OSRF_BUFFER_ADD( sql_buf, string );
5172 if( having_buf && *having_buf ) {
5173 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
5174 OSRF_BUFFER_ADD( sql_buf, having_buf );
5178 if( order_by_list ) {
5180 if( *order_by_list ) {
5181 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5182 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5185 free( order_by_list );
5189 const char* str = jsonObjectGetString( limit );
5190 if (str) { // limit could be JSON_NULL, etc.
5191 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
5196 const char* str = jsonObjectGetString( offset );
5198 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
5202 if( !(flags & SUBSELECT) )
5203 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5205 if( defaultselhash )
5206 jsonObjectFree( defaultselhash );
5208 return buffer_release( sql_buf );
5210 } // end of SELECT()
5213 @brief Build a list of ORDER BY expressions.
5214 @param ctx Pointer to the method context.
5215 @param order_array Pointer to a JSON_ARRAY of field specifications.
5216 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
5217 Each expression may be either a column reference or a function call whose first parameter
5218 is a column reference.
5220 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
5221 It may optionally include entries for "direction" and/or "transform".
5223 The calling code is responsible for freeing the returned string.
5225 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
5226 if( ! order_array ) {
5227 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
5230 osrfAppSessionStatus(
5232 OSRF_STATUS_INTERNALSERVERERROR,
5233 "osrfMethodException",
5235 "Logic error: ORDER BY clause expected, not found; "
5236 "see error log for more details"
5239 } else if( order_array->type != JSON_ARRAY ) {
5240 osrfLogError( OSRF_LOG_MARK,
5241 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
5243 osrfAppSessionStatus(
5245 OSRF_STATUS_INTERNALSERVERERROR,
5246 "osrfMethodException",
5248 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
5252 growing_buffer* order_buf = buffer_init( 128 );
5253 int first = 1; // boolean
5255 jsonObject* order_spec;
5256 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
5258 if( JSON_HASH != order_spec->type ) {
5259 osrfLogError( OSRF_LOG_MARK,
5260 "%s: Malformed field specification in ORDER BY clause; "
5261 "expected JSON_HASH, found %s",
5262 modulename, json_type( order_spec->type ) );
5264 osrfAppSessionStatus(
5266 OSRF_STATUS_INTERNALSERVERERROR,
5267 "osrfMethodException",
5269 "Malformed ORDER BY clause -- see error log for more details"
5271 buffer_free( order_buf );
5275 const char* class_alias =
5276 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5278 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5280 jsonObject* compare_to = jsonObjectGetKey( order_spec, "compare" );
5282 if( !field || !class_alias ) {
5283 osrfLogError( OSRF_LOG_MARK,
5284 "%s: Missing class or field name in field specification of ORDER BY clause",
5287 osrfAppSessionStatus(
5289 OSRF_STATUS_INTERNALSERVERERROR,
5290 "osrfMethodException",
5292 "Malformed ORDER BY clause -- see error log for more details"
5294 buffer_free( order_buf );
5298 const ClassInfo* order_class_info = search_alias( class_alias );
5299 if( ! order_class_info ) {
5300 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5301 "not in FROM clause, skipping it", modulename, class_alias );
5305 // Add a separating comma, except at the beginning
5309 OSRF_BUFFER_ADD( order_buf, ", " );
5311 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5313 osrfLogError( OSRF_LOG_MARK,
5314 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5315 modulename, class_alias, field );
5317 osrfAppSessionStatus(
5319 OSRF_STATUS_INTERNALSERVERERROR,
5320 "osrfMethodException",
5322 "Invalid field referenced in ORDER BY clause -- "
5323 "see error log for more details"
5327 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5328 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5329 modulename, field );
5331 osrfAppSessionStatus(
5333 OSRF_STATUS_INTERNALSERVERERROR,
5334 "osrfMethodException",
5336 "Virtual field in ORDER BY clause -- see error log for more details"
5338 buffer_free( order_buf );
5342 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5343 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5344 if( ! transform_str ) {
5346 osrfAppSessionStatus(
5348 OSRF_STATUS_INTERNALSERVERERROR,
5349 "osrfMethodException",
5351 "Severe query error in ORDER BY clause -- "
5352 "see error log for more details"
5354 buffer_free( order_buf );
5358 OSRF_BUFFER_ADD( order_buf, transform_str );
5359 free( transform_str );
5360 } else if( compare_to ) {
5361 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5362 if( ! compare_str ) {
5364 osrfAppSessionStatus(
5366 OSRF_STATUS_INTERNALSERVERERROR,
5367 "osrfMethodException",
5369 "Severe query error in ORDER BY clause -- "
5370 "see error log for more details"
5372 buffer_free( order_buf );
5376 buffer_fadd( order_buf, "(%s)", compare_str );
5377 free( compare_str );
5380 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5382 const char* direction =
5383 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5385 if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5386 OSRF_BUFFER_ADD( order_buf, " DESC" );
5388 OSRF_BUFFER_ADD( order_buf, " ASC" );
5392 return buffer_release( order_buf );
5396 @brief Build a SELECT statement.
5397 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5398 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5399 @param meta Pointer to the class metadata for the core class.
5400 @param ctx Pointer to the method context.
5401 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5403 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5404 "order_by", "limit", and "offset".
5406 The SELECT statements built here are distinct from those built for the json_query method.
5408 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5409 osrfHash* meta, osrfMethodContext* ctx ) {
5411 const char* locale = osrf_message_get_last_locale();
5413 osrfHash* fields = osrfHashGet( meta, "fields" );
5414 const char* core_class = osrfHashGet( meta, "classname" );
5416 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5418 jsonObject* selhash = NULL;
5419 jsonObject* defaultselhash = NULL;
5421 growing_buffer* sql_buf = buffer_init( 128 );
5422 growing_buffer* select_buf = buffer_init( 128 );
5424 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5425 defaultselhash = jsonNewObjectType( JSON_HASH );
5426 selhash = defaultselhash;
5429 // If there's no SELECT list for the core class, build one
5430 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5431 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5433 // Add every non-virtual field to the field list
5434 osrfHash* field_def = NULL;
5435 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5436 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5437 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5438 const char* field = osrfHashIteratorKey( field_itr );
5439 jsonObjectPush( field_list, jsonNewObject( field ) );
5442 osrfHashIteratorFree( field_itr );
5443 jsonObjectSetKey( selhash, core_class, field_list );
5446 // Build a list of columns for the SELECT clause
5448 const jsonObject* snode = NULL;
5449 jsonIterator* class_itr = jsonNewIterator( selhash );
5450 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5452 // If the class isn't in the IDL, ignore it
5453 const char* cname = class_itr->key;
5454 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5458 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5459 if( strcmp( core_class, class_itr->key )) {
5463 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5464 if( !found->size ) {
5465 jsonObjectFree( found );
5469 jsonObjectFree( found );
5472 const jsonObject* node = NULL;
5473 jsonIterator* select_itr = jsonNewIterator( snode );
5474 while( (node = jsonIteratorNext( select_itr )) ) {
5475 const char* item_str = jsonObjectGetString( node );
5476 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5477 char* fname = osrfHashGet( field, "name" );
5482 if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5488 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5493 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5494 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5497 i18n = osrfHashGet( field, "i18n" );
5499 if( str_is_true( i18n ) ) {
5500 char* pkey = osrfHashGet( idlClass, "primarykey" );
5501 char* tname = osrfHashGet( idlClass, "tablename" );
5503 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5504 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5505 tname, cname, fname, pkey, cname, pkey, locale, fname );
5507 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5510 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5514 jsonIteratorFree( select_itr );
5517 jsonIteratorFree( class_itr );
5519 char* col_list = buffer_release( select_buf );
5520 char* table = oilsGetRelation( meta );
5522 table = strdup( "(null)" );
5524 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5528 // Clear the query stack (as a fail-safe precaution against possible
5529 // leftover garbage); then push the first query frame onto the stack.
5530 clear_query_stack();
5532 if( add_query_core( NULL, core_class ) ) {
5534 osrfAppSessionStatus(
5536 OSRF_STATUS_INTERNALSERVERERROR,
5537 "osrfMethodException",
5539 "Unable to build query frame for core class"
5541 buffer_free( sql_buf );
5542 if( defaultselhash )
5543 jsonObjectFree( defaultselhash );
5547 // Add the JOIN clauses, if any
5549 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5550 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5551 OSRF_BUFFER_ADD( sql_buf, join_clause );
5552 free( join_clause );
5555 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5556 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5558 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5560 // Add the conditions in the WHERE clause
5561 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5563 osrfAppSessionStatus(
5565 OSRF_STATUS_INTERNALSERVERERROR,
5566 "osrfMethodException",
5568 "Severe query error -- see error log for more details"
5570 buffer_free( sql_buf );
5571 if( defaultselhash )
5572 jsonObjectFree( defaultselhash );
5573 clear_query_stack();
5576 buffer_add( sql_buf, pred );
5580 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5581 if( rest_of_query ) {
5582 const jsonObject* order_by = NULL;
5583 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5585 char* order_by_list = NULL;
5587 if( JSON_ARRAY == order_by->type ) {
5588 order_by_list = buildOrderByFromArray( ctx, order_by );
5589 if( !order_by_list ) {
5590 buffer_free( sql_buf );
5591 if( defaultselhash )
5592 jsonObjectFree( defaultselhash );
5593 clear_query_stack();
5596 } else if( JSON_HASH == order_by->type ) {
5597 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5598 // and build a list of ORDER BY expressions.
5599 growing_buffer* order_buf = buffer_init( 128 );
5601 jsonIterator* class_itr = jsonNewIterator( order_by );
5602 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5604 ClassInfo* order_class_info = search_alias( class_itr->key );
5605 if( ! order_class_info )
5606 continue; // class not referenced by FROM clause? Ignore it.
5608 if( JSON_HASH == snode->type ) {
5610 // If the data for the current class is a JSON_HASH, then it is
5611 // keyed on field name.
5613 const jsonObject* onode = NULL;
5614 jsonIterator* order_itr = jsonNewIterator( snode );
5615 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5617 osrfHash* field_def = osrfHashGet(
5618 order_class_info->fields, order_itr->key );
5620 continue; // Field not defined in IDL? Ignore it.
5621 if( str_is_true( osrfHashGet( field_def, "virtual")))
5622 continue; // Field is virtual? Ignore it.
5624 char* field_str = NULL;
5625 char* direction = NULL;
5626 if( onode->type == JSON_HASH ) {
5627 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5628 field_str = searchFieldTransform(
5629 class_itr->key, field_def, onode );
5631 osrfAppSessionStatus(
5633 OSRF_STATUS_INTERNALSERVERERROR,
5634 "osrfMethodException",
5636 "Severe query error in ORDER BY clause -- "
5637 "see error log for more details"
5639 jsonIteratorFree( order_itr );
5640 jsonIteratorFree( class_itr );
5641 buffer_free( order_buf );
5642 buffer_free( sql_buf );
5643 if( defaultselhash )
5644 jsonObjectFree( defaultselhash );
5645 clear_query_stack();
5649 growing_buffer* field_buf = buffer_init( 16 );
5650 buffer_fadd( field_buf, "\"%s\".%s",
5651 class_itr->key, order_itr->key );
5652 field_str = buffer_release( field_buf );
5655 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5656 const char* dir = jsonObjectGetString( order_by );
5657 if(!strncasecmp( dir, "d", 1 )) {
5658 direction = " DESC";
5662 field_str = strdup( order_itr->key );
5663 const char* dir = jsonObjectGetString( onode );
5664 if( !strncasecmp( dir, "d", 1 )) {
5665 direction = " DESC";
5674 buffer_add( order_buf, ", " );
5677 buffer_add( order_buf, field_str );
5681 buffer_add( order_buf, direction );
5683 } // end while; looping over ORDER BY expressions
5685 jsonIteratorFree( order_itr );
5687 } else if( JSON_STRING == snode->type ) {
5688 // We expect a comma-separated list of sort fields.
5689 const char* str = jsonObjectGetString( snode );
5690 if( strchr( str, ';' )) {
5691 // No semicolons allowed. It is theoretically possible for a
5692 // legitimate semicolon to occur within quotes, but it's not likely
5693 // to occur in practice in the context of an ORDER BY list.
5694 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5695 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5697 osrfAppSessionStatus(
5699 OSRF_STATUS_INTERNALSERVERERROR,
5700 "osrfMethodException",
5702 "Possible attempt at SOL injection -- "
5703 "semicolon found in ORDER BY list"
5706 jsonIteratorFree( class_itr );
5707 buffer_free( order_buf );
5708 buffer_free( sql_buf );
5709 if( defaultselhash )
5710 jsonObjectFree( defaultselhash );
5711 clear_query_stack();
5714 buffer_add( order_buf, str );
5718 } // end while; looping over order_by classes
5720 jsonIteratorFree( class_itr );
5721 order_by_list = buffer_release( order_buf );
5724 osrfLogWarning( OSRF_LOG_MARK,
5725 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5726 "no ORDER BY generated" );
5729 if( order_by_list && *order_by_list ) {
5730 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5731 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5734 free( order_by_list );
5737 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5739 const char* str = jsonObjectGetString( limit );
5749 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5751 const char* str = jsonObjectGetString( offset );
5762 if( defaultselhash )
5763 jsonObjectFree( defaultselhash );
5764 clear_query_stack();
5766 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5767 return buffer_release( sql_buf );
5770 int doJSONSearch ( osrfMethodContext* ctx ) {
5771 if(osrfMethodVerifyContext( ctx )) {
5772 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5776 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5780 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5784 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5785 flags |= SELECT_DISTINCT;
5787 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5788 flags |= DISABLE_I18N;
5790 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5791 clear_query_stack(); // a possibly needless precaution
5792 char* sql = buildQuery( ctx, hash, flags );
5793 clear_query_stack();
5800 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5803 dbhandle = writehandle;
5805 dbi_result result = dbi_conn_query( dbhandle, sql );
5808 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5810 if( dbi_result_first_row( result )) {
5811 /* JSONify the result */
5812 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5815 jsonObject* return_val = oilsMakeJSONFromResult( result );
5816 osrfAppRespond( ctx, return_val );
5817 jsonObjectFree( return_val );
5818 } while( dbi_result_next_row( result ));
5821 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5824 osrfAppRespondComplete( ctx, NULL );
5826 /* clean up the query */
5827 dbi_result_free( result );
5832 int errnum = dbi_conn_error( dbhandle, &msg );
5833 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5834 modulename, sql, errnum, msg ? msg : "(No description available)" );
5835 osrfAppSessionStatus(
5837 OSRF_STATUS_INTERNALSERVERERROR,
5838 "osrfMethodException",
5840 "Severe query error -- see error log for more details"
5842 if( !oilsIsDBConnected( dbhandle ))
5843 osrfAppSessionPanic( ctx->session );
5850 // The last parameter, err, is used to report an error condition by updating an int owned by
5851 // the calling code.
5853 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5854 // It is the responsibility of the calling code to initialize *err before the
5855 // call, so that it will be able to make sense of the result.
5857 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5858 // redundant anyway.
5859 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5860 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5862 const char* tz = _sanitize_tz_name(ctx->session->session_tz);
5865 dbhandle = writehandle;
5867 char* core_class = osrfHashGet( class_meta, "classname" );
5868 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5870 char* pkey = osrfHashGet( class_meta, "primarykey" );
5872 if (!ctx->session->userData)
5873 (void) initSessionCache( ctx );
5875 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5876 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5877 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5879 int i_respond_directly = 0;
5880 int flesh_depth = 0;
5882 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5884 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5889 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5891 // Setting the timezone if requested and not in a transaction
5892 if (!getXactId(ctx)) {
5896 dbi_result tz_res = dbi_conn_queryf( writehandle, "SET timezone TO '%s'; -- cstore", tz );
5898 osrfLogError( OSRF_LOG_MARK, "%s: Error setting timezone %s", modulename, tz);
5899 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
5900 "osrfMethodException", ctx->request, "Error setting timezone" );
5901 if( !oilsIsDBConnected( writehandle )) {
5902 osrfAppSessionPanic( ctx->session );
5906 dbi_result_free( tz_res );
5911 dbi_result res = dbi_conn_queryf( writehandle, "SET timezone TO DEFAULT; -- cstore" );
5913 osrfLogError( OSRF_LOG_MARK, "%s: Error resetting timezone", modulename);
5914 if( !oilsIsDBConnected( writehandle )) {
5915 osrfAppSessionPanic( ctx->session );
5919 dbi_result_free( res );
5925 dbi_result result = dbi_conn_query( dbhandle, sql );
5927 if( NULL == result ) {
5929 int errnum = dbi_conn_error( dbhandle, &msg );
5930 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5931 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5932 msg ? msg : "(No description available)" );
5933 if( !oilsIsDBConnected( dbhandle ))
5934 osrfAppSessionPanic( ctx->session );
5935 osrfAppSessionStatus(
5937 OSRF_STATUS_INTERNALSERVERERROR,
5938 "osrfMethodException",
5940 "Severe query error -- see error log for more details"
5947 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5951 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5952 jsonObject* row_obj = NULL;
5954 // The following two steps are for verifyObjectPCRUD()'s benefit.
5955 // 1. get the flesh depth
5956 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5958 flesh_depth = (int) jsonObjectGetNumber( _tmp );
5959 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5960 flesh_depth = max_flesh_depth;
5963 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
5964 // over the whole life of this request. This means if we've already set
5965 // up a rs_size_req_%d, do nothing.
5966 // a. Incidentally, we can also use this opportunity to set i_respond_directly
5967 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
5968 if( !rs_size ) { // pointer null, so value not set in hash
5969 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
5970 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
5972 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
5973 unsigned long long result_count = dbi_result_get_numrows( result );
5974 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
5975 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
5978 if( dbi_result_first_row( result )) {
5980 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5981 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5982 // eliminate the duplicates.
5983 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5984 osrfHash* dedup = osrfNewHash();
5986 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5987 char* pkey_val = oilsFMGetString( row_obj, pkey );
5988 if( osrfHashGet( dedup, pkey_val ) ) {
5989 jsonObjectFree( row_obj );
5992 if( !enforce_pcrud || !need_to_verify ||
5993 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
5994 osrfHashSet( dedup, pkey_val, pkey_val );
5995 jsonObjectPush( res_list, row_obj );
5998 } while( dbi_result_next_row( result ));
5999 osrfHashFree( dedup );
6002 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
6006 /* clean up the query */
6007 dbi_result_free( result );
6010 // If we're asked to flesh, and there's anything to flesh, then flesh it
6011 // (formerly we would skip fleshing if in pcrud mode, but now we support
6012 // fleshing even in PCRUD).
6013 if( res_list->size ) {
6014 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
6015 jsonObject* flesh_fields;
6016 jsonObject* flesh_blob = NULL;
6017 osrfStringArray* link_fields = NULL;
6018 osrfHash* links = NULL;
6022 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
6023 if( temp_blob && flesh_depth > 0 ) {
6025 flesh_blob = jsonObjectClone( temp_blob );
6026 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
6028 links = osrfHashGet( class_meta, "links" );
6030 // Make an osrfStringArray of the names of fields to be fleshed
6031 if( flesh_fields ) {
6032 if( flesh_fields->size == 1 ) {
6033 const char* _t = jsonObjectGetString(
6034 jsonObjectGetIndex( flesh_fields, 0 ) );
6035 if( !strcmp( _t, "*" ))
6036 link_fields = osrfHashKeys( links );
6039 if( !link_fields ) {
6041 link_fields = osrfNewStringArray( 1 );
6042 jsonIterator* _i = jsonNewIterator( flesh_fields );
6043 while ((_f = jsonIteratorNext( _i ))) {
6044 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
6046 jsonIteratorFree( _i );
6049 want_flesh = link_fields ? 1 : 0;
6053 osrfHash* fields = osrfHashGet( class_meta, "fields" );
6055 // Iterate over the JSON_ARRAY of rows
6057 unsigned long res_idx = 0;
6058 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
6061 const char* link_field;
6063 // Iterate over the list of fleshable fields
6065 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
6067 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
6069 osrfHash* kid_link = osrfHashGet( links, link_field );
6071 continue; // Not a link field; skip it
6073 osrfHash* field = osrfHashGet( fields, link_field );
6075 continue; // Not a field at all; skip it (IDL is ill-formed)
6077 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
6078 osrfHashGet( kid_link, "class" ));
6080 continue; // The class it links to doesn't exist; skip it
6082 const char* reltype = osrfHashGet( kid_link, "reltype" );
6084 continue; // No reltype; skip it (IDL is ill-formed)
6086 osrfHash* value_field = field;
6088 if( !strcmp( reltype, "has_many" )
6089 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
6090 value_field = osrfHashGet(
6091 fields, osrfHashGet( class_meta, "primarykey" ) );
6094 int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
6095 // fleshing pcrud case: we require the controller in need_to_verify mode
6096 if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
6097 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
6101 (unsigned long) atoi( osrfHashGet(field, "array_position") ),
6103 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
6109 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
6111 if( link_map->size > 0 ) {
6112 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
6115 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
6120 osrfHashGet( kid_link, "class" ),
6127 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
6128 osrfHashGet( kid_link, "field" ),
6129 osrfHashGet( kid_link, "class" ),
6130 osrfHashGet( kid_link, "key" ),
6131 osrfHashGet( kid_link, "reltype" )
6134 const char* search_key = jsonObjectGetString(
6135 jsonObjectGetIndex( cur,
6136 atoi( osrfHashGet( value_field, "array_position" ) )
6141 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
6145 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
6147 // construct WHERE clause
6148 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
6151 osrfHashGet( kid_link, "key" ),
6152 jsonNewObject( search_key )
6155 // construct the rest of the query, mostly
6156 // by copying pieces of the previous level of query
6157 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
6158 jsonObjectSetKey( rest_of_query, "flesh",
6159 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
6163 jsonObjectSetKey( rest_of_query, "flesh_fields",
6164 jsonObjectClone( flesh_blob ));
6166 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
6167 jsonObjectSetKey( rest_of_query, "order_by",
6168 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
6172 if( jsonObjectGetKeyConst( query_hash, "select" )) {
6173 jsonObjectSetKey( rest_of_query, "select",
6174 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
6178 // do the query, recursively, to expand the fleshable field
6179 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
6180 where_clause, rest_of_query, err );
6182 jsonObjectFree( where_clause );
6183 jsonObjectFree( rest_of_query );
6186 osrfStringArrayFree( link_fields );
6187 jsonObjectFree( res_list );
6188 jsonObjectFree( flesh_blob );
6192 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
6193 osrfHashGet( kid_link, "class" ), kids->size );
6195 // Traverse the result set
6196 jsonObject* X = NULL;
6197 if( link_map->size > 0 && kids->size > 0 ) {
6199 kids = jsonNewObjectType( JSON_ARRAY );
6201 jsonObject* _k_node;
6202 unsigned long res_idx = 0;
6203 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
6209 (unsigned long) atoi(
6215 osrfHashGet( kid_link, "class" )
6219 osrfStringArrayGetString( link_map, 0 )
6227 } // end while loop traversing X
6230 if (kids->size > 0) {
6232 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
6233 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
6235 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
6236 osrfHashGet( kid_link, "field" ));
6239 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6240 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
6245 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
6247 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
6248 osrfHashGet( kid_link, "field" ) );
6251 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
6252 jsonObjectClone( kids )
6257 jsonObjectFree( kids );
6261 jsonObjectFree( kids );
6263 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
6264 osrfHashGet( kid_link, "field" ) );
6265 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
6267 } // end while loop traversing list of fleshable fields
6270 if( i_respond_directly ) {
6271 if ( *methodtype == 'i' ) {
6272 osrfAppRespond( ctx,
6273 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
6275 osrfAppRespond( ctx, cur );
6278 } // end while loop traversing res_list
6279 jsonObjectFree( flesh_blob );
6280 osrfStringArrayFree( link_fields );
6283 if( i_respond_directly ) {
6284 jsonObjectFree( res_list );
6285 return jsonNewObjectType( JSON_ARRAY );
6292 int doUpdate( osrfMethodContext* ctx ) {
6293 if( osrfMethodVerifyContext( ctx )) {
6294 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6299 timeout_needs_resetting = 1;
6301 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6303 jsonObject* target = NULL;
6305 target = jsonObjectGetIndex( ctx->params, 1 );
6307 target = jsonObjectGetIndex( ctx->params, 0 );
6309 if(!verifyObjectClass( ctx, target )) {
6310 osrfAppRespondComplete( ctx, NULL );
6314 if( getXactId( ctx ) == NULL ) {
6315 osrfAppSessionStatus(
6317 OSRF_STATUS_BADREQUEST,
6318 "osrfMethodException",
6320 "No active transaction -- required for UPDATE"
6322 osrfAppRespondComplete( ctx, NULL );
6326 // The following test is harmless but redundant. If a class is
6327 // readonly, we don't register an update method for it.
6328 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6329 osrfAppSessionStatus(
6331 OSRF_STATUS_BADREQUEST,
6332 "osrfMethodException",
6334 "Cannot UPDATE readonly class"
6336 osrfAppRespondComplete( ctx, NULL );
6340 const char* trans_id = getXactId( ctx );
6342 // Set the last_xact_id
6343 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6345 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6346 trans_id, target->classname, index );
6347 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6350 char* pkey = osrfHashGet( meta, "primarykey" );
6351 osrfHash* fields = osrfHashGet( meta, "fields" );
6353 char* id = oilsFMGetString( target, pkey );
6357 "%s updating %s object with %s = %s",
6359 osrfHashGet( meta, "fieldmapper" ),
6364 dbhandle = writehandle;
6365 growing_buffer* sql = buffer_init( 128 );
6366 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6369 osrfHash* field_def = NULL;
6370 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6371 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6373 // Skip virtual fields, and the primary key
6374 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6377 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6381 const char* field_name = osrfHashIteratorKey( field_itr );
6382 if( ! strcmp( field_name, pkey ) )
6385 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6387 int value_is_numeric = 0; // boolean
6389 if( field_object && field_object->classname ) {
6390 value = oilsFMGetString(
6392 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6394 } else if( field_object && JSON_BOOL == field_object->type ) {
6395 if( jsonBoolIsTrue( field_object ) )
6396 value = strdup( "t" );
6398 value = strdup( "f" );
6400 value = jsonObjectToSimpleString( field_object );
6401 if( field_object && JSON_NUMBER == field_object->type )
6402 value_is_numeric = 1;
6405 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6406 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6408 if( !field_object || field_object->type == JSON_NULL ) {
6409 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6410 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6414 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6415 buffer_fadd( sql, " %s = NULL", field_name );
6418 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6422 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6424 const char* numtype = get_datatype( field_def );
6425 if( !strncmp( numtype, "INT", 3 ) ) {
6426 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6427 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6428 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6430 // Must really be intended as a string, so quote it
6431 if( dbi_conn_quote_string( dbhandle, &value )) {
6432 buffer_fadd( sql, " %s = %s", field_name, value );
6434 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6435 modulename, value );
6436 osrfAppSessionStatus(
6438 OSRF_STATUS_INTERNALSERVERERROR,
6439 "osrfMethodException",
6441 "Error quoting string -- please see the error log for more details"
6445 osrfHashIteratorFree( field_itr );
6447 osrfAppRespondComplete( ctx, NULL );
6452 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6455 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6459 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6460 buffer_fadd( sql, " %s = %s", field_name, value );
6462 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6463 osrfAppSessionStatus(
6465 OSRF_STATUS_INTERNALSERVERERROR,
6466 "osrfMethodException",
6468 "Error quoting string -- please see the error log for more details"
6472 osrfHashIteratorFree( field_itr );
6474 osrfAppRespondComplete( ctx, NULL );
6483 osrfHashIteratorFree( field_itr );
6485 jsonObject* obj = jsonNewObject( id );
6487 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6488 dbi_conn_quote_string( dbhandle, &id );
6490 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6492 char* query = buffer_release( sql );
6493 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6495 dbi_result result = dbi_conn_query( dbhandle, query );
6500 jsonObjectFree( obj );
6501 obj = jsonNewObject( NULL );
6503 int errnum = dbi_conn_error( dbhandle, &msg );
6506 "%s ERROR updating %s object with %s = %s: %d %s",
6508 osrfHashGet( meta, "fieldmapper" ),
6512 msg ? msg : "(No description available)"
6514 osrfAppSessionStatus(
6516 OSRF_STATUS_INTERNALSERVERERROR,
6517 "osrfMethodException",
6519 "Error in updating a row -- please see the error log for more details"
6521 if( !oilsIsDBConnected( dbhandle ))
6522 osrfAppSessionPanic( ctx->session );
6525 dbi_result_free( result );
6528 osrfAppRespondComplete( ctx, obj );
6529 jsonObjectFree( obj );
6533 int doDelete( osrfMethodContext* ctx ) {
6534 if( osrfMethodVerifyContext( ctx )) {
6535 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6540 timeout_needs_resetting = 1;
6542 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6544 if( getXactId( ctx ) == NULL ) {
6545 osrfAppSessionStatus(
6547 OSRF_STATUS_BADREQUEST,
6548 "osrfMethodException",
6550 "No active transaction -- required for DELETE"
6552 osrfAppRespondComplete( ctx, NULL );
6556 // The following test is harmless but redundant. If a class is
6557 // readonly, we don't register a delete method for it.
6558 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6559 osrfAppSessionStatus(
6561 OSRF_STATUS_BADREQUEST,
6562 "osrfMethodException",
6564 "Cannot DELETE readonly class"
6566 osrfAppRespondComplete( ctx, NULL );
6570 dbhandle = writehandle;
6572 char* pkey = osrfHashGet( meta, "primarykey" );
6579 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6580 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6581 osrfAppRespondComplete( ctx, NULL );
6585 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6587 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6588 osrfAppRespondComplete( ctx, NULL );
6591 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6596 "%s deleting %s object with %s = %s",
6598 osrfHashGet( meta, "fieldmapper" ),
6603 jsonObject* obj = jsonNewObject( id );
6605 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6606 dbi_conn_quote_string( writehandle, &id );
6608 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6609 osrfHashGet( meta, "tablename" ), pkey, id );
6614 jsonObjectFree( obj );
6615 obj = jsonNewObject( NULL );
6617 int errnum = dbi_conn_error( writehandle, &msg );
6620 "%s ERROR deleting %s object with %s = %s: %d %s",
6622 osrfHashGet( meta, "fieldmapper" ),
6626 msg ? msg : "(No description available)"
6628 osrfAppSessionStatus(
6630 OSRF_STATUS_INTERNALSERVERERROR,
6631 "osrfMethodException",
6633 "Error in deleting a row -- please see the error log for more details"
6635 if( !oilsIsDBConnected( writehandle ))
6636 osrfAppSessionPanic( ctx->session );
6638 dbi_result_free( result );
6642 osrfAppRespondComplete( ctx, obj );
6643 jsonObjectFree( obj );
6648 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6649 @param result An iterator for a result set; we only look at the current row.
6650 @param @meta Pointer to the class metadata for the core class.
6651 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6653 If a column is not defined in the IDL, or if it has no array_position defined for it in
6654 the IDL, or if it is defined as virtual, ignore it.
6656 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6657 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6658 array_position in the IDL.
6660 A field defined in the IDL but not represented in the returned row will leave a hole
6661 in the JSON_ARRAY. In effect it will be treated as a null value.
6663 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6664 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6665 classname corresponding to the @a meta argument.
6667 The calling code is responsible for freeing the the resulting jsonObject by calling
6670 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6671 if( !( result && meta )) return NULL;
6673 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6674 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6675 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6677 osrfHash* fields = osrfHashGet( meta, "fields" );
6679 int columnIndex = 1;
6680 const char* columnName;
6682 /* cycle through the columns in the row returned from the database */
6683 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6685 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6687 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6689 /* determine the field type and storage attributes */
6690 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6691 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6693 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6694 // or if it has no sequence number there, or if it's virtual, skip it.
6695 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6698 if( str_is_true( osrfHashGet( _f, "virtual" )))
6699 continue; // skip this column: IDL says it's virtual
6701 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6702 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6703 continue; // since we assign sequence numbers dynamically as we load the IDL.
6705 fmIndex = atoi( pos );
6706 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6708 continue; // This field is not defined in the IDL
6711 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6712 // sequence number from the IDL (which is likely to be different from the sequence
6713 // of columns in the SELECT clause).
6714 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6715 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6720 case DBI_TYPE_INTEGER :
6722 if( attr & DBI_INTEGER_SIZE8 )
6723 jsonObjectSetIndex( object, fmIndex,
6724 jsonNewNumberObject(
6725 dbi_result_get_longlong_idx( result, columnIndex )));
6727 jsonObjectSetIndex( object, fmIndex,
6728 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6732 case DBI_TYPE_DECIMAL :
6733 jsonObjectSetIndex( object, fmIndex,
6734 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6737 case DBI_TYPE_STRING :
6742 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6747 case DBI_TYPE_DATETIME : {
6749 char dt_string[ 256 ] = "";
6752 // Fetch the date column as a time_t
6753 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6755 // Translate the time_t to a human-readable string
6756 if( !( attr & DBI_DATETIME_DATE )) {
6757 gmtime_r( &_tmp_dt, &gmdt );
6758 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6759 } else if( !( attr & DBI_DATETIME_TIME )) {
6760 gmtime_r( &_tmp_dt, &gmdt );
6761 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6763 localtime_r( &_tmp_dt, &gmdt );
6764 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6767 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6771 case DBI_TYPE_BINARY :
6772 osrfLogError( OSRF_LOG_MARK,
6773 "Can't do binary at column %s : index %d", columnName, columnIndex );
6782 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6783 if( !result ) return NULL;
6785 jsonObject* object = jsonNewObject( NULL );
6788 char dt_string[ 256 ];
6792 int columnIndex = 1;
6794 unsigned short type;
6795 const char* columnName;
6797 /* cycle through the column list */
6798 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6800 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6802 fmIndex = -1; // reset the position
6804 /* determine the field type and storage attributes */
6805 type = dbi_result_get_field_type_idx( result, columnIndex );
6806 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6808 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6809 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6814 case DBI_TYPE_INTEGER :
6816 if( attr & DBI_INTEGER_SIZE8 )
6817 jsonObjectSetKey( object, columnName,
6818 jsonNewNumberObject( dbi_result_get_longlong_idx(
6819 result, columnIndex )) );
6821 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6822 dbi_result_get_int_idx( result, columnIndex )) );
6825 case DBI_TYPE_DECIMAL :
6826 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6827 dbi_result_get_double_idx( result, columnIndex )) );
6830 case DBI_TYPE_STRING :
6831 jsonObjectSetKey( object, columnName,
6832 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6835 case DBI_TYPE_DATETIME :
6837 memset( dt_string, '\0', sizeof( dt_string ));
6838 memset( &gmdt, '\0', sizeof( gmdt ));
6840 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6842 if( !( attr & DBI_DATETIME_DATE )) {
6843 gmtime_r( &_tmp_dt, &gmdt );
6844 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6845 } else if( !( attr & DBI_DATETIME_TIME )) {
6846 gmtime_r( &_tmp_dt, &gmdt );
6847 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6849 localtime_r( &_tmp_dt, &gmdt );
6850 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6853 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6856 case DBI_TYPE_BINARY :
6857 osrfLogError( OSRF_LOG_MARK,
6858 "Can't do binary at column %s : index %d", columnName, columnIndex );
6862 } // end while loop traversing result
6867 // Interpret a string as true or false
6868 int str_is_true( const char* str ) {
6869 if( NULL == str || strcasecmp( str, "true" ) )
6875 // Interpret a jsonObject as true or false
6876 static int obj_is_true( const jsonObject* obj ) {
6879 else switch( obj->type )
6887 if( strcasecmp( obj->value.s, "true" ) )
6891 case JSON_NUMBER : // Support 1/0 for perl's sake
6892 if( jsonObjectGetNumber( obj ) == 1.0 )
6901 // Translate a numeric code into a text string identifying a type of
6902 // jsonObject. To be used for building error messages.
6903 static const char* json_type( int code ) {
6909 return "JSON_ARRAY";
6911 return "JSON_STRING";
6913 return "JSON_NUMBER";
6919 return "(unrecognized)";
6923 // Extract the "primitive" attribute from an IDL field definition.
6924 // If we haven't initialized the app, then we must be running in
6925 // some kind of testbed. In that case, default to "string".
6926 static const char* get_primitive( osrfHash* field ) {
6927 const char* s = osrfHashGet( field, "primitive" );
6929 if( child_initialized )
6932 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6934 osrfHashGet( field, "name" )
6942 // Extract the "datatype" attribute from an IDL field definition.
6943 // If we haven't initialized the app, then we must be running in
6944 // some kind of testbed. In that case, default to to NUMERIC,
6945 // since we look at the datatype only for numbers.
6946 static const char* get_datatype( osrfHash* field ) {
6947 const char* s = osrfHashGet( field, "datatype" );
6949 if( child_initialized )
6952 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6954 osrfHashGet( field, "name" )
6963 @brief Determine whether a string is potentially a valid SQL identifier.
6964 @param s The identifier to be tested.
6965 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6967 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6968 need to follow all the rules exactly, such as requiring that the first character not
6971 We allow leading and trailing white space. In between, we do not allow punctuation
6972 (except for underscores and dollar signs), control characters, or embedded white space.
6974 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6975 for the foreseeable future such quoted identifiers are not likely to be an issue.
6977 int is_identifier( const char* s) {
6981 // Skip leading white space
6982 while( isspace( (unsigned char) *s ) )
6986 return 0; // Nothing but white space? Not okay.
6988 // Check each character until we reach white space or
6989 // end-of-string. Letters, digits, underscores, and
6990 // dollar signs are okay. With the exception of periods
6991 // (as in schema.identifier), control characters and other
6992 // punctuation characters are not okay. Anything else
6993 // is okay -- it could for example be part of a multibyte
6994 // UTF8 character such as a letter with diacritical marks,
6995 // and those are allowed.
6997 if( isalnum( (unsigned char) *s )
7001 ; // Fine; keep going
7002 else if( ispunct( (unsigned char) *s )
7003 || iscntrl( (unsigned char) *s ) )
7006 } while( *s && ! isspace( (unsigned char) *s ) );
7008 // If we found any white space in the above loop,
7009 // the rest had better be all white space.
7011 while( isspace( (unsigned char) *s ) )
7015 return 0; // White space was embedded within non-white space
7021 @brief Determine whether to accept a character string as a comparison operator.
7022 @param op The candidate comparison operator.
7023 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
7025 We don't validate the operator for real. We just make sure that it doesn't contain
7026 any semicolons or white space (with special exceptions for a few specific operators).
7027 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
7028 space but it's still not a valid operator, then the database will complain.
7030 Another approach would be to compare the string against a short list of approved operators.
7031 We don't do that because we want to allow custom operators like ">100*", which at this
7032 writing would be difficult or impossible to express otherwise in a JSON query.
7034 int is_good_operator( const char* op ) {
7035 if( !op ) return 0; // Sanity check
7039 if( isspace( (unsigned char) *s ) ) {
7040 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
7041 // and IS NOT DISTINCT FROM.
7042 if( !strcasecmp( op, "similar to" ) )
7044 else if( !strcasecmp( op, "is distinct from" ) )
7046 else if( !strcasecmp( op, "is not distinct from" ) )
7051 else if( ';' == *s )
7059 @name Query Frame Management
7061 The following machinery supports a stack of query frames for use by SELECT().
7063 A query frame caches information about one level of a SELECT query. When we enter
7064 a subquery, we push another query frame onto the stack, and pop it off when we leave.
7066 The query frame stores information about the core class, and about any joined classes
7069 The main purpose is to map table aliases to classes and tables, so that a query can
7070 join to the same table more than once. A secondary goal is to reduce the number of
7071 lookups in the IDL by caching the results.
7075 #define STATIC_CLASS_INFO_COUNT 3
7077 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
7080 @brief Allocate a ClassInfo as raw memory.
7081 @return Pointer to the newly allocated ClassInfo.
7083 Except for the in_use flag, which is used only by the allocation and deallocation
7084 logic, we don't initialize the ClassInfo here.
7086 static ClassInfo* allocate_class_info( void ) {
7087 // In order to reduce the number of mallocs and frees, we return a static
7088 // instance of ClassInfo, if we can find one that we're not already using.
7089 // We rely on the fact that the compiler will implicitly initialize the
7090 // static instances so that in_use == 0.
7093 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7094 if( ! static_class_info[ i ].in_use ) {
7095 static_class_info[ i ].in_use = 1;
7096 return static_class_info + i;
7100 // The static ones are all in use. Malloc one.
7102 return safe_malloc( sizeof( ClassInfo ) );
7106 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
7107 @param info Pointer to the ClassInfo to be cleared.
7109 static void clear_class_info( ClassInfo* info ) {
7114 // Free any malloc'd strings
7116 if( info->alias != info->alias_store )
7117 free( info->alias );
7119 if( info->class_name != info->class_name_store )
7120 free( info->class_name );
7122 free( info->source_def );
7124 info->alias = info->class_name = info->source_def = NULL;
7129 @brief Free a ClassInfo and everything it owns.
7130 @param info Pointer to the ClassInfo to be freed.
7132 static void free_class_info( ClassInfo* info ) {
7137 clear_class_info( info );
7139 // If it's one of the static instances, just mark it as not in use
7142 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
7143 if( info == static_class_info + i ) {
7144 static_class_info[ i ].in_use = 0;
7149 // Otherwise it must have been malloc'd, so free it
7155 @brief Populate an already-allocated ClassInfo.
7156 @param info Pointer to the ClassInfo to be populated.
7157 @param alias Alias for the class. If it is NULL, or an empty string, use the class
7159 @param class Name of the class.
7160 @return Zero if successful, or 1 if not.
7162 Populate the ClassInfo with copies of the alias and class name, and with pointers to
7163 the relevant portions of the IDL for the specified class.
7165 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
7168 osrfLogError( OSRF_LOG_MARK,
7169 "%s ERROR: No ClassInfo available to populate", modulename );
7170 info->alias = info->class_name = info->source_def = NULL;
7171 info->class_def = info->fields = info->links = NULL;
7176 osrfLogError( OSRF_LOG_MARK,
7177 "%s ERROR: No class name provided for lookup", modulename );
7178 info->alias = info->class_name = info->source_def = NULL;
7179 info->class_def = info->fields = info->links = NULL;
7183 // Alias defaults to class name if not supplied
7184 if( ! alias || ! alias[ 0 ] )
7187 // Look up class info in the IDL
7188 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
7190 osrfLogError( OSRF_LOG_MARK,
7191 "%s ERROR: Class %s not defined in IDL", modulename, class );
7192 info->alias = info->class_name = info->source_def = NULL;
7193 info->class_def = info->fields = info->links = NULL;
7195 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
7196 osrfLogError( OSRF_LOG_MARK,
7197 "%s ERROR: Class %s is defined as virtual", modulename, class );
7198 info->alias = info->class_name = info->source_def = NULL;
7199 info->class_def = info->fields = info->links = NULL;
7203 osrfHash* links = osrfHashGet( class_def, "links" );
7205 osrfLogError( OSRF_LOG_MARK,
7206 "%s ERROR: No links defined in IDL for class %s", modulename, class );
7207 info->alias = info->class_name = info->source_def = NULL;
7208 info->class_def = info->fields = info->links = NULL;
7212 osrfHash* fields = osrfHashGet( class_def, "fields" );
7214 osrfLogError( OSRF_LOG_MARK,
7215 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
7216 info->alias = info->class_name = info->source_def = NULL;
7217 info->class_def = info->fields = info->links = NULL;
7221 char* source_def = oilsGetRelation( class_def );
7225 // We got everything we need, so populate the ClassInfo
7226 if( strlen( alias ) > ALIAS_STORE_SIZE )
7227 info->alias = strdup( alias );
7229 strcpy( info->alias_store, alias );
7230 info->alias = info->alias_store;
7233 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
7234 info->class_name = strdup( class );
7236 strcpy( info->class_name_store, class );
7237 info->class_name = info->class_name_store;
7240 info->source_def = source_def;
7242 info->class_def = class_def;
7243 info->links = links;
7244 info->fields = fields;
7249 #define STATIC_FRAME_COUNT 3
7251 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
7254 @brief Allocate a QueryFrame as raw memory.
7255 @return Pointer to the newly allocated QueryFrame.
7257 Except for the in_use flag, which is used only by the allocation and deallocation
7258 logic, we don't initialize the QueryFrame here.
7260 static QueryFrame* allocate_frame( void ) {
7261 // In order to reduce the number of mallocs and frees, we return a static
7262 // instance of QueryFrame, if we can find one that we're not already using.
7263 // We rely on the fact that the compiler will implicitly initialize the
7264 // static instances so that in_use == 0.
7267 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7268 if( ! static_frame[ i ].in_use ) {
7269 static_frame[ i ].in_use = 1;
7270 return static_frame + i;
7274 // The static ones are all in use. Malloc one.
7276 return safe_malloc( sizeof( QueryFrame ) );
7280 @brief Free a QueryFrame, and all the memory it owns.
7281 @param frame Pointer to the QueryFrame to be freed.
7283 static void free_query_frame( QueryFrame* frame ) {
7288 clear_class_info( &frame->core );
7290 // Free the join list
7292 ClassInfo* info = frame->join_list;
7295 free_class_info( info );
7299 frame->join_list = NULL;
7302 // If the frame is a static instance, just mark it as unused
7304 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7305 if( frame == static_frame + i ) {
7306 static_frame[ i ].in_use = 0;
7311 // Otherwise it must have been malloc'd, so free it
7317 @brief Search a given QueryFrame for a specified alias.
7318 @param frame Pointer to the QueryFrame to be searched.
7319 @param target The alias for which to search.
7320 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7322 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7323 if( ! frame || ! target ) {
7327 ClassInfo* found_class = NULL;
7329 if( !strcmp( target, frame->core.alias ) )
7330 return &(frame->core);
7332 ClassInfo* curr_class = frame->join_list;
7333 while( curr_class ) {
7334 if( strcmp( target, curr_class->alias ) )
7335 curr_class = curr_class->next;
7337 found_class = curr_class;
7347 @brief Push a new (blank) QueryFrame onto the stack.
7349 static void push_query_frame( void ) {
7350 QueryFrame* frame = allocate_frame();
7351 frame->join_list = NULL;
7352 frame->next = curr_query;
7354 // Initialize the ClassInfo for the core class
7355 ClassInfo* core = &frame->core;
7356 core->alias = core->class_name = core->source_def = NULL;
7357 core->class_def = core->fields = core->links = NULL;
7363 @brief Pop a QueryFrame off the stack and destroy it.
7365 static void pop_query_frame( void ) {
7370 QueryFrame* popped = curr_query;
7371 curr_query = popped->next;
7373 free_query_frame( popped );
7377 @brief Populate the ClassInfo for the core class.
7378 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7379 class name as an alias.
7380 @param class_name Name of the core class.
7381 @return Zero if successful, or 1 if not.
7383 Populate the ClassInfo of the core class with copies of the alias and class name, and
7384 with pointers to the relevant portions of the IDL for the core class.
7386 static int add_query_core( const char* alias, const char* class_name ) {
7389 if( ! curr_query ) {
7390 osrfLogError( OSRF_LOG_MARK,
7391 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7393 } else if( curr_query->core.alias ) {
7394 osrfLogError( OSRF_LOG_MARK,
7395 "%s ERROR: Core class %s already populated as %s",
7396 modulename, curr_query->core.class_name, curr_query->core.alias );
7400 build_class_info( &curr_query->core, alias, class_name );
7401 if( curr_query->core.alias )
7404 osrfLogError( OSRF_LOG_MARK,
7405 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7411 @brief Search the current QueryFrame for a specified alias.
7412 @param target The alias for which to search.
7413 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7415 static inline ClassInfo* search_alias( const char* target ) {
7416 return search_alias_in_frame( curr_query, target );
7420 @brief Search all levels of query for a specified alias, starting with the current query.
7421 @param target The alias for which to search.
7422 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7424 static ClassInfo* search_all_alias( const char* target ) {
7425 ClassInfo* found_class = NULL;
7426 QueryFrame* curr_frame = curr_query;
7428 while( curr_frame ) {
7429 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7432 curr_frame = curr_frame->next;
7439 @brief Add a class to the list of classes joined to the current query.
7440 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7441 the class name as an alias.
7442 @param classname The name of the class to be added.
7443 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7445 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7447 if( ! classname || ! *classname ) { // sanity check
7448 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7455 const ClassInfo* conflict = search_alias( alias );
7457 osrfLogError( OSRF_LOG_MARK,
7458 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7459 modulename, alias, conflict->class_name );
7463 ClassInfo* info = allocate_class_info();
7465 if( build_class_info( info, alias, classname ) ) {
7466 free_class_info( info );
7470 // Add the new ClassInfo to the join list of the current QueryFrame
7471 info->next = curr_query->join_list;
7472 curr_query->join_list = info;
7478 @brief Destroy all nodes on the query stack.
7480 static void clear_query_stack( void ) {
7486 @brief Implement the set_audit_info method.
7487 @param ctx Pointer to the method context.
7488 @return Zero if successful, or -1 if not.
7490 Issue a SAVEPOINT to the database server.
7495 - workstation id (int)
7497 If user id is not provided the authkey will be used.
7498 For PCRUD the authkey is always used, even if a user is provided.
7500 int setAuditInfo( osrfMethodContext* ctx ) {
7501 if(osrfMethodVerifyContext( ctx )) {
7502 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
7506 // Get the user id from the parameters
7507 const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7509 if( enforce_pcrud || !user_id ) {
7510 timeout_needs_resetting = 1;
7511 const jsonObject* user = verifyUserPCRUD( ctx );
7514 osrfAppRespondComplete( ctx, NULL );
7518 // Not PCRUD and have a user_id?
7519 int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7520 osrfAppRespondComplete( ctx, NULL );
7525 @brief Save a audit info
7526 @param ctx Pointer to the method context.
7527 @param user_id User ID to write as a string
7528 @param ws_id Workstation ID to write as a string
7530 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7531 if( ctx && ctx->session ) {
7532 osrfAppSession* session = ctx->session;
7534 osrfHash* cache = session->userData;
7536 // If the session doesn't already have a hash, create one. Make sure
7537 // that the application session frees the hash when it terminates.
7538 if( NULL == cache ) {
7539 session->userData = cache = osrfNewHash();
7540 osrfHashSetCallback( cache, &sessionDataFree );
7541 ctx->session->userDataFree = &userDataFree;
7544 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7546 osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7548 int errnum = dbi_conn_error( writehandle, &msg );
7551 "%s: Error setting auditor information: %d %s",
7554 msg ? msg : "(No description available)"
7556 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7557 "osrfMethodException", ctx->request, "Error setting auditor info" );
7558 if( !oilsIsDBConnected( writehandle ))
7559 osrfAppSessionPanic( ctx->session );
7562 dbi_result_free( result );
7569 @brief Remove all but safe character from savepoint name
7570 @param sp User-supplied savepoint name
7571 @return sanitized savepoint name, or NULL
7573 The caller is expected to free the returned string. Note that
7574 this function exists only because we can't use PQescapeLiteral
7575 without either forking libdbi or abandoning it.
7577 static char* _sanitize_savepoint_name( const char* sp ) {
7579 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_";
7581 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7582 // and the default value of NAMEDATALEN is 64; that should be long enough
7583 // for our purposes, and it's unlikely that anyone is going to recompile
7584 // PostgreSQL to have a smaller value, so cap the identifier name
7585 // accordingly to avoid the remote chance that someone manages to pass in a
7586 // 12GB savepoint name
7587 const int MAX_LITERAL_NAMELEN = 63;
7590 if (len > MAX_LITERAL_NAMELEN) {
7591 len = MAX_LITERAL_NAMELEN;
7594 char* safeSpName = safe_malloc( len + 1 );
7598 for (j = 0; j < len; j++) {
7599 found = strchr(safe_chars, sp[j]);
7601 safeSpName[ i++ ] = found[0];
7604 safeSpName[ i ] = '\0';
7609 @brief Remove all but safe character from TZ name
7610 @param tz User-supplied TZ name
7611 @return sanitized TZ name, or NULL
7613 The caller is expected to free the returned string. Note that
7614 this function exists only because we can't use PQescapeLiteral
7615 without either forking libdbi or abandoning it.
7617 static char* _sanitize_tz_name( const char* tz ) {
7619 if (NULL == tz) return NULL;
7621 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_/-+";
7623 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7624 // and the default value of NAMEDATALEN is 64; that should be long enough
7625 // for our purposes, and it's unlikely that anyone is going to recompile
7626 // PostgreSQL to have a smaller value, so cap the identifier name
7627 // accordingly to avoid the remote chance that someone manages to pass in a
7628 // 12GB savepoint name
7629 const int MAX_LITERAL_NAMELEN = 63;
7632 if (len > MAX_LITERAL_NAMELEN) {
7633 len = MAX_LITERAL_NAMELEN;
7636 char* safeSpName = safe_malloc( len + 1 );
7640 for (j = 0; j < len; j++) {
7641 found = strchr(safe_chars, tz[j]);
7643 safeSpName[ i++ ] = found[0];
7646 safeSpName[ i ] = '\0';