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 int verifyObjectPCRUD( osrfMethodContext*, osrfHash*, const jsonObject*, int );
123 static const char* org_tree_root( osrfMethodContext* ctx );
124 static jsonObject* single_hash( const char* key, const char* value );
126 static int child_initialized = 0; /* boolean */
128 static dbi_conn writehandle; /* our MASTER db connection */
129 static dbi_conn dbhandle; /* our CURRENT db connection */
130 //static osrfHash * readHandles;
132 // The following points to the top of a stack of QueryFrames. It's a little
133 // confusing because the top level of the query is at the bottom of the stack.
134 static QueryFrame* curr_query = NULL;
136 static dbi_conn writehandle; /* our MASTER db connection */
137 static dbi_conn dbhandle; /* our CURRENT db connection */
138 //static osrfHash * readHandles;
140 static int max_flesh_depth = 100;
142 static int perm_at_threshold = 5;
143 static int enforce_pcrud = 0; // Boolean
144 static char* modulename = NULL;
146 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id);
148 static char* _sanitize_savepoint_name( const char* sp );
151 @brief Connect to the database.
152 @return A database connection if successful, or NULL if not.
154 dbi_conn oilsConnectDB( const char* mod_name ) {
156 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
157 if( dbi_initialize( NULL ) == -1 ) {
158 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
161 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
163 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
164 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
165 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
166 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
167 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
168 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
170 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
171 dbi_conn handle = dbi_conn_new( driver );
174 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
177 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
179 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
180 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
182 if( host ) dbi_conn_set_option( handle, "host", host );
183 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
184 if( user ) dbi_conn_set_option( handle, "username", user );
185 if( pw ) dbi_conn_set_option( handle, "password", pw );
186 if( db ) dbi_conn_set_option( handle, "dbname", db );
194 if( dbi_conn_connect( handle ) < 0 ) {
196 if( dbi_conn_connect( handle ) < 0 ) {
198 dbi_conn_error( handle, &msg );
199 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
200 msg ? msg : "(No description available)" );
205 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
211 @brief Select some options.
212 @param module_name: Name of the server.
213 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
215 This source file is used (at this writing) to implement three different servers:
216 - open-ils.reporter-store
220 These servers behave mostly the same, but they implement different combinations of
221 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
223 Here we use the server name in messages to identify which kind of server issued them.
224 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
226 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
228 module_name = "open-ils.cstore"; // bulletproofing with a default
233 modulename = strdup( module_name );
234 enforce_pcrud = do_pcrud;
235 max_flesh_depth = flesh_depth;
239 @brief Install a database connection.
240 @param conn Pointer to a database connection.
242 In some contexts, @a conn may merely provide a driver so that we can process strings
243 properly, without providing an open database connection.
245 void oilsSetDBConnection( dbi_conn conn ) {
246 dbhandle = writehandle = conn;
250 @brief Determine whether a database connection is alive.
251 @param handle Handle for a database connection.
252 @return 1 if the connection is alive, or zero if it isn't.
254 int oilsIsDBConnected( dbi_conn handle ) {
255 // Do an innocuous SELECT. If it succeeds, the database connection is still good.
256 dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
258 dbi_result_free( result );
261 // This is a terrible, horrible, no good, very bad kludge.
262 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
263 // but because (due to a previous error) the database is ignoring all commands,
264 // even innocuous SELECTs, until the current transaction is rolled back. The only
265 // known way to detect this condition via the dbi library is by looking at the error
266 // message. This approach will break if the language or wording of the message ever
268 // Note: the dbi_conn_ping function purports to determine whether the database
269 // connection is live, but at this writing this function is unreliable and useless.
270 static const char* ok_msg = "ERROR: current transaction is aborted, commands "
271 "ignored until end of transaction block\n";
273 dbi_conn_error( handle, &msg );
274 if( strcmp( msg, ok_msg )) {
275 osrfLogError( OSRF_LOG_MARK, "Database connection isn't working" );
278 return 1; // ignoring SELECT due to previous error; that's okay
283 @brief Get a table name, view name, or subquery for use in a FROM clause.
284 @param class Pointer to the IDL class entry.
285 @return A table name, a view name, or a subquery in parentheses.
287 In some cases the IDL defines a class, not with a table name or a view name, but with
288 a SELECT statement, which may be used as a subquery.
290 char* oilsGetRelation( osrfHash* classdef ) {
292 char* source_def = NULL;
293 const char* tabledef = osrfHashGet( classdef, "tablename" );
296 source_def = strdup( tabledef ); // Return the name of a table or view
298 tabledef = osrfHashGet( classdef, "source_definition" );
300 // Return a subquery, enclosed in parentheses
301 source_def = safe_malloc( strlen( tabledef ) + 3 );
302 source_def[ 0 ] = '(';
303 strcpy( source_def + 1, tabledef );
304 strcat( source_def, ")" );
306 // Not found: return an error
307 const char* classname = osrfHashGet( classdef, "classname" );
312 "%s ERROR No tablename or source_definition for class \"%s\"",
323 @brief Add datatypes from the database to the fields in the IDL.
324 @param handle Handle for a database connection
325 @return Zero if successful, or 1 upon error.
327 For each relevant class in the IDL: ask the database for the datatype of every field.
328 In particular, determine which fields are text fields and which fields are numeric
329 fields, so that we know whether to enclose their values in quotes.
331 int oilsExtendIDL( dbi_conn handle ) {
332 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
333 osrfHash* class = NULL;
334 growing_buffer* query_buf = buffer_init( 64 );
335 int results_found = 0; // boolean
337 // For each class in the IDL...
338 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
339 const char* classname = osrfHashIteratorKey( class_itr );
340 osrfHash* fields = osrfHashGet( class, "fields" );
342 // If the class is virtual, ignore it
343 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
344 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
348 char* tabledef = oilsGetRelation( class );
350 continue; // No such relation -- a query of it would be doomed to failure
352 buffer_reset( query_buf );
353 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
357 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
358 modulename, OSRF_BUFFER_C_STR( query_buf ) );
360 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
365 const char* columnName;
366 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
368 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
371 /* fetch the fieldmapper index */
372 osrfHash* _f = osrfHashGet(fields, columnName);
375 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
377 /* determine the field type and storage attributes */
379 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
381 case DBI_TYPE_INTEGER : {
383 if( !osrfHashGet(_f, "primitive") )
384 osrfHashSet(_f, "number", "primitive");
386 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
387 if( attr & DBI_INTEGER_SIZE8 )
388 osrfHashSet( _f, "INT8", "datatype" );
390 osrfHashSet( _f, "INT", "datatype" );
393 case DBI_TYPE_DECIMAL :
394 if( !osrfHashGet( _f, "primitive" ))
395 osrfHashSet( _f, "number", "primitive" );
397 osrfHashSet( _f, "NUMERIC", "datatype" );
400 case DBI_TYPE_STRING :
401 if( !osrfHashGet( _f, "primitive" ))
402 osrfHashSet( _f, "string", "primitive" );
404 osrfHashSet( _f,"TEXT", "datatype" );
407 case DBI_TYPE_DATETIME :
408 if( !osrfHashGet( _f, "primitive" ))
409 osrfHashSet( _f, "string", "primitive" );
411 osrfHashSet( _f, "TIMESTAMP", "datatype" );
414 case DBI_TYPE_BINARY :
415 if( !osrfHashGet( _f, "primitive" ))
416 osrfHashSet( _f, "string", "primitive" );
418 osrfHashSet( _f, "BYTEA", "datatype" );
423 "Setting [%s] to primitive [%s] and datatype [%s]...",
425 osrfHashGet( _f, "primitive" ),
426 osrfHashGet( _f, "datatype" )
430 } // end while loop for traversing columns of result
431 dbi_result_free( result );
434 int errnum = dbi_conn_error( handle, &msg );
435 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
436 errnum, msg ? msg : "(No description available)" );
437 // We don't check the database connection here. It's routine to get failures at
438 // this point; we routinely try to query tables that don't exist, because they
439 // are defined in the IDL but not in the database.
441 } // end for each class in IDL
443 buffer_free( query_buf );
444 osrfHashIteratorFree( class_itr );
445 child_initialized = 1;
447 if( !results_found ) {
448 osrfLogError( OSRF_LOG_MARK,
449 "No results found for any class -- bad database connection?" );
451 } else if( ! oilsIsDBConnected( handle )) {
452 osrfLogError( OSRF_LOG_MARK,
453 "Unable to extend IDL: database connection isn't working" );
461 @brief Free an osrfHash that stores a transaction ID.
462 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
464 This function is a callback, to be called by the application session when it ends.
465 The application session stores the osrfHash via an opaque pointer.
467 If the osrfHash contains an entry for the key "xact_id", it means that an
468 uncommitted transaction is pending. Roll it back.
470 void userDataFree( void* blob ) {
471 osrfHash* hash = (osrfHash*) blob;
472 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
473 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
475 int errnum = dbi_conn_error( writehandle, &msg );
476 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
477 errnum, msg ? msg : "(No description available)" );
481 if( !dbi_conn_query( writehandle, "SELECT auditor.clear_audit_info();" ) ) {
483 int errnum = dbi_conn_error( writehandle, &msg );
484 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform audit info clearing: %d %s",
485 errnum, msg ? msg : "(No description available)" );
489 osrfHashFree( hash );
493 @name Managing session data
494 @brief Maintain data stored via the userData pointer of the application session.
496 Currently, session-level data is stored in an osrfHash. Other arrangements are
497 possible, and some would be more efficient. The application session calls a
498 callback function to free userData before terminating.
500 Currently, the only data we store at the session level is the transaction id. By this
501 means we can ensure that any pending transactions are rolled back before the application
507 @brief Free an item in the application session's userData.
508 @param key The name of a key for an osrfHash.
509 @param item An opaque pointer to the item associated with the key.
511 We store an osrfHash as userData with the application session, and arrange (by
512 installing userDataFree() as a different callback) for the session to free that
513 osrfHash before terminating.
515 This function is a callback for freeing items in the osrfHash. Currently we store
517 - Transaction id of a pending transaction; a character string. Key: "xact_id".
518 - Authkey; a character string. Key: "authkey".
519 - User object from the authentication server; a jsonObject. Key: "user_login".
521 If we ever store anything else in userData, we will need to revisit this function so
522 that it will free whatever else needs freeing.
524 static void sessionDataFree( char* key, void* item ) {
525 if( !strcmp( key, "xact_id" ) || !strcmp( key, "authkey" ) || !strncmp( key, "rs_size_", 8) )
527 else if( !strcmp( key, "user_login" ) )
528 jsonObjectFree( (jsonObject*) item );
529 else if( !strcmp( key, "pcache" ) )
530 osrfHashFree( (osrfHash*) item );
533 static void pcacheFree( char* key, void* item ) {
534 osrfStringArrayFree( (osrfStringArray*) item );
538 @brief Initialize session cache.
539 @param ctx Pointer to the method context.
541 Create a cache for the session by making the session's userData member point
542 to an osrfHash instance.
544 static osrfHash* initSessionCache( osrfMethodContext* ctx ) {
545 ctx->session->userData = osrfNewHash();
546 osrfHashSetCallback( (osrfHash*) ctx->session->userData, &sessionDataFree );
547 ctx->session->userDataFree = &userDataFree;
548 return ctx->session->userData;
552 @brief Save a transaction id.
553 @param ctx Pointer to the method context.
555 Save the session_id of the current application session as a transaction id.
557 static void setXactId( osrfMethodContext* ctx ) {
558 if( ctx && ctx->session ) {
559 osrfAppSession* session = ctx->session;
561 osrfHash* cache = session->userData;
563 // If the session doesn't already have a hash, create one. Make sure
564 // that the application session frees the hash when it terminates.
566 cache = initSessionCache( ctx );
568 // Save the transaction id in the hash, with the key "xact_id"
569 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
574 @brief Get the transaction ID for the current transaction, if any.
575 @param ctx Pointer to the method context.
576 @return Pointer to the transaction ID.
578 The return value points to an internal buffer, and will become invalid upon issuing
579 a commit or rollback.
581 static inline const char* getXactId( osrfMethodContext* ctx ) {
582 if( ctx && ctx->session && ctx->session->userData )
583 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
589 @brief Clear the current transaction id.
590 @param ctx Pointer to the method context.
592 static inline void clearXactId( osrfMethodContext* ctx ) {
593 if( ctx && ctx->session && ctx->session->userData )
594 osrfHashRemove( ctx->session->userData, "xact_id" );
599 @brief Stash the location for a particular perm in the sessionData cache
600 @param ctx Pointer to the method context.
601 @param perm Name of the permission we're looking at
602 @param array StringArray of perm location ids
604 static void setPermLocationCache( osrfMethodContext* ctx, const char* perm, osrfStringArray* locations ) {
605 if( ctx && ctx->session ) {
606 osrfAppSession* session = ctx->session;
608 osrfHash* cache = session->userData;
610 // If the session doesn't already have a hash, create one. Make sure
611 // that the application session frees the hash when it terminates.
613 cache = initSessionCache( ctx );
615 osrfHash* pcache = osrfHashGet(cache, "pcache");
617 if( NULL == pcache ) {
618 pcache = osrfNewHash();
619 osrfHashSetCallback( pcache, &pcacheFree );
620 osrfHashSet( cache, pcache, "pcache" );
623 if( perm && locations )
624 osrfHashSet( pcache, locations, strdup(perm) );
629 @brief Grab stashed location for a particular perm in the sessionData cache
630 @param ctx Pointer to the method context.
631 @param perm Name of the permission we're looking at
633 static osrfStringArray* getPermLocationCache( osrfMethodContext* ctx, const char* perm ) {
634 if( ctx && ctx->session ) {
635 osrfAppSession* session = ctx->session;
636 osrfHash* cache = session->userData;
638 osrfHash* pcache = osrfHashGet(cache, "pcache");
640 return osrfHashGet( pcache, perm );
649 @brief Save the user's login in the userData for the current application session.
650 @param ctx Pointer to the method context.
651 @param user_login Pointer to the user login object to be cached (we cache the original,
654 If @a user_login is NULL, remove the user login if one is already cached.
656 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
657 if( ctx && ctx->session ) {
658 osrfAppSession* session = ctx->session;
660 osrfHash* cache = session->userData;
662 // If the session doesn't already have a hash, create one. Make sure
663 // that the application session frees the hash when it terminates.
665 cache = initSessionCache( ctx );
668 osrfHashSet( cache, user_login, "user_login" );
670 osrfHashRemove( cache, "user_login" );
675 @brief Get the user login object for the current application session, if any.
676 @param ctx Pointer to the method context.
677 @return Pointer to the user login object if found; otherwise NULL.
679 The user login object was returned from the authentication server, and then cached so
680 we don't have to call the authentication server again for the same user.
682 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
683 if( ctx && ctx->session && ctx->session->userData )
684 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
690 @brief Save a copy of an authkey in the userData of the current application session.
691 @param ctx Pointer to the method context.
692 @param authkey The authkey to be saved.
694 If @a authkey is NULL, remove the authkey if one is already cached.
696 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
697 if( ctx && ctx->session && authkey ) {
698 osrfAppSession* session = ctx->session;
699 osrfHash* cache = session->userData;
701 // If the session doesn't already have a hash, create one. Make sure
702 // that the application session frees the hash when it terminates.
704 cache = initSessionCache( ctx );
706 // Save the transaction id in the hash, with the key "xact_id"
707 if( authkey && *authkey )
708 osrfHashSet( cache, strdup( authkey ), "authkey" );
710 osrfHashRemove( cache, "authkey" );
715 @brief Reset the login timeout.
716 @param authkey The authentication key for the current login session.
717 @param now The current time.
718 @return Zero if successful, or 1 if not.
720 Tell the authentication server to reset the timeout so that the login session won't
721 expire for a while longer.
723 We could dispense with the @a now parameter by calling time(). But we just called
724 time() in order to decide whether to reset the timeout, so we might as well reuse
725 the result instead of calling time() again.
727 static int reset_timeout( const char* authkey, time_t now ) {
728 jsonObject* auth_object = jsonNewObject( authkey );
730 // Ask the authentication server to reset the timeout. It returns an event
731 // indicating success or failure.
732 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
733 "open-ils.auth.session.reset_timeout", auth_object );
734 jsonObjectFree( auth_object );
736 if( !result || result->type != JSON_HASH ) {
737 osrfLogError( OSRF_LOG_MARK,
738 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
739 jsonObjectFree( result );
740 return 1; // Not the right sort of object returned
743 const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
744 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
745 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
746 jsonObjectFree( result );
747 return 1; // Return code from method not available
750 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
751 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
753 desc = "(No reason available)"; // failsafe; shouldn't happen
754 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
755 jsonObjectFree( result );
759 // Revise our local proxy for the timeout deadline
760 // by a smallish fraction of the timeout interval
761 const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
763 timeout = "1"; // failsafe; shouldn't happen
764 time_next_reset = now + atoi( timeout ) / 15;
766 jsonObjectFree( result );
767 return 0; // Successfully reset timeout
771 @brief Get the authkey string for the current application session, if any.
772 @param ctx Pointer to the method context.
773 @return Pointer to the cached authkey if found; otherwise NULL.
775 If present, the authkey string was cached from a previous method call.
777 static const char* getAuthkey( osrfMethodContext* ctx ) {
778 if( ctx && ctx->session && ctx->session->userData ) {
779 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
780 // LFW recent changes mean the userData hash gets set up earlier, but
781 // doesn't necessarily have an authkey yet
785 // Possibly reset the authentication timeout to keep the login alive. We do so
786 // no more than once per method call, and not at all if it has been only a short
787 // time since the last reset.
789 // Here we reset explicitly, if at all. We also implicitly reset the timeout
790 // whenever we call the "open-ils.auth.session.retrieve" method.
791 if( timeout_needs_resetting ) {
792 time_t now = time( NULL );
793 if( now >= time_next_reset && reset_timeout( authkey, now ) )
794 authkey = NULL; // timeout has apparently expired already
797 timeout_needs_resetting = 0;
805 @brief Implement the transaction.begin method.
806 @param ctx Pointer to the method context.
807 @return Zero if successful, or -1 upon error.
809 Start a transaction. Save a transaction ID for future reference.
812 - authkey (PCRUD only)
814 Return to client: Transaction ID
816 int beginTransaction( osrfMethodContext* ctx ) {
817 if(osrfMethodVerifyContext( ctx )) {
818 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
822 if( enforce_pcrud ) {
823 timeout_needs_resetting = 1;
824 const jsonObject* user = verifyUserPCRUD( ctx );
829 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
832 int errnum = dbi_conn_error( writehandle, &msg );
833 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
834 modulename, errnum, msg ? msg : "(No description available)" );
835 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
836 "osrfMethodException", ctx->request, "Error starting transaction" );
837 if( !oilsIsDBConnected( writehandle ))
838 osrfAppSessionPanic( ctx->session );
841 dbi_result_free( result );
843 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
844 osrfAppRespondComplete( ctx, ret );
845 jsonObjectFree( ret );
851 @brief Implement the savepoint.set method.
852 @param ctx Pointer to the method context.
853 @return Zero if successful, or -1 if not.
855 Issue a SAVEPOINT to the database server.
858 - authkey (PCRUD only)
861 Return to client: Savepoint name
863 int setSavepoint( osrfMethodContext* ctx ) {
864 if(osrfMethodVerifyContext( ctx )) {
865 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
870 if( enforce_pcrud ) {
872 timeout_needs_resetting = 1;
873 const jsonObject* user = verifyUserPCRUD( ctx );
878 // Verify that a transaction is pending
879 const char* trans_id = getXactId( ctx );
880 if( NULL == trans_id ) {
881 osrfAppSessionStatus(
883 OSRF_STATUS_INTERNALSERVERERROR,
884 "osrfMethodException",
886 "No active transaction -- required for savepoints"
891 // Get the savepoint name from the method params
892 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
895 osrfLogWarning(OSRF_LOG_MARK, "savepoint.set called with no name");
899 char *safeSpName = _sanitize_savepoint_name( spName );
901 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", safeSpName );
905 int errnum = dbi_conn_error( writehandle, &msg );
908 "%s: Error creating savepoint %s in transaction %s: %d %s",
913 msg ? msg : "(No description available)"
915 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
916 "osrfMethodException", ctx->request, "Error creating savepoint" );
917 if( !oilsIsDBConnected( writehandle ))
918 osrfAppSessionPanic( ctx->session );
921 dbi_result_free( result );
922 jsonObject* ret = jsonNewObject( spName );
923 osrfAppRespondComplete( ctx, ret );
924 jsonObjectFree( ret );
930 @brief Implement the savepoint.release method.
931 @param ctx Pointer to the method context.
932 @return Zero if successful, or -1 if not.
934 Issue a RELEASE SAVEPOINT to the database server.
937 - authkey (PCRUD only)
940 Return to client: Savepoint name
942 int releaseSavepoint( osrfMethodContext* ctx ) {
943 if(osrfMethodVerifyContext( ctx )) {
944 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
949 if( enforce_pcrud ) {
951 timeout_needs_resetting = 1;
952 const jsonObject* user = verifyUserPCRUD( ctx );
957 // Verify that a transaction is pending
958 const char* trans_id = getXactId( ctx );
959 if( NULL == trans_id ) {
960 osrfAppSessionStatus(
962 OSRF_STATUS_INTERNALSERVERERROR,
963 "osrfMethodException",
965 "No active transaction -- required for savepoints"
970 // Get the savepoint name from the method params
971 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
974 osrfLogWarning(OSRF_LOG_MARK, "savepoint.release called with no name");
978 char *safeSpName = _sanitize_savepoint_name( spName );
980 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", safeSpName );
984 int errnum = dbi_conn_error( writehandle, &msg );
987 "%s: Error releasing savepoint %s in transaction %s: %d %s",
992 msg ? msg : "(No description available)"
994 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
995 "osrfMethodException", ctx->request, "Error releasing savepoint" );
996 if( !oilsIsDBConnected( writehandle ))
997 osrfAppSessionPanic( ctx->session );
1000 dbi_result_free( result );
1001 jsonObject* ret = jsonNewObject( spName );
1002 osrfAppRespondComplete( ctx, ret );
1003 jsonObjectFree( ret );
1009 @brief Implement the savepoint.rollback method.
1010 @param ctx Pointer to the method context.
1011 @return Zero if successful, or -1 if not.
1013 Issue a ROLLBACK TO SAVEPOINT to the database server.
1016 - authkey (PCRUD only)
1019 Return to client: Savepoint name
1021 int rollbackSavepoint( osrfMethodContext* ctx ) {
1022 if(osrfMethodVerifyContext( ctx )) {
1023 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1028 if( enforce_pcrud ) {
1030 timeout_needs_resetting = 1;
1031 const jsonObject* user = verifyUserPCRUD( ctx );
1036 // Verify that a transaction is pending
1037 const char* trans_id = getXactId( ctx );
1038 if( NULL == trans_id ) {
1039 osrfAppSessionStatus(
1041 OSRF_STATUS_INTERNALSERVERERROR,
1042 "osrfMethodException",
1044 "No active transaction -- required for savepoints"
1049 // Get the savepoint name from the method params
1050 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1053 osrfLogWarning(OSRF_LOG_MARK, "savepoint.rollback called with no name");
1057 char *safeSpName = _sanitize_savepoint_name( spName );
1059 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", safeSpName );
1063 int errnum = dbi_conn_error( writehandle, &msg );
1066 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
1071 msg ? msg : "(No description available)"
1073 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1074 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1075 if( !oilsIsDBConnected( writehandle ))
1076 osrfAppSessionPanic( ctx->session );
1079 dbi_result_free( result );
1080 jsonObject* ret = jsonNewObject( spName );
1081 osrfAppRespondComplete( ctx, ret );
1082 jsonObjectFree( ret );
1088 @brief Implement the transaction.commit method.
1089 @param ctx Pointer to the method context.
1090 @return Zero if successful, or -1 if not.
1092 Issue a COMMIT to the database server.
1095 - authkey (PCRUD only)
1097 Return to client: Transaction ID.
1099 int commitTransaction( osrfMethodContext* ctx ) {
1100 if(osrfMethodVerifyContext( ctx )) {
1101 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1105 if( enforce_pcrud ) {
1106 timeout_needs_resetting = 1;
1107 const jsonObject* user = verifyUserPCRUD( ctx );
1112 // Verify that a transaction is pending
1113 const char* trans_id = getXactId( ctx );
1114 if( NULL == trans_id ) {
1115 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1116 "osrfMethodException", ctx->request, "No active transaction to commit" );
1120 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1123 int errnum = dbi_conn_error( writehandle, &msg );
1124 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1125 modulename, errnum, msg ? msg : "(No description available)" );
1126 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1127 "osrfMethodException", ctx->request, "Error committing transaction" );
1128 if( !oilsIsDBConnected( writehandle ))
1129 osrfAppSessionPanic( ctx->session );
1132 dbi_result_free( result );
1133 jsonObject* ret = jsonNewObject( trans_id );
1134 osrfAppRespondComplete( ctx, ret );
1135 jsonObjectFree( ret );
1142 @brief Implement the transaction.rollback method.
1143 @param ctx Pointer to the method context.
1144 @return Zero if successful, or -1 if not.
1146 Issue a ROLLBACK to the database server.
1149 - authkey (PCRUD only)
1151 Return to client: Transaction ID
1153 int rollbackTransaction( osrfMethodContext* ctx ) {
1154 if( osrfMethodVerifyContext( ctx )) {
1155 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1159 if( enforce_pcrud ) {
1160 timeout_needs_resetting = 1;
1161 const jsonObject* user = verifyUserPCRUD( ctx );
1166 // Verify that a transaction is pending
1167 const char* trans_id = getXactId( ctx );
1168 if( NULL == trans_id ) {
1169 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1170 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1174 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1177 int errnum = dbi_conn_error( writehandle, &msg );
1178 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1179 modulename, errnum, msg ? msg : "(No description available)" );
1180 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1181 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1182 if( !oilsIsDBConnected( writehandle ))
1183 osrfAppSessionPanic( ctx->session );
1186 dbi_result_free( result );
1187 jsonObject* ret = jsonNewObject( trans_id );
1188 osrfAppRespondComplete( ctx, ret );
1189 jsonObjectFree( ret );
1196 @brief Implement the "search" method.
1197 @param ctx Pointer to the method context.
1198 @return Zero if successful, or -1 if not.
1201 - authkey (PCRUD only)
1202 - WHERE clause, as jsonObject
1203 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1205 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1206 Optionally flesh linked fields.
1208 int doSearch( osrfMethodContext* ctx ) {
1209 if( osrfMethodVerifyContext( ctx )) {
1210 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1215 timeout_needs_resetting = 1;
1217 jsonObject* where_clause;
1218 jsonObject* rest_of_query;
1220 if( enforce_pcrud ) {
1221 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1222 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1224 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1225 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1228 if( !where_clause ) {
1229 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1233 // Get the class metadata
1234 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1235 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1239 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1241 osrfAppRespondComplete( ctx, NULL );
1245 // doFieldmapperSearch() now takes care of our responding for us
1246 // // Return each row to the client
1247 // jsonObject* cur = 0;
1248 // unsigned long res_idx = 0;
1250 // while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1251 // // We used to discard based on perms here, but now that's
1252 // // inside doFieldmapperSearch()
1253 // osrfAppRespond( ctx, cur );
1256 jsonObjectFree( obj );
1258 osrfAppRespondComplete( ctx, NULL );
1263 @brief Implement the "id_list" method.
1264 @param ctx Pointer to the method context.
1265 @param err Pointer through which to return an error code.
1266 @return Zero if successful, or -1 if not.
1269 - authkey (PCRUD only)
1270 - WHERE clause, as jsonObject
1271 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1273 Return to client: The primary key values for all rows of the relevant class that
1274 satisfy a specified WHERE clause.
1276 This method relies on the assumption that every class has a primary key consisting of
1279 int doIdList( osrfMethodContext* ctx ) {
1280 if( osrfMethodVerifyContext( ctx )) {
1281 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1286 timeout_needs_resetting = 1;
1288 jsonObject* where_clause;
1289 jsonObject* rest_of_query;
1291 // We use the where clause without change. But we need to massage the rest of the
1292 // query, so we work with a copy of it instead of modifying the original.
1294 if( enforce_pcrud ) {
1295 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1296 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1298 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1299 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1302 if( !where_clause ) {
1303 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1307 // Eliminate certain SQL clauses, if present.
1308 if( rest_of_query ) {
1309 jsonObjectRemoveKey( rest_of_query, "select" );
1310 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1311 jsonObjectRemoveKey( rest_of_query, "flesh" );
1312 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1314 rest_of_query = jsonNewObjectType( JSON_HASH );
1317 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1319 // Get the class metadata
1320 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1321 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1323 // Build a SELECT list containing just the primary key,
1324 // i.e. like { "classname":["keyname"] }
1325 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1327 // Load array with name of primary key
1328 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1329 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1330 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1332 jsonObjectSetKey( rest_of_query, "select", select_clause );
1337 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1339 jsonObjectFree( rest_of_query );
1341 osrfAppRespondComplete( ctx, NULL );
1345 // Return each primary key value to the client
1347 unsigned long res_idx = 0;
1348 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1349 // We used to discard based on perms here, but now that's
1350 // inside doFieldmapperSearch()
1351 osrfAppRespond( ctx,
1352 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1355 jsonObjectFree( obj );
1356 osrfAppRespondComplete( ctx, NULL );
1361 @brief Verify that we have a valid class reference.
1362 @param ctx Pointer to the method context.
1363 @param param Pointer to the method parameters.
1364 @return 1 if the class reference is valid, or zero if it isn't.
1366 The class of the method params must match the class to which the method id devoted.
1367 For PCRUD there are additional restrictions.
1369 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1371 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1372 osrfHash* class = osrfHashGet( method_meta, "class" );
1374 // Compare the method's class to the parameters' class
1375 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1377 // Oops -- they don't match. Complain.
1378 growing_buffer* msg = buffer_init( 128 );
1381 "%s: %s method for type %s was passed a %s",
1383 osrfHashGet( method_meta, "methodtype" ),
1384 osrfHashGet( class, "classname" ),
1385 param->classname ? param->classname : "(null)"
1388 char* m = buffer_release( msg );
1389 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1397 return verifyObjectPCRUD( ctx, class, param, 1 );
1403 @brief (PCRUD only) Verify that the user is properly logged in.
1404 @param ctx Pointer to the method context.
1405 @return If the user is logged in, a pointer to the user object from the authentication
1406 server; otherwise NULL.
1408 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1410 // Get the authkey (the first method parameter)
1411 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1413 // See if we have the same authkey, and a user object,
1414 // locally cached from a previous call
1415 const char* cached_authkey = getAuthkey( ctx );
1416 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1417 const jsonObject* cached_user = getUserLogin( ctx );
1422 // We have no matching authentication data in the cache. Authenticate from scratch.
1423 jsonObject* auth_object = jsonNewObject( auth );
1425 // Fetch the user object from the authentication server
1426 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1428 jsonObjectFree( auth_object );
1430 if( !user->classname || strcmp(user->classname, "au" )) {
1432 growing_buffer* msg = buffer_init( 128 );
1435 "%s: permacrud received a bad auth token: %s",
1440 char* m = buffer_release( msg );
1441 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1445 jsonObjectFree( user );
1447 } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
1448 // Failed to set audit information - But note that write_audit_info already set error information.
1449 jsonObjectFree( user );
1453 setUserLogin( ctx, user );
1454 setAuthkey( ctx, auth );
1456 // Allow ourselves up to a second before we have to reset the login timeout.
1457 // It would be nice to use some fraction of the timeout interval enforced by the
1458 // authentication server, but that value is not readily available at this point.
1459 // Instead, we use a conservative default interval.
1460 time_next_reset = time( NULL ) + 1;
1466 @brief For PCRUD: Determine whether the current user may access the current row.
1467 @param ctx Pointer to the method context.
1468 @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1469 @param obj Pointer to the row being potentially accessed.
1470 @return 1 if access is permitted, or 0 if it isn't.
1472 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1474 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1476 dbhandle = writehandle;
1478 // Figure out what class and method are involved
1479 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1480 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1483 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1484 if (rs_size_from_hash) {
1485 rs_size = *rs_size_from_hash;
1486 osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1490 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1491 // contexts we will do another lookup of the current row, even if we already have a
1492 // previously fetched row image, because the row image in hand may not include the
1493 // foreign key(s) that we need.
1495 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1496 // but they aren't implemented yet.
1499 if( *method_type == 's' || *method_type == 'i' ) {
1500 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1502 } else if( *method_type == 'u' || *method_type == 'd' ) {
1503 fetch = 1; // MUST go to the db for the object for update and delete
1506 // Get the appropriate permacrud entry from the IDL, depending on method type
1507 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1509 // No permacrud for this method type on this class
1511 growing_buffer* msg = buffer_init( 128 );
1514 "%s: %s on class %s has no permacrud IDL entry",
1516 osrfHashGet( method_metadata, "methodtype" ),
1517 osrfHashGet( class, "classname" )
1520 char* m = buffer_release( msg );
1521 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1522 "osrfMethodException", ctx->request, m );
1529 // Get the user id, and make sure the user is logged in
1530 const jsonObject* user = verifyUserPCRUD( ctx );
1532 return 0; // Not logged in? No access.
1534 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1536 // Get a list of permissions from the permacrud entry.
1537 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1538 if( permission->size == 0 ) {
1541 "No permissions required for this action (class %s), passing through",
1542 osrfHashGet(class, "classname")
1547 // Build a list of org units that own the row. This is fairly convoluted because there
1548 // are several different ways that an org unit may own the row, as defined by the
1551 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1552 // identifying an owning org_unit..
1553 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1555 // Foreign context adds a layer of indirection. The row points to some other row that
1556 // an org unit may own. The "jump" attribute, if present, adds another layer of
1558 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1560 // The following string array stores the list of org units. (We don't have a thingie
1561 // for storing lists of integers, so we fake it with a list of strings.)
1562 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1565 const char* pkey_value = NULL;
1566 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1567 // If the global_required attribute is present and true, then the only owning
1568 // org unit is the root org unit, i.e. the one with no parent.
1569 osrfLogDebug( OSRF_LOG_MARK,
1570 "global-level permissions required, fetching top of the org tree" );
1572 // no need to check perms for org tree root retrieval
1573 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1574 // check for perm at top of org tree
1575 const char* org_tree_root_id = org_tree_root( ctx );
1576 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1578 if( org_tree_root_id ) {
1579 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1580 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1582 osrfStringArrayFree( context_org_array );
1587 // If the global_required attribute is absent or false, then we look for
1588 // local and/or foreign context. In order to find the relevant foreign
1589 // keys, we must either read the relevant row from the database, or look at
1590 // the image of the row that we already have in memory.
1592 // Even if we have an image of the row in memory, that image may not include the
1593 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1594 // of the row to make sure that we have what we need.
1596 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1597 "fetching context org ids" );
1598 const char* pkey = osrfHashGet( class, "primarykey" );
1599 jsonObject *param = NULL;
1602 // There is no primary key, so we can't do a fresh lookup. Use the row
1603 // image that we already have. If it doesn't have everything we need, too bad.
1605 param = jsonObjectClone( obj );
1606 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1607 } else if( obj->classname ) {
1608 pkey_value = oilsFMGetStringConst( obj, pkey );
1610 param = jsonObjectClone( obj );
1611 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1614 pkey_value = jsonObjectGetString( obj );
1616 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1617 "of %s and retrieving from the database", pkey_value );
1621 // Fetch the row so that we can look at the foreign key(s)
1622 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1623 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1624 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1625 jsonObjectFree( _tmp_params );
1626 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1628 param = jsonObjectExtractIndex( _list, 0 );
1629 jsonObjectFree( _list );
1633 // The row doesn't exist. Complain, and deny access.
1634 osrfLogDebug( OSRF_LOG_MARK,
1635 "Object not found in the database with primary key %s of %s",
1638 growing_buffer* msg = buffer_init( 128 );
1641 "%s: no object found with primary key %s of %s",
1647 char* m = buffer_release( msg );
1648 osrfAppSessionStatus(
1650 OSRF_STATUS_INTERNALSERVERERROR,
1651 "osrfMethodException",
1660 if( local_context && local_context->size > 0 ) {
1661 // The IDL provides a list of column names for the foreign keys denoting
1662 // local context, i.e. columns identifying owing org units directly. Look up
1663 // the value of each one, and if it isn't null, add it to the list of org units.
1664 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1665 local_context->size );
1667 const char* lcontext = NULL;
1668 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1669 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1670 if( fkey_value ) { // if not null
1671 osrfStringArrayAdd( context_org_array, fkey_value );
1674 "adding class-local field %s (value: %s) to the context org list",
1676 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1682 if( foreign_context ) {
1683 unsigned long class_count = osrfHashGetCount( foreign_context );
1684 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1686 if( class_count > 0 ) {
1688 // The IDL provides a list of foreign key columns pointing to rows that
1689 // an org unit may own. Follow each link, identify the owning org unit,
1690 // and add it to the list.
1691 osrfHash* fcontext = NULL;
1692 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1693 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1694 // For each class to which a foreign key points:
1695 const char* class_name = osrfHashIteratorKey( class_itr );
1696 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1700 "%d foreign context fields(s) specified for class %s",
1701 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1705 // Get the name of the key field in the foreign table
1706 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1708 // Get the value of the foreign key pointing to the foreign table
1709 char* foreign_pkey_value =
1710 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1711 if( !foreign_pkey_value )
1712 continue; // Foreign key value is null; skip it
1714 // Look up the row to which the foreign key points
1715 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1717 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1718 jsonObject* _list = doFieldmapperSearch(
1719 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1720 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1722 jsonObject* _fparam = NULL;
1723 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1724 _fparam = jsonObjectExtractIndex( _list, 0 );
1726 jsonObjectFree( _tmp_params );
1727 jsonObjectFree( _list );
1729 // At this point _fparam either points to the row identified by the
1730 // foreign key, or it's NULL (no such row found).
1732 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1734 const char* bad_class = NULL; // For noting failed lookups
1736 bad_class = class_name; // Referenced row not found
1737 else if( jump_list ) {
1738 // Follow a chain of rows, linked by foreign keys, to find an owner
1739 const char* flink = NULL;
1741 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1742 // For each entry in the jump list. Each entry (i.e. flink) is
1743 // the name of a foreign key column in the current row.
1745 // From the IDL, get the linkage information for the next jump
1746 osrfHash* foreign_link_hash =
1747 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1749 // Get the class metadata for the class
1750 // to which the foreign key points
1751 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1752 osrfHashGet( foreign_link_hash, "class" ));
1754 // Get the name of the referenced key of that class
1755 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1757 // Get the value of the foreign key pointing to that class
1758 free( foreign_pkey_value );
1759 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1760 if( !foreign_pkey_value )
1761 break; // Foreign key is null; quit looking
1763 // Build a WHERE clause for the lookup
1764 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1767 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1768 _tmp_params, NULL, &err );
1770 // Get the resulting row
1771 jsonObjectFree( _fparam );
1772 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1773 _fparam = jsonObjectExtractIndex( _list, 0 );
1775 // Referenced row not found
1777 bad_class = osrfHashGet( foreign_link_hash, "class" );
1780 jsonObjectFree( _tmp_params );
1781 jsonObjectFree( _list );
1787 // We had a foreign key pointing to such-and-such a row, but then
1788 // we couldn't fetch that row. The data in the database are in an
1789 // inconsistent state; the database itself may even be corrupted.
1790 growing_buffer* msg = buffer_init( 128 );
1793 "%s: no object of class %s found with primary key %s of %s",
1797 foreign_pkey_value ? foreign_pkey_value : "(null)"
1800 char* m = buffer_release( msg );
1801 osrfAppSessionStatus(
1803 OSRF_STATUS_INTERNALSERVERERROR,
1804 "osrfMethodException",
1810 osrfHashIteratorFree( class_itr );
1811 free( foreign_pkey_value );
1812 jsonObjectFree( param );
1817 free( foreign_pkey_value );
1820 // Examine each context column of the foreign row,
1821 // and add its value to the list of org units.
1823 const char* foreign_field = NULL;
1824 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1825 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1826 osrfStringArrayAdd( context_org_array,
1827 oilsFMGetStringConst( _fparam, foreign_field ));
1828 osrfLogDebug( OSRF_LOG_MARK,
1829 "adding foreign class %s field %s (value: %s) "
1830 "to the context org list",
1833 osrfStringArrayGetString(
1834 context_org_array, context_org_array->size - 1 )
1838 jsonObjectFree( _fparam );
1842 osrfHashIteratorFree( class_itr );
1846 jsonObjectFree( param );
1849 const char* context_org = NULL;
1850 const char* perm = NULL;
1853 // For every combination of permission and context org unit: call a stored procedure
1854 // to determine if the user has this permission in the context of this org unit.
1855 // If the answer is yes at any point, then we're done, and the user has permission.
1856 // In other words permissions are additive.
1858 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1861 osrfStringArray* pcache = NULL;
1862 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
1863 pcache = getPermLocationCache(ctx, perm);
1866 pcache = osrfNewStringArray(0);
1868 result = dbi_conn_queryf(
1870 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
1878 "Received a result for permission [%s] for user %d",
1883 if( dbi_result_first_row( result )) {
1885 jsonObject* return_val = oilsMakeJSONFromResult( result );
1886 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
1887 jsonObjectFree( return_val );
1888 } while( dbi_result_next_row( result ));
1890 setPermLocationCache(ctx, perm, pcache);
1893 dbi_result_free( result );
1899 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1901 if (rs_size > perm_at_threshold) {
1902 if (osrfStringArrayContains( pcache, context_org )) {
1911 "Checking object permission [%s] for user %d "
1912 "on object %s (class %s) at org %d",
1916 osrfHashGet( class, "classname" ),
1920 result = dbi_conn_queryf(
1922 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1925 osrfHashGet( class, "classname" ),
1933 "Received a result for object permission [%s] "
1934 "for user %d on object %s (class %s) at org %d",
1938 osrfHashGet( class, "classname" ),
1942 if( dbi_result_first_row( result )) {
1943 jsonObject* return_val = oilsMakeJSONFromResult( result );
1944 const char* has_perm = jsonObjectGetString(
1945 jsonObjectGetKeyConst( return_val, "has_perm" ));
1949 "Status of object permission [%s] for user %d "
1950 "on object %s (class %s) at org %d is %s",
1954 osrfHashGet(class, "classname"),
1959 if( *has_perm == 't' )
1961 jsonObjectFree( return_val );
1964 dbi_result_free( result );
1969 int errnum = dbi_conn_error( writehandle, &msg );
1970 osrfLogWarning( OSRF_LOG_MARK,
1971 "Unable to call check object permissions: %d, %s",
1972 errnum, msg ? msg : "(No description available)" );
1973 if( !oilsIsDBConnected( writehandle ))
1974 osrfAppSessionPanic( ctx->session );
1978 if (rs_size > perm_at_threshold) break;
1980 osrfLogDebug( OSRF_LOG_MARK,
1981 "Checking non-object permission [%s] for user %d at org %d",
1982 perm, userid, atoi(context_org) );
1983 result = dbi_conn_queryf(
1985 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1992 osrfLogDebug( OSRF_LOG_MARK,
1993 "Received a result for permission [%s] for user %d at org %d",
1994 perm, userid, atoi( context_org ));
1995 if( dbi_result_first_row( result )) {
1996 jsonObject* return_val = oilsMakeJSONFromResult( result );
1997 const char* has_perm = jsonObjectGetString(
1998 jsonObjectGetKeyConst( return_val, "has_perm" ));
1999 osrfLogDebug( OSRF_LOG_MARK,
2000 "Status of permission [%s] for user %d at org %d is [%s]",
2001 perm, userid, atoi( context_org ), has_perm );
2002 if( *has_perm == 't' )
2004 jsonObjectFree( return_val );
2007 dbi_result_free( result );
2012 int errnum = dbi_conn_error( writehandle, &msg );
2013 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
2014 errnum, msg ? msg : "(No description available)" );
2015 if( !oilsIsDBConnected( writehandle ))
2016 osrfAppSessionPanic( ctx->session );
2025 osrfStringArrayFree( context_org_array );
2031 @brief Look up the root of the org_unit tree.
2032 @param ctx Pointer to the method context.
2033 @return The id of the root org unit, as a character string.
2035 Query actor.org_unit where parent_ou is null, and return the id as a string.
2037 This function assumes that there is only one root org unit, i.e. that we
2038 have a single tree, not a forest.
2040 The calling code is responsible for freeing the returned string.
2042 static const char* org_tree_root( osrfMethodContext* ctx ) {
2044 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
2045 static time_t last_lookup_time = 0;
2046 time_t current_time = time( NULL );
2048 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2049 // We successfully looked this up less than an hour ago.
2050 // It's not likely to have changed since then.
2051 return strdup( cached_root_id );
2053 last_lookup_time = current_time;
2056 jsonObject* where_clause = single_hash( "parent_ou", NULL );
2057 jsonObject* result = doFieldmapperSearch(
2058 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2059 jsonObjectFree( where_clause );
2061 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2064 jsonObjectFree( result );
2066 growing_buffer* msg = buffer_init( 128 );
2067 OSRF_BUFFER_ADD( msg, modulename );
2068 OSRF_BUFFER_ADD( msg,
2069 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2071 char* m = buffer_release( msg );
2072 osrfAppSessionStatus( ctx->session,
2073 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2076 cached_root_id[ 0 ] = '\0';
2080 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2081 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2083 strcpy( cached_root_id, root_org_unit_id );
2084 jsonObjectFree( result );
2085 return cached_root_id;
2089 @brief Create a JSON_HASH with a single key/value pair.
2090 @param key The key of the key/value pair.
2091 @param value the value of the key/value pair.
2092 @return Pointer to a newly created jsonObject of type JSON_HASH.
2094 The value of the key/value is either a string or (if @a value is NULL) a null.
2096 static jsonObject* single_hash( const char* key, const char* value ) {
2098 if( ! key ) key = "";
2100 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2101 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2106 int doCreate( osrfMethodContext* ctx ) {
2107 if(osrfMethodVerifyContext( ctx )) {
2108 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2113 timeout_needs_resetting = 1;
2115 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2116 jsonObject* target = NULL;
2117 jsonObject* options = NULL;
2119 if( enforce_pcrud ) {
2120 target = jsonObjectGetIndex( ctx->params, 1 );
2121 options = jsonObjectGetIndex( ctx->params, 2 );
2123 target = jsonObjectGetIndex( ctx->params, 0 );
2124 options = jsonObjectGetIndex( ctx->params, 1 );
2127 if( !verifyObjectClass( ctx, target )) {
2128 osrfAppRespondComplete( ctx, NULL );
2132 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2134 const char* trans_id = getXactId( ctx );
2136 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2138 osrfAppSessionStatus(
2140 OSRF_STATUS_BADREQUEST,
2141 "osrfMethodException",
2143 "No active transaction -- required for CREATE"
2145 osrfAppRespondComplete( ctx, NULL );
2149 // The following test is harmless but redundant. If a class is
2150 // readonly, we don't register a create method for it.
2151 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2152 osrfAppSessionStatus(
2154 OSRF_STATUS_BADREQUEST,
2155 "osrfMethodException",
2157 "Cannot INSERT readonly class"
2159 osrfAppRespondComplete( ctx, NULL );
2163 // Set the last_xact_id
2164 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2166 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2167 trans_id, target->classname, index);
2168 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2171 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2173 dbhandle = writehandle;
2175 osrfHash* fields = osrfHashGet( meta, "fields" );
2176 char* pkey = osrfHashGet( meta, "primarykey" );
2177 char* seq = osrfHashGet( meta, "sequence" );
2179 growing_buffer* table_buf = buffer_init( 128 );
2180 growing_buffer* col_buf = buffer_init( 128 );
2181 growing_buffer* val_buf = buffer_init( 128 );
2183 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2184 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2185 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2186 buffer_add( val_buf,"VALUES (" );
2190 osrfHash* field = NULL;
2191 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2192 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2194 const char* field_name = osrfHashIteratorKey( field_itr );
2196 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2199 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2202 if( field_object && field_object->classname ) {
2203 value = oilsFMGetString(
2205 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2207 } else if( field_object && JSON_BOOL == field_object->type ) {
2208 if( jsonBoolIsTrue( field_object ) )
2209 value = strdup( "t" );
2211 value = strdup( "f" );
2213 value = jsonObjectToSimpleString( field_object );
2219 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2220 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2223 buffer_add( col_buf, field_name );
2225 if( !field_object || field_object->type == JSON_NULL ) {
2226 buffer_add( val_buf, "DEFAULT" );
2228 } else if( !strcmp( get_primitive( field ), "number" )) {
2229 const char* numtype = get_datatype( field );
2230 if( !strcmp( numtype, "INT8" )) {
2231 buffer_fadd( val_buf, "%lld", atoll( value ));
2233 } else if( !strcmp( numtype, "INT" )) {
2234 buffer_fadd( val_buf, "%d", atoi( value ));
2236 } else if( !strcmp( numtype, "NUMERIC" )) {
2237 buffer_fadd( val_buf, "%f", atof( value ));
2240 if( dbi_conn_quote_string( writehandle, &value )) {
2241 OSRF_BUFFER_ADD( val_buf, value );
2244 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2245 osrfAppSessionStatus(
2247 OSRF_STATUS_INTERNALSERVERERROR,
2248 "osrfMethodException",
2250 "Error quoting string -- please see the error log for more details"
2253 buffer_free( table_buf );
2254 buffer_free( col_buf );
2255 buffer_free( val_buf );
2256 osrfAppRespondComplete( ctx, NULL );
2264 osrfHashIteratorFree( field_itr );
2266 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2267 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2269 char* table_str = buffer_release( table_buf );
2270 char* col_str = buffer_release( col_buf );
2271 char* val_str = buffer_release( val_buf );
2272 growing_buffer* sql = buffer_init( 128 );
2273 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2278 char* query = buffer_release( sql );
2280 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2282 jsonObject* obj = NULL;
2285 dbi_result result = dbi_conn_query( writehandle, query );
2287 obj = jsonNewObject( NULL );
2289 int errnum = dbi_conn_error( writehandle, &msg );
2292 "%s ERROR inserting %s object using query [%s]: %d %s",
2294 osrfHashGet(meta, "fieldmapper"),
2297 msg ? msg : "(No description available)"
2299 osrfAppSessionStatus(
2301 OSRF_STATUS_INTERNALSERVERERROR,
2302 "osrfMethodException",
2304 "INSERT error -- please see the error log for more details"
2306 if( !oilsIsDBConnected( writehandle ))
2307 osrfAppSessionPanic( ctx->session );
2310 dbi_result_free( result );
2312 char* id = oilsFMGetString( target, pkey );
2314 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2315 growing_buffer* _id = buffer_init( 10 );
2316 buffer_fadd( _id, "%lld", new_id );
2317 id = buffer_release( _id );
2320 // Find quietness specification, if present
2321 const char* quiet_str = NULL;
2323 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2325 quiet_str = jsonObjectGetString( quiet_obj );
2328 if( str_is_true( quiet_str )) { // if quietness is specified
2329 obj = jsonNewObject( id );
2333 // Fetch the row that we just inserted, so that we can return it to the client
2334 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2335 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2338 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2342 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2344 jsonObjectFree( list );
2345 jsonObjectFree( where_clause );
2352 osrfAppRespondComplete( ctx, obj );
2353 jsonObjectFree( obj );
2358 @brief Implement the retrieve method.
2359 @param ctx Pointer to the method context.
2360 @param err Pointer through which to return an error code.
2361 @return If successful, a pointer to the result to be returned to the client;
2364 From the method's class, fetch a row with a specified value in the primary key. This
2365 method relies on the database design convention that a primary key consists of a single
2369 - authkey (PCRUD only)
2370 - value of the primary key for the desired row, for building the WHERE clause
2371 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2373 Return to client: One row from the query.
2375 int doRetrieve( osrfMethodContext* ctx ) {
2376 if(osrfMethodVerifyContext( ctx )) {
2377 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2382 timeout_needs_resetting = 1;
2387 if( enforce_pcrud ) {
2392 // Get the class metadata
2393 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2395 // Get the value of the primary key, from a method parameter
2396 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2400 "%s retrieving %s object with primary key value of %s",
2402 osrfHashGet( class_def, "fieldmapper" ),
2403 jsonObjectGetString( id_obj )
2406 // Build a WHERE clause based on the key value
2407 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2410 osrfHashGet( class_def, "primarykey" ), // name of key column
2411 jsonObjectClone( id_obj ) // value of key column
2414 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2418 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2420 jsonObjectFree( where_clause );
2422 osrfAppRespondComplete( ctx, NULL );
2426 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2427 jsonObjectFree( list );
2429 if( enforce_pcrud ) {
2430 // no result, skip this entirely
2431 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2432 jsonObjectFree( obj );
2434 growing_buffer* msg = buffer_init( 128 );
2435 OSRF_BUFFER_ADD( msg, modulename );
2436 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2438 char* m = buffer_release( msg );
2439 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2443 osrfAppRespondComplete( ctx, NULL );
2448 // doFieldmapperSearch() now does the responding for us
2449 //osrfAppRespondComplete( ctx, obj );
2450 osrfAppRespondComplete( ctx, NULL );
2452 jsonObjectFree( obj );
2457 @brief Translate a numeric value to a string representation for the database.
2458 @param field Pointer to the IDL field definition.
2459 @param value Pointer to a jsonObject holding the value of a field.
2460 @return Pointer to a newly allocated string.
2462 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2463 its contents are numeric. A non-numeric string is likely to result in invalid SQL.
2465 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2467 The calling code is responsible for freeing the resulting string by calling free().
2469 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2470 growing_buffer* val_buf = buffer_init( 32 );
2471 const char* numtype = get_datatype( field );
2473 // If the value is a number and the DB field is numeric, no quotes needed
2474 if( value->type == JSON_NUMBER && !strcmp( get_primitive( field ), "number") ) {
2475 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2477 // Presumably this was really intended to be a string, so quote it
2478 char* str = jsonObjectToSimpleString( value );
2479 if( dbi_conn_quote_string( dbhandle, &str )) {
2480 OSRF_BUFFER_ADD( val_buf, str );
2483 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2485 buffer_free( val_buf );
2490 return buffer_release( val_buf );
2493 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2494 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2495 growing_buffer* sql_buf = buffer_init( 32 );
2501 osrfHashGet( field, "name" )
2505 buffer_add( sql_buf, "IN (" );
2506 } else if( !strcasecmp( op,"not in" )) {
2507 buffer_add( sql_buf, "NOT IN (" );
2509 buffer_add( sql_buf, "IN (" );
2512 if( node->type == JSON_HASH ) {
2513 // subquery predicate
2514 char* subpred = buildQuery( ctx, node, SUBSELECT );
2516 buffer_free( sql_buf );
2520 buffer_add( sql_buf, subpred );
2523 } else if( node->type == JSON_ARRAY ) {
2524 // literal value list
2525 int in_item_index = 0;
2526 int in_item_first = 1;
2527 const jsonObject* in_item;
2528 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2533 buffer_add( sql_buf, ", " );
2536 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2537 osrfLogError( OSRF_LOG_MARK,
2538 "%s: Expected string or number within IN list; found %s",
2539 modulename, json_type( in_item->type ) );
2540 buffer_free( sql_buf );
2544 // Append the literal value -- quoted if not a number
2545 if( JSON_NUMBER == in_item->type ) {
2546 char* val = jsonNumberToDBString( field, in_item );
2547 OSRF_BUFFER_ADD( sql_buf, val );
2550 } else if( !strcmp( get_primitive( field ), "number" )) {
2551 char* val = jsonNumberToDBString( field, in_item );
2552 OSRF_BUFFER_ADD( sql_buf, val );
2556 char* key_string = jsonObjectToSimpleString( in_item );
2557 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2558 OSRF_BUFFER_ADD( sql_buf, key_string );
2561 osrfLogError( OSRF_LOG_MARK,
2562 "%s: Error quoting key string [%s]", modulename, key_string );
2564 buffer_free( sql_buf );
2570 if( in_item_first ) {
2571 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2572 buffer_free( sql_buf );
2576 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2577 modulename, json_type( node->type ));
2578 buffer_free( sql_buf );
2582 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2584 return buffer_release( sql_buf );
2587 // Receive a JSON_ARRAY representing a function call. The first
2588 // entry in the array is the function name. The rest are parameters.
2589 static char* searchValueTransform( const jsonObject* array ) {
2591 if( array->size < 1 ) {
2592 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2596 // Get the function name
2597 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2598 if( func_item->type != JSON_STRING ) {
2599 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2600 modulename, json_type( func_item->type ));
2604 growing_buffer* sql_buf = buffer_init( 32 );
2606 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2607 OSRF_BUFFER_ADD( sql_buf, "( " );
2609 // Get the parameters
2610 int func_item_index = 1; // We already grabbed the zeroth entry
2611 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2613 // Add a separator comma, if we need one
2614 if( func_item_index > 2 )
2615 buffer_add( sql_buf, ", " );
2617 // Add the current parameter
2618 if( func_item->type == JSON_NULL ) {
2619 buffer_add( sql_buf, "NULL" );
2621 if( func_item->type == JSON_BOOL ) {
2622 if( jsonBoolIsTrue(func_item) ) {
2623 buffer_add( sql_buf, "TRUE" );
2625 buffer_add( sql_buf, "FALSE" );
2628 char* val = jsonObjectToSimpleString( func_item );
2629 if( dbi_conn_quote_string( dbhandle, &val )) {
2630 OSRF_BUFFER_ADD( sql_buf, val );
2633 osrfLogError( OSRF_LOG_MARK,
2634 "%s: Error quoting key string [%s]", modulename, val );
2635 buffer_free( sql_buf );
2643 buffer_add( sql_buf, " )" );
2645 return buffer_release( sql_buf );
2648 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2649 const jsonObject* node, const char* op ) {
2651 if( ! is_good_operator( op ) ) {
2652 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2656 char* val = searchValueTransform( node );
2660 growing_buffer* sql_buf = buffer_init( 32 );
2665 osrfHashGet( field, "name" ),
2672 return buffer_release( sql_buf );
2675 // class_alias is a class name or other table alias
2676 // field is a field definition as stored in the IDL
2677 // node comes from the method parameter, and may represent an entry in the SELECT list
2678 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2679 const jsonObject* node ) {
2680 growing_buffer* sql_buf = buffer_init( 32 );
2682 const char* field_transform = jsonObjectGetString(
2683 jsonObjectGetKeyConst( node, "transform" ) );
2684 const char* transform_subcolumn = jsonObjectGetString(
2685 jsonObjectGetKeyConst( node, "result_field" ) );
2687 if( transform_subcolumn ) {
2688 if( ! is_identifier( transform_subcolumn ) ) {
2689 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2690 modulename, transform_subcolumn );
2691 buffer_free( sql_buf );
2694 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2697 if( field_transform ) {
2699 if( ! is_identifier( field_transform ) ) {
2700 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2701 modulename, field_transform );
2702 buffer_free( sql_buf );
2706 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2707 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2708 field_transform, class_alias, osrfHashGet( field, "name" ));
2710 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2711 field_transform, class_alias, osrfHashGet( field, "name" ));
2714 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2717 if( array->type != JSON_ARRAY ) {
2718 osrfLogError( OSRF_LOG_MARK,
2719 "%s: Expected JSON_ARRAY for function params; found %s",
2720 modulename, json_type( array->type ) );
2721 buffer_free( sql_buf );
2724 int func_item_index = 0;
2725 jsonObject* func_item;
2726 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2728 char* val = jsonObjectToSimpleString( func_item );
2731 buffer_add( sql_buf, ",NULL" );
2732 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2733 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2734 OSRF_BUFFER_ADD( sql_buf, val );
2736 osrfLogError( OSRF_LOG_MARK,
2737 "%s: Error quoting key string [%s]", modulename, val );
2739 buffer_free( sql_buf );
2746 buffer_add( sql_buf, " )" );
2749 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2752 if( transform_subcolumn )
2753 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2755 return buffer_release( sql_buf );
2758 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2759 const jsonObject* node, const char* op ) {
2761 if( ! is_good_operator( op ) ) {
2762 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2766 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2767 if( ! field_transform )
2770 int extra_parens = 0; // boolean
2772 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2774 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2776 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2778 free( field_transform );
2782 } else if( value_obj->type == JSON_ARRAY ) {
2783 value = searchValueTransform( value_obj );
2785 osrfLogError( OSRF_LOG_MARK,
2786 "%s: Error building value transform for field transform", modulename );
2787 free( field_transform );
2790 } else if( value_obj->type == JSON_HASH ) {
2791 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2793 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2795 free( field_transform );
2799 } else if( value_obj->type == JSON_NUMBER ) {
2800 value = jsonNumberToDBString( field, value_obj );
2801 } else if( value_obj->type == JSON_NULL ) {
2802 osrfLogError( OSRF_LOG_MARK,
2803 "%s: Error building predicate for field transform: null value", modulename );
2804 free( field_transform );
2806 } else if( value_obj->type == JSON_BOOL ) {
2807 osrfLogError( OSRF_LOG_MARK,
2808 "%s: Error building predicate for field transform: boolean value", modulename );
2809 free( field_transform );
2812 if( !strcmp( get_primitive( field ), "number") ) {
2813 value = jsonNumberToDBString( field, value_obj );
2815 value = jsonObjectToSimpleString( value_obj );
2816 if( !dbi_conn_quote_string( dbhandle, &value )) {
2817 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2818 modulename, value );
2820 free( field_transform );
2826 const char* left_parens = "";
2827 const char* right_parens = "";
2829 if( extra_parens ) {
2834 const char* right_percent = "";
2835 const char* real_op = op;
2837 if( !strcasecmp( op, "startwith") ) {
2839 right_percent = "|| '%'";
2842 growing_buffer* sql_buf = buffer_init( 32 );
2846 "%s%s %s %s %s%s %s%s",
2858 free( field_transform );
2860 return buffer_release( sql_buf );
2863 static char* searchSimplePredicate( const char* op, const char* class_alias,
2864 osrfHash* field, const jsonObject* node ) {
2866 if( ! is_good_operator( op ) ) {
2867 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2873 // Get the value to which we are comparing the specified column
2874 if( node->type != JSON_NULL ) {
2875 if( node->type == JSON_NUMBER ) {
2876 val = jsonNumberToDBString( field, node );
2877 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2878 val = jsonNumberToDBString( field, node );
2880 val = jsonObjectToSimpleString( node );
2885 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2886 // Value is not numeric; enclose it in quotes
2887 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2888 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2895 // Compare to a null value
2896 val = strdup( "NULL" );
2897 if( strcmp( op, "=" ))
2903 growing_buffer* sql_buf = buffer_init( 32 );
2904 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2905 char* pred = buffer_release( sql_buf );
2912 static char* searchBETWEENPredicate( const char* class_alias,
2913 osrfHash* field, const jsonObject* node ) {
2915 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2916 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2918 if( NULL == y_node ) {
2919 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2922 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2923 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2930 if( !strcmp( get_primitive( field ), "number") ) {
2931 x_string = jsonNumberToDBString( field, x_node );
2932 y_string = jsonNumberToDBString( field, y_node );
2935 x_string = jsonObjectToSimpleString( x_node );
2936 y_string = jsonObjectToSimpleString( y_node );
2937 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2938 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2939 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2940 modulename, x_string, y_string );
2947 growing_buffer* sql_buf = buffer_init( 32 );
2948 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2949 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2953 return buffer_release( sql_buf );
2956 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2957 jsonObject* node, osrfMethodContext* ctx ) {
2960 if( node->type == JSON_ARRAY ) { // equality IN search
2961 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2962 } else if( node->type == JSON_HASH ) { // other search
2963 jsonIterator* pred_itr = jsonNewIterator( node );
2964 if( !jsonIteratorHasNext( pred_itr ) ) {
2965 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2966 modulename, osrfHashGet(field, "name" ));
2968 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2970 // Verify that there are no additional predicates
2971 if( jsonIteratorHasNext( pred_itr ) ) {
2972 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2973 modulename, osrfHashGet(field, "name" ));
2974 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2975 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2976 else if( !(strcasecmp( pred_itr->key,"in" ))
2977 || !(strcasecmp( pred_itr->key,"not in" )) )
2978 pred = searchINPredicate(
2979 class_info->alias, field, pred_node, pred_itr->key, ctx );
2980 else if( pred_node->type == JSON_ARRAY )
2981 pred = searchFunctionPredicate(
2982 class_info->alias, field, pred_node, pred_itr->key );
2983 else if( pred_node->type == JSON_HASH )
2984 pred = searchFieldTransformPredicate(
2985 class_info, field, pred_node, pred_itr->key );
2987 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2989 jsonIteratorFree( pred_itr );
2991 } else if( node->type == JSON_NULL ) { // IS NULL search
2992 growing_buffer* _p = buffer_init( 64 );
2995 "\"%s\".%s IS NULL",
2997 osrfHashGet( field, "name" )
2999 pred = buffer_release( _p );
3000 } else { // equality search
3001 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3020 field : call_number,
3036 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3038 const jsonObject* working_hash;
3039 jsonObject* freeable_hash = NULL;
3041 if( join_hash->type == JSON_HASH ) {
3042 working_hash = join_hash;
3043 } else if( join_hash->type == JSON_STRING ) {
3044 // turn it into a JSON_HASH by creating a wrapper
3045 // around a copy of the original
3046 const char* _tmp = jsonObjectGetString( join_hash );
3047 freeable_hash = jsonNewObjectType( JSON_HASH );
3048 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3049 working_hash = freeable_hash;
3053 "%s: JOIN failed; expected JSON object type not found",
3059 growing_buffer* join_buf = buffer_init( 128 );
3060 const char* leftclass = left_info->class_name;
3062 jsonObject* snode = NULL;
3063 jsonIterator* search_itr = jsonNewIterator( working_hash );
3065 while ( (snode = jsonIteratorNext( search_itr )) ) {
3066 const char* right_alias = search_itr->key;
3068 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3070 class = right_alias;
3072 const ClassInfo* right_info = add_joined_class( right_alias, class );
3076 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3080 jsonIteratorFree( search_itr );
3081 buffer_free( join_buf );
3083 jsonObjectFree( freeable_hash );
3086 osrfHash* links = right_info->links;
3087 const char* table = right_info->source_def;
3089 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3090 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3092 if( field && !fkey ) {
3093 // Look up the corresponding join column in the IDL.
3094 // The link must be defined in the child table,
3095 // and point to the right parent table.
3096 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3097 const char* reltype = NULL;
3098 const char* other_class = NULL;
3099 reltype = osrfHashGet( idl_link, "reltype" );
3100 if( reltype && strcmp( reltype, "has_many" ) )
3101 other_class = osrfHashGet( idl_link, "class" );
3102 if( other_class && !strcmp( other_class, leftclass ) )
3103 fkey = osrfHashGet( idl_link, "key" );
3107 "%s: JOIN failed. No link defined from %s.%s to %s",
3113 buffer_free( join_buf );
3115 jsonObjectFree( freeable_hash );
3116 jsonIteratorFree( search_itr );
3120 } else if( !field && fkey ) {
3121 // Look up the corresponding join column in the IDL.
3122 // The link must be defined in the child table,
3123 // and point to the right parent table.
3124 osrfHash* left_links = left_info->links;
3125 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3126 const char* reltype = NULL;
3127 const char* other_class = NULL;
3128 reltype = osrfHashGet( idl_link, "reltype" );
3129 if( reltype && strcmp( reltype, "has_many" ) )
3130 other_class = osrfHashGet( idl_link, "class" );
3131 if( other_class && !strcmp( other_class, class ) )
3132 field = osrfHashGet( idl_link, "key" );
3136 "%s: JOIN failed. No link defined from %s.%s to %s",
3142 buffer_free( join_buf );
3144 jsonObjectFree( freeable_hash );
3145 jsonIteratorFree( search_itr );
3149 } else if( !field && !fkey ) {
3150 osrfHash* left_links = left_info->links;
3152 // For each link defined for the left class:
3153 // see if the link references the joined class
3154 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3155 osrfHash* curr_link = NULL;
3156 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3157 const char* other_class = osrfHashGet( curr_link, "class" );
3158 if( other_class && !strcmp( other_class, class ) ) {
3160 // In the IDL, the parent class doesn't always know then names of the child
3161 // columns that are pointing to it, so don't use that end of the link
3162 const char* reltype = osrfHashGet( curr_link, "reltype" );
3163 if( reltype && strcmp( reltype, "has_many" ) ) {
3164 // Found a link between the classes
3165 fkey = osrfHashIteratorKey( itr );
3166 field = osrfHashGet( curr_link, "key" );
3171 osrfHashIteratorFree( itr );
3173 if( !field || !fkey ) {
3174 // Do another such search, with the classes reversed
3176 // For each link defined for the joined class:
3177 // see if the link references the left class
3178 osrfHashIterator* itr = osrfNewHashIterator( links );
3179 osrfHash* curr_link = NULL;
3180 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3181 const char* other_class = osrfHashGet( curr_link, "class" );
3182 if( other_class && !strcmp( other_class, leftclass ) ) {
3184 // In the IDL, the parent class doesn't know then names of the child
3185 // columns that are pointing to it, so don't use that end of the link
3186 const char* reltype = osrfHashGet( curr_link, "reltype" );
3187 if( reltype && strcmp( reltype, "has_many" ) ) {
3188 // Found a link between the classes
3189 field = osrfHashIteratorKey( itr );
3190 fkey = osrfHashGet( curr_link, "key" );
3195 osrfHashIteratorFree( itr );
3198 if( !field || !fkey ) {
3201 "%s: JOIN failed. No link defined between %s and %s",
3206 buffer_free( join_buf );
3208 jsonObjectFree( freeable_hash );
3209 jsonIteratorFree( search_itr );
3214 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3216 if( !strcasecmp( type,"left" )) {
3217 buffer_add( join_buf, " LEFT JOIN" );
3218 } else if( !strcasecmp( type,"right" )) {
3219 buffer_add( join_buf, " RIGHT JOIN" );
3220 } else if( !strcasecmp( type,"full" )) {
3221 buffer_add( join_buf, " FULL JOIN" );
3223 buffer_add( join_buf, " INNER JOIN" );
3226 buffer_add( join_buf, " INNER JOIN" );
3229 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3230 table, right_alias, right_alias, field, left_info->alias, fkey );
3232 // Add any other join conditions as specified by "filter"
3233 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3235 const char* filter_op = jsonObjectGetString(
3236 jsonObjectGetKeyConst( snode, "filter_op" ) );
3237 if( filter_op && !strcasecmp( "or",filter_op )) {
3238 buffer_add( join_buf, " OR " );
3240 buffer_add( join_buf, " AND " );
3243 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3245 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3246 OSRF_BUFFER_ADD( join_buf, jpred );
3251 "%s: JOIN failed. Invalid conditional expression.",
3254 jsonIteratorFree( search_itr );
3255 buffer_free( join_buf );
3257 jsonObjectFree( freeable_hash );
3262 buffer_add( join_buf, " ) " );
3264 // Recursively add a nested join, if one is present
3265 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3267 char* jpred = searchJOIN( join_filter, right_info );
3269 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3270 OSRF_BUFFER_ADD( join_buf, jpred );
3273 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3274 jsonIteratorFree( search_itr );
3275 buffer_free( join_buf );
3277 jsonObjectFree( freeable_hash );
3284 jsonObjectFree( freeable_hash );
3285 jsonIteratorFree( search_itr );
3287 return buffer_release( join_buf );
3292 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3293 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3294 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3296 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3298 search_hash is the JSON expression of the conditions.
3299 meta is the class definition from the IDL, for the relevant table.
3300 opjoin_type indicates whether multiple conditions, if present, should be
3301 connected by AND or OR.
3302 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3303 to pass it to other functions -- and all they do with it is to use the session
3304 and request members to send error messages back to the client.
3308 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3309 int opjoin_type, osrfMethodContext* ctx ) {
3313 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3314 "opjoin_type = %d, ctx addr = %p",
3317 class_info->class_def,
3322 growing_buffer* sql_buf = buffer_init( 128 );
3324 jsonObject* node = NULL;
3327 if( search_hash->type == JSON_ARRAY ) {
3328 if( 0 == search_hash->size ) {
3331 "%s: Invalid predicate structure: empty JSON array",
3334 buffer_free( sql_buf );
3338 unsigned long i = 0;
3339 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3343 if( opjoin_type == OR_OP_JOIN )
3344 buffer_add( sql_buf, " OR " );
3346 buffer_add( sql_buf, " AND " );
3349 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3351 buffer_free( sql_buf );
3355 buffer_fadd( sql_buf, "( %s )", subpred );
3359 } else if( search_hash->type == JSON_HASH ) {
3360 osrfLogDebug( OSRF_LOG_MARK,
3361 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3362 jsonIterator* search_itr = jsonNewIterator( search_hash );
3363 if( !jsonIteratorHasNext( search_itr ) ) {
3366 "%s: Invalid predicate structure: empty JSON object",
3369 jsonIteratorFree( search_itr );
3370 buffer_free( sql_buf );
3374 while( (node = jsonIteratorNext( search_itr )) ) {
3379 if( opjoin_type == OR_OP_JOIN )
3380 buffer_add( sql_buf, " OR " );
3382 buffer_add( sql_buf, " AND " );
3385 if( '+' == search_itr->key[ 0 ] ) {
3387 // This plus sign prefixes a class name or other table alias;
3388 // make sure the table alias is in scope
3389 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3390 if( ! alias_info ) {
3393 "%s: Invalid table alias \"%s\" in WHERE clause",
3397 jsonIteratorFree( search_itr );
3398 buffer_free( sql_buf );
3402 if( node->type == JSON_STRING ) {
3403 // It's the name of a column; make sure it belongs to the class
3404 const char* fieldname = jsonObjectGetString( node );
3405 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3408 "%s: Invalid column name \"%s\" in WHERE clause "
3409 "for table alias \"%s\"",
3414 jsonIteratorFree( search_itr );
3415 buffer_free( sql_buf );
3419 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3421 // It's something more complicated
3422 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3424 jsonIteratorFree( search_itr );
3425 buffer_free( sql_buf );
3429 buffer_fadd( sql_buf, "( %s )", subpred );
3432 } else if( '-' == search_itr->key[ 0 ] ) {
3433 if( !strcasecmp( "-or", search_itr->key )) {
3434 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3436 jsonIteratorFree( search_itr );
3437 buffer_free( sql_buf );
3441 buffer_fadd( sql_buf, "( %s )", subpred );
3443 } else if( !strcasecmp( "-and", search_itr->key )) {
3444 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3446 jsonIteratorFree( search_itr );
3447 buffer_free( sql_buf );
3451 buffer_fadd( sql_buf, "( %s )", subpred );
3453 } else if( !strcasecmp("-not",search_itr->key) ) {
3454 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3456 jsonIteratorFree( search_itr );
3457 buffer_free( sql_buf );
3461 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3463 } else if( !strcasecmp( "-exists", search_itr->key )) {
3464 char* subpred = buildQuery( ctx, node, SUBSELECT );
3466 jsonIteratorFree( search_itr );
3467 buffer_free( sql_buf );
3471 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3473 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3474 char* subpred = buildQuery( ctx, node, SUBSELECT );
3476 jsonIteratorFree( search_itr );
3477 buffer_free( sql_buf );
3481 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3483 } else { // Invalid "minus" operator
3486 "%s: Invalid operator \"%s\" in WHERE clause",
3490 jsonIteratorFree( search_itr );
3491 buffer_free( sql_buf );
3497 const char* class = class_info->class_name;
3498 osrfHash* fields = class_info->fields;
3499 osrfHash* field = osrfHashGet( fields, search_itr->key );
3502 const char* table = class_info->source_def;
3505 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3508 table ? table : "?",
3511 jsonIteratorFree( search_itr );
3512 buffer_free( sql_buf );
3516 char* subpred = searchPredicate( class_info, field, node, ctx );
3518 buffer_free( sql_buf );
3519 jsonIteratorFree( search_itr );
3523 buffer_add( sql_buf, subpred );
3527 jsonIteratorFree( search_itr );
3530 // ERROR ... only hash and array allowed at this level
3531 char* predicate_string = jsonObjectToJSON( search_hash );
3534 "%s: Invalid predicate structure: %s",
3538 buffer_free( sql_buf );
3539 free( predicate_string );
3543 return buffer_release( sql_buf );
3546 /* Build a JSON_ARRAY of field names for a given table alias
3548 static jsonObject* defaultSelectList( const char* table_alias ) {
3553 ClassInfo* class_info = search_all_alias( table_alias );
3554 if( ! class_info ) {
3557 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3564 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3565 osrfHash* field_def = NULL;
3566 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3567 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3568 const char* field_name = osrfHashIteratorKey( field_itr );
3569 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3570 jsonObjectPush( array, jsonNewObject( field_name ) );
3573 osrfHashIteratorFree( field_itr );
3578 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3579 // The jsonObject must be a JSON_HASH with an single entry for "union",
3580 // "intersect", or "except". The data associated with this key must be an
3581 // array of hashes, each hash being a query.
3582 // Also allowed but currently ignored: entries for "order_by" and "alias".
3583 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3585 if( ! combo || combo->type != JSON_HASH )
3586 return NULL; // should be impossible; validated by caller
3588 const jsonObject* query_array = NULL; // array of subordinate queries
3589 const char* op = NULL; // name of operator, e.g. UNION
3590 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3591 int op_count = 0; // for detecting conflicting operators
3592 int excepting = 0; // boolean
3593 int all = 0; // boolean
3594 jsonObject* order_obj = NULL;
3596 // Identify the elements in the hash
3597 jsonIterator* query_itr = jsonNewIterator( combo );
3598 jsonObject* curr_obj = NULL;
3599 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3600 if( ! strcmp( "union", query_itr->key ) ) {
3603 query_array = curr_obj;
3604 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3607 query_array = curr_obj;
3608 } else if( ! strcmp( "except", query_itr->key ) ) {
3612 query_array = curr_obj;
3613 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3616 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3619 order_obj = curr_obj;
3620 } else if( ! strcmp( "alias", query_itr->key ) ) {
3621 if( curr_obj->type != JSON_STRING ) {
3622 jsonIteratorFree( query_itr );
3625 alias = jsonObjectGetString( curr_obj );
3626 } else if( ! strcmp( "all", query_itr->key ) ) {
3627 if( obj_is_true( curr_obj ) )
3631 osrfAppSessionStatus(
3633 OSRF_STATUS_INTERNALSERVERERROR,
3634 "osrfMethodException",
3636 "Malformed query; unexpected entry in query object"
3640 "%s: Unexpected entry for \"%s\" in%squery",
3645 jsonIteratorFree( query_itr );
3649 jsonIteratorFree( query_itr );
3651 // More sanity checks
3652 if( ! query_array ) {
3654 osrfAppSessionStatus(
3656 OSRF_STATUS_INTERNALSERVERERROR,
3657 "osrfMethodException",
3659 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3663 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3666 return NULL; // should be impossible...
3667 } else if( op_count > 1 ) {
3669 osrfAppSessionStatus(
3671 OSRF_STATUS_INTERNALSERVERERROR,
3672 "osrfMethodException",
3674 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3678 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3682 } if( query_array->type != JSON_ARRAY ) {
3684 osrfAppSessionStatus(
3686 OSRF_STATUS_INTERNALSERVERERROR,
3687 "osrfMethodException",
3689 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3693 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3696 json_type( query_array->type )
3699 } if( query_array->size < 2 ) {
3701 osrfAppSessionStatus(
3703 OSRF_STATUS_INTERNALSERVERERROR,
3704 "osrfMethodException",
3706 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3710 "%s:%srequires multiple queries as operands",
3715 } else if( excepting && query_array->size > 2 ) {
3717 osrfAppSessionStatus(
3719 OSRF_STATUS_INTERNALSERVERERROR,
3720 "osrfMethodException",
3722 "EXCEPT operator has too many queries as operands"
3726 "%s:EXCEPT operator has too many queries as operands",
3730 } else if( order_obj && ! alias ) {
3732 osrfAppSessionStatus(
3734 OSRF_STATUS_INTERNALSERVERERROR,
3735 "osrfMethodException",
3737 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3741 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3747 // So far so good. Now build the SQL.
3748 growing_buffer* sql = buffer_init( 256 );
3750 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3751 // Add a layer of parentheses
3752 if( flags & SUBCOMBO )
3753 OSRF_BUFFER_ADD( sql, "( " );
3755 // Traverse the query array. Each entry should be a hash.
3756 int first = 1; // boolean
3758 jsonObject* query = NULL;
3759 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3760 if( query->type != JSON_HASH ) {
3762 osrfAppSessionStatus(
3764 OSRF_STATUS_INTERNALSERVERERROR,
3765 "osrfMethodException",
3767 "Malformed query under UNION, INTERSECT or EXCEPT"
3771 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3774 json_type( query->type )
3783 OSRF_BUFFER_ADD( sql, op );
3785 OSRF_BUFFER_ADD( sql, "ALL " );
3788 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3792 "%s: Error building query under%s",
3800 OSRF_BUFFER_ADD( sql, query_str );
3803 if( flags & SUBCOMBO )
3804 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3806 if( !(flags & SUBSELECT) )
3807 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3809 return buffer_release( sql );
3812 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3813 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3814 // or "except" to indicate the type of query.
3815 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3819 osrfAppSessionStatus(
3821 OSRF_STATUS_INTERNALSERVERERROR,
3822 "osrfMethodException",
3824 "Malformed query; no query object"
3826 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3828 } else if( query->type != JSON_HASH ) {
3830 osrfAppSessionStatus(
3832 OSRF_STATUS_INTERNALSERVERERROR,
3833 "osrfMethodException",
3835 "Malformed query object"
3839 "%s: Query object is %s instead of JSON_HASH",
3841 json_type( query->type )
3846 // Determine what kind of query it purports to be, and dispatch accordingly.
3847 if( jsonObjectGetKeyConst( query, "union" ) ||
3848 jsonObjectGetKeyConst( query, "intersect" ) ||
3849 jsonObjectGetKeyConst( query, "except" )) {
3850 return doCombo( ctx, query, flags );
3852 // It is presumably a SELECT query
3854 // Push a node onto the stack for the current query. Every level of
3855 // subquery gets its own QueryFrame on the Stack.
3858 // Build an SQL SELECT statement
3861 jsonObjectGetKey( query, "select" ),
3862 jsonObjectGetKeyConst( query, "from" ),
3863 jsonObjectGetKeyConst( query, "where" ),
3864 jsonObjectGetKeyConst( query, "having" ),
3865 jsonObjectGetKeyConst( query, "order_by" ),
3866 jsonObjectGetKeyConst( query, "limit" ),
3867 jsonObjectGetKeyConst( query, "offset" ),
3876 /* method context */ osrfMethodContext* ctx,
3878 /* SELECT */ jsonObject* selhash,
3879 /* FROM */ const jsonObject* join_hash,
3880 /* WHERE */ const jsonObject* search_hash,
3881 /* HAVING */ const jsonObject* having_hash,
3882 /* ORDER BY */ const jsonObject* order_hash,
3883 /* LIMIT */ const jsonObject* limit,
3884 /* OFFSET */ const jsonObject* offset,
3885 /* flags */ int flags
3887 const char* locale = osrf_message_get_last_locale();
3889 // general tmp objects
3890 const jsonObject* tmp_const;
3891 jsonObject* selclass = NULL;
3892 jsonObject* snode = NULL;
3893 jsonObject* onode = NULL;
3895 char* string = NULL;
3896 int from_function = 0;
3901 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3903 // punt if there's no FROM clause
3904 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3907 "%s: FROM clause is missing or empty",
3911 osrfAppSessionStatus(
3913 OSRF_STATUS_INTERNALSERVERERROR,
3914 "osrfMethodException",
3916 "FROM clause is missing or empty in JSON query"
3921 // the core search class
3922 const char* core_class = NULL;
3924 // get the core class -- the only key of the top level FROM clause, or a string
3925 if( join_hash->type == JSON_HASH ) {
3926 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3927 snode = jsonIteratorNext( tmp_itr );
3929 // Populate the current QueryFrame with information
3930 // about the core class
3931 if( add_query_core( NULL, tmp_itr->key ) ) {
3933 osrfAppSessionStatus(
3935 OSRF_STATUS_INTERNALSERVERERROR,
3936 "osrfMethodException",
3938 "Unable to look up core class"
3942 core_class = curr_query->core.class_name;
3945 jsonObject* extra = jsonIteratorNext( tmp_itr );
3947 jsonIteratorFree( tmp_itr );
3950 // There shouldn't be more than one entry in join_hash
3954 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3958 osrfAppSessionStatus(
3960 OSRF_STATUS_INTERNALSERVERERROR,
3961 "osrfMethodException",
3963 "Malformed FROM clause in JSON query"
3965 return NULL; // Malformed join_hash; extra entry
3967 } else if( join_hash->type == JSON_ARRAY ) {
3968 // We're selecting from a function, not from a table
3970 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3973 } else if( join_hash->type == JSON_STRING ) {
3974 // Populate the current QueryFrame with information
3975 // about the core class
3976 core_class = jsonObjectGetString( join_hash );
3978 if( add_query_core( NULL, core_class ) ) {
3980 osrfAppSessionStatus(
3982 OSRF_STATUS_INTERNALSERVERERROR,
3983 "osrfMethodException",
3985 "Unable to look up core class"
3993 "%s: FROM clause is unexpected JSON type: %s",
3995 json_type( join_hash->type )
3998 osrfAppSessionStatus(
4000 OSRF_STATUS_INTERNALSERVERERROR,
4001 "osrfMethodException",
4003 "Ill-formed FROM clause in JSON query"
4008 // Build the join clause, if any, while filling out the list
4009 // of joined classes in the current QueryFrame.
4010 char* join_clause = NULL;
4011 if( join_hash && ! from_function ) {
4013 join_clause = searchJOIN( join_hash, &curr_query->core );
4014 if( ! join_clause ) {
4016 osrfAppSessionStatus(
4018 OSRF_STATUS_INTERNALSERVERERROR,
4019 "osrfMethodException",
4021 "Unable to construct JOIN clause(s)"
4027 // For in case we don't get a select list
4028 jsonObject* defaultselhash = NULL;
4030 // if there is no select list, build a default select list ...
4031 if( !selhash && !from_function ) {
4032 jsonObject* default_list = defaultSelectList( core_class );
4033 if( ! default_list ) {
4035 osrfAppSessionStatus(
4037 OSRF_STATUS_INTERNALSERVERERROR,
4038 "osrfMethodException",
4040 "Unable to build default SELECT clause in JSON query"
4042 free( join_clause );
4047 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4048 jsonObjectSetKey( selhash, core_class, default_list );
4051 // The SELECT clause can be encoded only by a hash
4052 if( !from_function && selhash->type != JSON_HASH ) {
4055 "%s: Expected JSON_HASH for SELECT clause; found %s",
4057 json_type( selhash->type )
4061 osrfAppSessionStatus(
4063 OSRF_STATUS_INTERNALSERVERERROR,
4064 "osrfMethodException",
4066 "Malformed SELECT clause in JSON query"
4068 free( join_clause );
4072 // If you see a null or wild card specifier for the core class, or an
4073 // empty array, replace it with a default SELECT list
4074 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4076 int default_needed = 0; // boolean
4077 if( JSON_STRING == tmp_const->type
4078 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4080 else if( JSON_NULL == tmp_const->type )
4083 if( default_needed ) {
4084 // Build a default SELECT list
4085 jsonObject* default_list = defaultSelectList( core_class );
4086 if( ! default_list ) {
4088 osrfAppSessionStatus(
4090 OSRF_STATUS_INTERNALSERVERERROR,
4091 "osrfMethodException",
4093 "Can't build default SELECT clause in JSON query"
4095 free( join_clause );
4100 jsonObjectSetKey( selhash, core_class, default_list );
4104 // temp buffers for the SELECT list and GROUP BY clause
4105 growing_buffer* select_buf = buffer_init( 128 );
4106 growing_buffer* group_buf = buffer_init( 128 );
4108 int aggregate_found = 0; // boolean
4110 // Build a select list
4111 if( from_function ) // From a function we select everything
4112 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4115 // Build the SELECT list as SQL
4119 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4120 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4122 const char* cname = selclass_itr->key;
4124 // Make sure the target relation is in the FROM clause.
4126 // At this point join_hash is a step down from the join_hash we
4127 // received as a parameter. If the original was a JSON_STRING,
4128 // then json_hash is now NULL. If the original was a JSON_HASH,
4129 // then json_hash is now the first (and only) entry in it,
4130 // denoting the core class. We've already excluded the
4131 // possibility that the original was a JSON_ARRAY, because in
4132 // that case from_function would be non-NULL, and we wouldn't
4135 // If the current table alias isn't in scope, bail out
4136 ClassInfo* class_info = search_alias( cname );
4137 if( ! class_info ) {
4140 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4145 osrfAppSessionStatus(
4147 OSRF_STATUS_INTERNALSERVERERROR,
4148 "osrfMethodException",
4150 "Selected class not in FROM clause in JSON query"
4152 jsonIteratorFree( selclass_itr );
4153 buffer_free( select_buf );
4154 buffer_free( group_buf );
4155 if( defaultselhash )
4156 jsonObjectFree( defaultselhash );
4157 free( join_clause );
4161 if( selclass->type != JSON_ARRAY ) {
4164 "%s: Malformed SELECT list for class \"%s\"; not an array",
4169 osrfAppSessionStatus(
4171 OSRF_STATUS_INTERNALSERVERERROR,
4172 "osrfMethodException",
4174 "Selected class not in FROM clause in JSON query"
4177 jsonIteratorFree( selclass_itr );
4178 buffer_free( select_buf );
4179 buffer_free( group_buf );
4180 if( defaultselhash )
4181 jsonObjectFree( defaultselhash );
4182 free( join_clause );
4186 // Look up some attributes of the current class
4187 osrfHash* idlClass = class_info->class_def;
4188 osrfHash* class_field_set = class_info->fields;
4189 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4190 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4192 if( 0 == selclass->size ) {
4195 "%s: No columns selected from \"%s\"",
4201 // stitch together the column list for the current table alias...
4202 unsigned long field_idx = 0;
4203 jsonObject* selfield = NULL;
4204 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4206 // If we need a separator comma, add one
4210 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4213 // if the field specification is a string, add it to the list
4214 if( selfield->type == JSON_STRING ) {
4216 // Look up the field in the IDL
4217 const char* col_name = jsonObjectGetString( selfield );
4218 osrfHash* field_def;
4220 if (!osrfStringArrayContains(
4222 osrfHashGet( class_field_set, col_name ),
4223 "suppress_controller"),
4226 field_def = osrfHashGet( class_field_set, col_name );
4229 // No such field in current class
4232 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4238 osrfAppSessionStatus(
4240 OSRF_STATUS_INTERNALSERVERERROR,
4241 "osrfMethodException",
4243 "Selected column not defined in JSON query"
4245 jsonIteratorFree( selclass_itr );
4246 buffer_free( select_buf );
4247 buffer_free( group_buf );
4248 if( defaultselhash )
4249 jsonObjectFree( defaultselhash );
4250 free( join_clause );
4252 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4253 // Virtual field not allowed
4256 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4262 osrfAppSessionStatus(
4264 OSRF_STATUS_INTERNALSERVERERROR,
4265 "osrfMethodException",
4267 "Selected column may not be virtual in JSON query"
4269 jsonIteratorFree( selclass_itr );
4270 buffer_free( select_buf );
4271 buffer_free( group_buf );
4272 if( defaultselhash )
4273 jsonObjectFree( defaultselhash );
4274 free( join_clause );
4280 if( flags & DISABLE_I18N )
4283 i18n = osrfHashGet( field_def, "i18n" );
4285 if( str_is_true( i18n ) ) {
4286 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4287 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4288 class_tname, cname, col_name, class_pkey,
4289 cname, class_pkey, locale, col_name );
4291 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4292 cname, col_name, col_name );
4295 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4296 cname, col_name, col_name );
4299 // ... but it could be an object, in which case we check for a Field Transform
4300 } else if( selfield->type == JSON_HASH ) {
4302 const char* col_name = jsonObjectGetString(
4303 jsonObjectGetKeyConst( selfield, "column" ) );
4305 // Get the field definition from the IDL
4306 osrfHash* field_def;
4307 if (!osrfStringArrayContains(
4309 osrfHashGet( class_field_set, col_name ),
4310 "suppress_controller"),
4313 field_def = osrfHashGet( class_field_set, col_name );
4317 // No such field in current class
4320 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4326 osrfAppSessionStatus(
4328 OSRF_STATUS_INTERNALSERVERERROR,
4329 "osrfMethodException",
4331 "Selected column is not defined in JSON query"
4333 jsonIteratorFree( selclass_itr );
4334 buffer_free( select_buf );
4335 buffer_free( group_buf );
4336 if( defaultselhash )
4337 jsonObjectFree( defaultselhash );
4338 free( join_clause );
4340 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4341 // No such field in current class
4344 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4350 osrfAppSessionStatus(
4352 OSRF_STATUS_INTERNALSERVERERROR,
4353 "osrfMethodException",
4355 "Selected column is virtual in JSON query"
4357 jsonIteratorFree( selclass_itr );
4358 buffer_free( select_buf );
4359 buffer_free( group_buf );
4360 if( defaultselhash )
4361 jsonObjectFree( defaultselhash );
4362 free( join_clause );
4366 // Decide what to use as a column alias
4368 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4369 _alias = jsonObjectGetString( tmp_const );
4370 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4371 _alias = jsonObjectGetString( tmp_const );
4372 } else { // Use field name as the alias
4376 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4377 char* transform_str = searchFieldTransform(
4378 class_info->alias, field_def, selfield );
4379 if( transform_str ) {
4380 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4381 free( transform_str );
4384 osrfAppSessionStatus(
4386 OSRF_STATUS_INTERNALSERVERERROR,
4387 "osrfMethodException",
4389 "Unable to generate transform function in JSON query"
4391 jsonIteratorFree( selclass_itr );
4392 buffer_free( select_buf );
4393 buffer_free( group_buf );
4394 if( defaultselhash )
4395 jsonObjectFree( defaultselhash );
4396 free( join_clause );
4403 if( flags & DISABLE_I18N )
4406 i18n = osrfHashGet( field_def, "i18n" );
4408 if( str_is_true( i18n ) ) {
4409 buffer_fadd( select_buf,
4410 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4411 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4412 class_tname, cname, col_name, class_pkey, cname,
4413 class_pkey, locale, _alias );
4415 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4416 cname, col_name, _alias );
4419 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4420 cname, col_name, _alias );
4427 "%s: Selected item is unexpected JSON type: %s",
4429 json_type( selfield->type )
4432 osrfAppSessionStatus(
4434 OSRF_STATUS_INTERNALSERVERERROR,
4435 "osrfMethodException",
4437 "Ill-formed SELECT item in JSON query"
4439 jsonIteratorFree( selclass_itr );
4440 buffer_free( select_buf );
4441 buffer_free( group_buf );
4442 if( defaultselhash )
4443 jsonObjectFree( defaultselhash );
4444 free( join_clause );
4448 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4449 if( obj_is_true( agg_obj ) )
4450 aggregate_found = 1;
4452 // Append a comma (except for the first one)
4453 // and add the column to a GROUP BY clause
4457 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4459 buffer_fadd( group_buf, " %d", sel_pos );
4463 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4465 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( elfield, "aggregate");
4466 if ( ! obj_is_true( aggregate_obj ) ) {
4470 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4473 buffer_fadd(group_buf, " %d", sel_pos);
4476 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4480 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4483 _column = searchFieldTransform(class_info->alias, field, selfield);
4484 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4485 OSRF_BUFFER_ADD(group_buf, _column);
4486 _column = searchFieldTransform(class_info->alias, field, selfield);
4493 } // end while -- iterating across SELECT columns
4495 } // end while -- iterating across classes
4497 jsonIteratorFree( selclass_itr );
4500 char* col_list = buffer_release( select_buf );
4502 // Make sure the SELECT list isn't empty. This can happen, for example,
4503 // if we try to build a default SELECT clause from a non-core table.
4506 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4508 osrfAppSessionStatus(
4510 OSRF_STATUS_INTERNALSERVERERROR,
4511 "osrfMethodException",
4513 "SELECT list is empty"
4516 buffer_free( group_buf );
4517 if( defaultselhash )
4518 jsonObjectFree( defaultselhash );
4519 free( join_clause );
4525 table = searchValueTransform( join_hash );
4527 table = strdup( curr_query->core.source_def );
4531 osrfAppSessionStatus(
4533 OSRF_STATUS_INTERNALSERVERERROR,
4534 "osrfMethodException",
4536 "Unable to identify table for core class"
4539 buffer_free( group_buf );
4540 if( defaultselhash )
4541 jsonObjectFree( defaultselhash );
4542 free( join_clause );
4546 // Put it all together
4547 growing_buffer* sql_buf = buffer_init( 128 );
4548 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4552 // Append the join clause, if any
4554 buffer_add(sql_buf, join_clause );
4555 free( join_clause );
4558 char* order_by_list = NULL;
4559 char* having_buf = NULL;
4561 if( !from_function ) {
4563 // Build a WHERE clause, if there is one
4565 buffer_add( sql_buf, " WHERE " );
4567 // and it's on the WHERE clause
4568 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4571 osrfAppSessionStatus(
4573 OSRF_STATUS_INTERNALSERVERERROR,
4574 "osrfMethodException",
4576 "Severe query error in WHERE predicate -- see error log for more details"
4579 buffer_free( group_buf );
4580 buffer_free( sql_buf );
4581 if( defaultselhash )
4582 jsonObjectFree( defaultselhash );
4586 buffer_add( sql_buf, pred );
4590 // Build a HAVING clause, if there is one
4593 // and it's on the the WHERE clause
4594 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4596 if( ! having_buf ) {
4598 osrfAppSessionStatus(
4600 OSRF_STATUS_INTERNALSERVERERROR,
4601 "osrfMethodException",
4603 "Severe query error in HAVING predicate -- see error log for more details"
4606 buffer_free( group_buf );
4607 buffer_free( sql_buf );
4608 if( defaultselhash )
4609 jsonObjectFree( defaultselhash );
4614 // Build an ORDER BY clause, if there is one
4615 if( NULL == order_hash )
4616 ; // No ORDER BY? do nothing
4617 else if( JSON_ARRAY == order_hash->type ) {
4618 order_by_list = buildOrderByFromArray( ctx, order_hash );
4619 if( !order_by_list ) {
4621 buffer_free( group_buf );
4622 buffer_free( sql_buf );
4623 if( defaultselhash )
4624 jsonObjectFree( defaultselhash );
4627 } else if( JSON_HASH == order_hash->type ) {
4628 // This hash is keyed on class alias. Each class has either
4629 // an array of field names or a hash keyed on field name.
4630 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4631 jsonIterator* class_itr = jsonNewIterator( order_hash );
4632 while( (snode = jsonIteratorNext( class_itr )) ) {
4634 ClassInfo* order_class_info = search_alias( class_itr->key );
4635 if( ! order_class_info ) {
4636 osrfLogError( OSRF_LOG_MARK,
4637 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4638 modulename, class_itr->key );
4640 osrfAppSessionStatus(
4642 OSRF_STATUS_INTERNALSERVERERROR,
4643 "osrfMethodException",
4645 "Invalid class referenced in ORDER BY clause -- "
4646 "see error log for more details"
4648 jsonIteratorFree( class_itr );
4649 buffer_free( order_buf );
4651 buffer_free( group_buf );
4652 buffer_free( sql_buf );
4653 if( defaultselhash )
4654 jsonObjectFree( defaultselhash );
4658 osrfHash* field_list_def = order_class_info->fields;
4660 if( snode->type == JSON_HASH ) {
4662 // Hash is keyed on field names from the current class. For each field
4663 // there is another layer of hash to define the sorting details, if any,
4664 // or a string to indicate direction of sorting.
4665 jsonIterator* order_itr = jsonNewIterator( snode );
4666 while( (onode = jsonIteratorNext( order_itr )) ) {
4668 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4670 osrfLogError( OSRF_LOG_MARK,
4671 "%s: Invalid field \"%s\" in ORDER BY clause",
4672 modulename, order_itr->key );
4674 osrfAppSessionStatus(
4676 OSRF_STATUS_INTERNALSERVERERROR,
4677 "osrfMethodException",
4679 "Invalid field in ORDER BY clause -- "
4680 "see error log for more details"
4682 jsonIteratorFree( order_itr );
4683 jsonIteratorFree( class_itr );
4684 buffer_free( order_buf );
4686 buffer_free( group_buf );
4687 buffer_free( sql_buf );
4688 if( defaultselhash )
4689 jsonObjectFree( defaultselhash );
4691 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4692 osrfLogError( OSRF_LOG_MARK,
4693 "%s: Virtual field \"%s\" in ORDER BY clause",
4694 modulename, order_itr->key );
4696 osrfAppSessionStatus(
4698 OSRF_STATUS_INTERNALSERVERERROR,
4699 "osrfMethodException",
4701 "Virtual field in ORDER BY clause -- "
4702 "see error log for more details"
4704 jsonIteratorFree( order_itr );
4705 jsonIteratorFree( class_itr );
4706 buffer_free( order_buf );
4708 buffer_free( group_buf );
4709 buffer_free( sql_buf );
4710 if( defaultselhash )
4711 jsonObjectFree( defaultselhash );
4715 const char* direction = NULL;
4716 if( onode->type == JSON_HASH ) {
4717 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4718 string = searchFieldTransform(
4720 osrfHashGet( field_list_def, order_itr->key ),
4724 if( ctx ) osrfAppSessionStatus(
4726 OSRF_STATUS_INTERNALSERVERERROR,
4727 "osrfMethodException",
4729 "Severe query error in ORDER BY clause -- "
4730 "see error log for more details"
4732 jsonIteratorFree( order_itr );
4733 jsonIteratorFree( class_itr );
4735 buffer_free( group_buf );
4736 buffer_free( order_buf);
4737 buffer_free( sql_buf );
4738 if( defaultselhash )
4739 jsonObjectFree( defaultselhash );
4743 growing_buffer* field_buf = buffer_init( 16 );
4744 buffer_fadd( field_buf, "\"%s\".%s",
4745 class_itr->key, order_itr->key );
4746 string = buffer_release( field_buf );
4749 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4750 const char* dir = jsonObjectGetString( tmp_const );
4751 if(!strncasecmp( dir, "d", 1 )) {
4752 direction = " DESC";
4758 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4759 osrfLogError( OSRF_LOG_MARK,
4760 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4761 modulename, json_type( onode->type ) );
4763 osrfAppSessionStatus(
4765 OSRF_STATUS_INTERNALSERVERERROR,
4766 "osrfMethodException",
4768 "Malformed ORDER BY clause -- see error log for more details"
4770 jsonIteratorFree( order_itr );
4771 jsonIteratorFree( class_itr );
4773 buffer_free( group_buf );
4774 buffer_free( order_buf );
4775 buffer_free( sql_buf );
4776 if( defaultselhash )
4777 jsonObjectFree( defaultselhash );
4781 string = strdup( order_itr->key );
4782 const char* dir = jsonObjectGetString( onode );
4783 if( !strncasecmp( dir, "d", 1 )) {
4784 direction = " DESC";
4791 OSRF_BUFFER_ADD( order_buf, ", " );
4793 order_buf = buffer_init( 128 );
4795 OSRF_BUFFER_ADD( order_buf, string );
4799 OSRF_BUFFER_ADD( order_buf, direction );
4803 jsonIteratorFree( order_itr );
4805 } else if( snode->type == JSON_ARRAY ) {
4807 // Array is a list of fields from the current class
4808 unsigned long order_idx = 0;
4809 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4811 const char* _f = jsonObjectGetString( onode );
4813 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4815 osrfLogError( OSRF_LOG_MARK,
4816 "%s: Invalid field \"%s\" in ORDER BY clause",
4819 osrfAppSessionStatus(
4821 OSRF_STATUS_INTERNALSERVERERROR,
4822 "osrfMethodException",
4824 "Invalid field in ORDER BY clause -- "
4825 "see error log for more details"
4827 jsonIteratorFree( class_itr );
4828 buffer_free( order_buf );
4830 buffer_free( group_buf );
4831 buffer_free( sql_buf );
4832 if( defaultselhash )
4833 jsonObjectFree( defaultselhash );
4835 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4836 osrfLogError( OSRF_LOG_MARK,
4837 "%s: Virtual field \"%s\" in ORDER BY clause",
4840 osrfAppSessionStatus(
4842 OSRF_STATUS_INTERNALSERVERERROR,
4843 "osrfMethodException",
4845 "Virtual field in ORDER BY clause -- "
4846 "see error log for more details"
4848 jsonIteratorFree( class_itr );
4849 buffer_free( order_buf );
4851 buffer_free( group_buf );
4852 buffer_free( sql_buf );
4853 if( defaultselhash )
4854 jsonObjectFree( defaultselhash );
4859 OSRF_BUFFER_ADD( order_buf, ", " );
4861 order_buf = buffer_init( 128 );
4863 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4867 // IT'S THE OOOOOOOOOOOLD STYLE!
4869 osrfLogError( OSRF_LOG_MARK,
4870 "%s: Possible SQL injection attempt; direct order by is not allowed",
4873 osrfAppSessionStatus(
4875 OSRF_STATUS_INTERNALSERVERERROR,
4876 "osrfMethodException",
4878 "Severe query error -- see error log for more details"
4883 buffer_free( group_buf );
4884 buffer_free( order_buf );
4885 buffer_free( sql_buf );
4886 if( defaultselhash )
4887 jsonObjectFree( defaultselhash );
4888 jsonIteratorFree( class_itr );
4892 jsonIteratorFree( class_itr );
4894 order_by_list = buffer_release( order_buf );
4896 osrfLogError( OSRF_LOG_MARK,
4897 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4898 modulename, json_type( order_hash->type ) );
4900 osrfAppSessionStatus(
4902 OSRF_STATUS_INTERNALSERVERERROR,
4903 "osrfMethodException",
4905 "Malformed ORDER BY clause -- see error log for more details"
4908 buffer_free( group_buf );
4909 buffer_free( sql_buf );
4910 if( defaultselhash )
4911 jsonObjectFree( defaultselhash );
4916 string = buffer_release( group_buf );
4918 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4919 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4920 OSRF_BUFFER_ADD( sql_buf, string );
4925 if( having_buf && *having_buf ) {
4926 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4927 OSRF_BUFFER_ADD( sql_buf, having_buf );
4931 if( order_by_list ) {
4933 if( *order_by_list ) {
4934 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4935 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4938 free( order_by_list );
4942 const char* str = jsonObjectGetString( limit );
4943 if (str) { // limit could be JSON_NULL, etc.
4944 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4949 const char* str = jsonObjectGetString( offset );
4951 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4955 if( !(flags & SUBSELECT) )
4956 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4958 if( defaultselhash )
4959 jsonObjectFree( defaultselhash );
4961 return buffer_release( sql_buf );
4963 } // end of SELECT()
4966 @brief Build a list of ORDER BY expressions.
4967 @param ctx Pointer to the method context.
4968 @param order_array Pointer to a JSON_ARRAY of field specifications.
4969 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
4970 Each expression may be either a column reference or a function call whose first parameter
4971 is a column reference.
4973 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
4974 It may optionally include entries for "direction" and/or "transform".
4976 The calling code is responsible for freeing the returned string.
4978 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
4979 if( ! order_array ) {
4980 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
4983 osrfAppSessionStatus(
4985 OSRF_STATUS_INTERNALSERVERERROR,
4986 "osrfMethodException",
4988 "Logic error: ORDER BY clause expected, not found; "
4989 "see error log for more details"
4992 } else if( order_array->type != JSON_ARRAY ) {
4993 osrfLogError( OSRF_LOG_MARK,
4994 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
4996 osrfAppSessionStatus(
4998 OSRF_STATUS_INTERNALSERVERERROR,
4999 "osrfMethodException",
5001 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
5005 growing_buffer* order_buf = buffer_init( 128 );
5006 int first = 1; // boolean
5008 jsonObject* order_spec;
5009 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
5011 if( JSON_HASH != order_spec->type ) {
5012 osrfLogError( OSRF_LOG_MARK,
5013 "%s: Malformed field specification in ORDER BY clause; "
5014 "expected JSON_HASH, found %s",
5015 modulename, json_type( order_spec->type ) );
5017 osrfAppSessionStatus(
5019 OSRF_STATUS_INTERNALSERVERERROR,
5020 "osrfMethodException",
5022 "Malformed ORDER BY clause -- see error log for more details"
5024 buffer_free( order_buf );
5028 const char* class_alias =
5029 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5031 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5033 jsonObject* compare_to = jsonObjectGetKeyConst( order_spec, "compare" );
5035 if( !field || !class_alias ) {
5036 osrfLogError( OSRF_LOG_MARK,
5037 "%s: Missing class or field name in field specification of ORDER BY clause",
5040 osrfAppSessionStatus(
5042 OSRF_STATUS_INTERNALSERVERERROR,
5043 "osrfMethodException",
5045 "Malformed ORDER BY clause -- see error log for more details"
5047 buffer_free( order_buf );
5051 const ClassInfo* order_class_info = search_alias( class_alias );
5052 if( ! order_class_info ) {
5053 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5054 "not in FROM clause, skipping it", modulename, class_alias );
5058 // Add a separating comma, except at the beginning
5062 OSRF_BUFFER_ADD( order_buf, ", " );
5064 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5066 osrfLogError( OSRF_LOG_MARK,
5067 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5068 modulename, class_alias, field );
5070 osrfAppSessionStatus(
5072 OSRF_STATUS_INTERNALSERVERERROR,
5073 "osrfMethodException",
5075 "Invalid field referenced in ORDER BY clause -- "
5076 "see error log for more details"
5080 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5081 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5082 modulename, field );
5084 osrfAppSessionStatus(
5086 OSRF_STATUS_INTERNALSERVERERROR,
5087 "osrfMethodException",
5089 "Virtual field in ORDER BY clause -- see error log for more details"
5091 buffer_free( order_buf );
5095 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5096 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5097 if( ! transform_str ) {
5099 osrfAppSessionStatus(
5101 OSRF_STATUS_INTERNALSERVERERROR,
5102 "osrfMethodException",
5104 "Severe query error in ORDER BY clause -- "
5105 "see error log for more details"
5107 buffer_free( order_buf );
5111 OSRF_BUFFER_ADD( order_buf, transform_str );
5112 free( transform_str );
5113 } else if( compare_to ) {
5114 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5115 if( ! compare_str ) {
5117 osrfAppSessionStatus(
5119 OSRF_STATUS_INTERNALSERVERERROR,
5120 "osrfMethodException",
5122 "Severe query error in ORDER BY clause -- "
5123 "see error log for more details"
5125 buffer_free( order_buf );
5129 buffer_fadd( order_buf, "(%s)", compare_str );
5130 free( compare_str );
5133 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5135 const char* direction =
5136 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5138 if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5139 OSRF_BUFFER_ADD( order_buf, " DESC" );
5141 OSRF_BUFFER_ADD( order_buf, " ASC" );
5145 return buffer_release( order_buf );
5149 @brief Build a SELECT statement.
5150 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5151 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5152 @param meta Pointer to the class metadata for the core class.
5153 @param ctx Pointer to the method context.
5154 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5156 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5157 "order_by", "limit", and "offset".
5159 The SELECT statements built here are distinct from those built for the json_query method.
5161 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5162 osrfHash* meta, osrfMethodContext* ctx ) {
5164 const char* locale = osrf_message_get_last_locale();
5166 osrfHash* fields = osrfHashGet( meta, "fields" );
5167 const char* core_class = osrfHashGet( meta, "classname" );
5169 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5171 jsonObject* selhash = NULL;
5172 jsonObject* defaultselhash = NULL;
5174 growing_buffer* sql_buf = buffer_init( 128 );
5175 growing_buffer* select_buf = buffer_init( 128 );
5177 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5178 defaultselhash = jsonNewObjectType( JSON_HASH );
5179 selhash = defaultselhash;
5182 // If there's no SELECT list for the core class, build one
5183 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5184 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5186 // Add every non-virtual field to the field list
5187 osrfHash* field_def = NULL;
5188 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5189 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5190 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5191 const char* field = osrfHashIteratorKey( field_itr );
5192 jsonObjectPush( field_list, jsonNewObject( field ) );
5195 osrfHashIteratorFree( field_itr );
5196 jsonObjectSetKey( selhash, core_class, field_list );
5199 // Build a list of columns for the SELECT clause
5201 const jsonObject* snode = NULL;
5202 jsonIterator* class_itr = jsonNewIterator( selhash );
5203 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5205 // If the class isn't in the IDL, ignore it
5206 const char* cname = class_itr->key;
5207 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5211 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5212 if( strcmp( core_class, class_itr->key )) {
5216 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5217 if( !found->size ) {
5218 jsonObjectFree( found );
5222 jsonObjectFree( found );
5225 const jsonObject* node = NULL;
5226 jsonIterator* select_itr = jsonNewIterator( snode );
5227 while( (node = jsonIteratorNext( select_itr )) ) {
5228 const char* item_str = jsonObjectGetString( node );
5229 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5230 char* fname = osrfHashGet( field, "name" );
5235 if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5241 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5246 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5247 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5250 i18n = osrfHashGet( field, "i18n" );
5252 if( str_is_true( i18n ) ) {
5253 char* pkey = osrfHashGet( idlClass, "primarykey" );
5254 char* tname = osrfHashGet( idlClass, "tablename" );
5256 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5257 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5258 tname, cname, fname, pkey, cname, pkey, locale, fname );
5260 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5263 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5267 jsonIteratorFree( select_itr );
5270 jsonIteratorFree( class_itr );
5272 char* col_list = buffer_release( select_buf );
5273 char* table = oilsGetRelation( meta );
5275 table = strdup( "(null)" );
5277 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5281 // Clear the query stack (as a fail-safe precaution against possible
5282 // leftover garbage); then push the first query frame onto the stack.
5283 clear_query_stack();
5285 if( add_query_core( NULL, core_class ) ) {
5287 osrfAppSessionStatus(
5289 OSRF_STATUS_INTERNALSERVERERROR,
5290 "osrfMethodException",
5292 "Unable to build query frame for core class"
5294 buffer_free( sql_buf );
5295 if( defaultselhash )
5296 jsonObjectFree( defaultselhash );
5300 // Add the JOIN clauses, if any
5302 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5303 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5304 OSRF_BUFFER_ADD( sql_buf, join_clause );
5305 free( join_clause );
5308 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5309 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5311 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5313 // Add the conditions in the WHERE clause
5314 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5316 osrfAppSessionStatus(
5318 OSRF_STATUS_INTERNALSERVERERROR,
5319 "osrfMethodException",
5321 "Severe query error -- see error log for more details"
5323 buffer_free( sql_buf );
5324 if( defaultselhash )
5325 jsonObjectFree( defaultselhash );
5326 clear_query_stack();
5329 buffer_add( sql_buf, pred );
5333 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5334 if( rest_of_query ) {
5335 const jsonObject* order_by = NULL;
5336 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5338 char* order_by_list = NULL;
5340 if( JSON_ARRAY == order_by->type ) {
5341 order_by_list = buildOrderByFromArray( ctx, order_by );
5342 if( !order_by_list ) {
5343 buffer_free( sql_buf );
5344 if( defaultselhash )
5345 jsonObjectFree( defaultselhash );
5346 clear_query_stack();
5349 } else if( JSON_HASH == order_by->type ) {
5350 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5351 // and build a list of ORDER BY expressions.
5352 growing_buffer* order_buf = buffer_init( 128 );
5354 jsonIterator* class_itr = jsonNewIterator( order_by );
5355 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5357 ClassInfo* order_class_info = search_alias( class_itr->key );
5358 if( ! order_class_info )
5359 continue; // class not referenced by FROM clause? Ignore it.
5361 if( JSON_HASH == snode->type ) {
5363 // If the data for the current class is a JSON_HASH, then it is
5364 // keyed on field name.
5366 const jsonObject* onode = NULL;
5367 jsonIterator* order_itr = jsonNewIterator( snode );
5368 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5370 osrfHash* field_def = osrfHashGet(
5371 order_class_info->fields, order_itr->key );
5373 continue; // Field not defined in IDL? Ignore it.
5374 if( str_is_true( osrfHashGet( field_def, "virtual")))
5375 continue; // Field is virtual? Ignore it.
5377 char* field_str = NULL;
5378 char* direction = NULL;
5379 if( onode->type == JSON_HASH ) {
5380 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5381 field_str = searchFieldTransform(
5382 class_itr->key, field_def, onode );
5384 osrfAppSessionStatus(
5386 OSRF_STATUS_INTERNALSERVERERROR,
5387 "osrfMethodException",
5389 "Severe query error in ORDER BY clause -- "
5390 "see error log for more details"
5392 jsonIteratorFree( order_itr );
5393 jsonIteratorFree( class_itr );
5394 buffer_free( order_buf );
5395 buffer_free( sql_buf );
5396 if( defaultselhash )
5397 jsonObjectFree( defaultselhash );
5398 clear_query_stack();
5402 growing_buffer* field_buf = buffer_init( 16 );
5403 buffer_fadd( field_buf, "\"%s\".%s",
5404 class_itr->key, order_itr->key );
5405 field_str = buffer_release( field_buf );
5408 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5409 const char* dir = jsonObjectGetString( order_by );
5410 if(!strncasecmp( dir, "d", 1 )) {
5411 direction = " DESC";
5415 field_str = strdup( order_itr->key );
5416 const char* dir = jsonObjectGetString( onode );
5417 if( !strncasecmp( dir, "d", 1 )) {
5418 direction = " DESC";
5427 buffer_add( order_buf, ", " );
5430 buffer_add( order_buf, field_str );
5434 buffer_add( order_buf, direction );
5436 } // end while; looping over ORDER BY expressions
5438 jsonIteratorFree( order_itr );
5440 } else if( JSON_STRING == snode->type ) {
5441 // We expect a comma-separated list of sort fields.
5442 const char* str = jsonObjectGetString( snode );
5443 if( strchr( str, ';' )) {
5444 // No semicolons allowed. It is theoretically possible for a
5445 // legitimate semicolon to occur within quotes, but it's not likely
5446 // to occur in practice in the context of an ORDER BY list.
5447 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5448 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5450 osrfAppSessionStatus(
5452 OSRF_STATUS_INTERNALSERVERERROR,
5453 "osrfMethodException",
5455 "Possible attempt at SOL injection -- "
5456 "semicolon found in ORDER BY list"
5459 jsonIteratorFree( class_itr );
5460 buffer_free( order_buf );
5461 buffer_free( sql_buf );
5462 if( defaultselhash )
5463 jsonObjectFree( defaultselhash );
5464 clear_query_stack();
5467 buffer_add( order_buf, str );
5471 } // end while; looping over order_by classes
5473 jsonIteratorFree( class_itr );
5474 order_by_list = buffer_release( order_buf );
5477 osrfLogWarning( OSRF_LOG_MARK,
5478 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5479 "no ORDER BY generated" );
5482 if( order_by_list && *order_by_list ) {
5483 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5484 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5487 free( order_by_list );
5490 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5492 const char* str = jsonObjectGetString( limit );
5502 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5504 const char* str = jsonObjectGetString( offset );
5515 if( defaultselhash )
5516 jsonObjectFree( defaultselhash );
5517 clear_query_stack();
5519 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5520 return buffer_release( sql_buf );
5523 int doJSONSearch ( osrfMethodContext* ctx ) {
5524 if(osrfMethodVerifyContext( ctx )) {
5525 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5529 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5533 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5537 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5538 flags |= SELECT_DISTINCT;
5540 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5541 flags |= DISABLE_I18N;
5543 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5544 clear_query_stack(); // a possibly needless precaution
5545 char* sql = buildQuery( ctx, hash, flags );
5546 clear_query_stack();
5553 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5556 dbhandle = writehandle;
5558 dbi_result result = dbi_conn_query( dbhandle, sql );
5561 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5563 if( dbi_result_first_row( result )) {
5564 /* JSONify the result */
5565 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5568 jsonObject* return_val = oilsMakeJSONFromResult( result );
5569 osrfAppRespond( ctx, return_val );
5570 jsonObjectFree( return_val );
5571 } while( dbi_result_next_row( result ));
5574 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5577 osrfAppRespondComplete( ctx, NULL );
5579 /* clean up the query */
5580 dbi_result_free( result );
5585 int errnum = dbi_conn_error( dbhandle, &msg );
5586 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5587 modulename, sql, errnum, msg ? msg : "(No description available)" );
5588 osrfAppSessionStatus(
5590 OSRF_STATUS_INTERNALSERVERERROR,
5591 "osrfMethodException",
5593 "Severe query error -- see error log for more details"
5595 if( !oilsIsDBConnected( dbhandle ))
5596 osrfAppSessionPanic( ctx->session );
5603 // The last parameter, err, is used to report an error condition by updating an int owned by
5604 // the calling code.
5606 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5607 // It is the responsibility of the calling code to initialize *err before the
5608 // call, so that it will be able to make sense of the result.
5610 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5611 // redundant anyway.
5612 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5613 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5616 dbhandle = writehandle;
5618 char* core_class = osrfHashGet( class_meta, "classname" );
5619 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5621 char* pkey = osrfHashGet( class_meta, "primarykey" );
5623 if (!ctx->session->userData)
5624 (void) initSessionCache( ctx );
5626 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5627 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5628 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5630 int i_respond_directly = 0;
5631 int flesh_depth = 0;
5633 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5635 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5640 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5642 dbi_result result = dbi_conn_query( dbhandle, sql );
5643 if( NULL == result ) {
5645 int errnum = dbi_conn_error( dbhandle, &msg );
5646 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5647 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5648 msg ? msg : "(No description available)" );
5649 if( !oilsIsDBConnected( dbhandle ))
5650 osrfAppSessionPanic( ctx->session );
5651 osrfAppSessionStatus(
5653 OSRF_STATUS_INTERNALSERVERERROR,
5654 "osrfMethodException",
5656 "Severe query error -- see error log for more details"
5663 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5666 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5667 jsonObject* row_obj = NULL;
5669 // The following two steps are for verifyObjectPCRUD()'s benefit.
5670 // 1. get the flesh depth
5671 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5673 flesh_depth = (int) jsonObjectGetNumber( _tmp );
5674 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5675 flesh_depth = max_flesh_depth;
5678 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
5679 // over the whole life of this request. This means if we've already set
5680 // up a rs_size_req_%d, do nothing.
5681 // a. Incidentally, we can also use this opportunity to set i_respond_directly
5682 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
5683 if( !rs_size ) { // pointer null, so value not set in hash
5684 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
5685 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
5687 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
5688 unsigned long long result_count = dbi_result_get_numrows( result );
5689 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
5690 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
5693 if( dbi_result_first_row( result )) {
5695 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5696 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5697 // eliminate the duplicates.
5698 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5699 osrfHash* dedup = osrfNewHash();
5701 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5702 char* pkey_val = oilsFMGetString( row_obj, pkey );
5703 if( osrfHashGet( dedup, pkey_val ) ) {
5704 jsonObjectFree( row_obj );
5707 if( !enforce_pcrud || !need_to_verify ||
5708 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
5709 osrfHashSet( dedup, pkey_val, pkey_val );
5710 jsonObjectPush( res_list, row_obj );
5713 } while( dbi_result_next_row( result ));
5714 osrfHashFree( dedup );
5717 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5721 /* clean up the query */
5722 dbi_result_free( result );
5725 // If we're asked to flesh, and there's anything to flesh, then flesh it
5726 // (formerly we would skip fleshing if in pcrud mode, but now we support
5727 // fleshing even in PCRUD).
5728 if( res_list->size ) {
5729 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
5730 jsonObject* flesh_fields;
5731 jsonObject* flesh_blob = NULL;
5732 osrfStringArray* link_fields = NULL;
5733 osrfHash* links = NULL;
5737 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
5738 if( temp_blob && flesh_depth > 0 ) {
5740 flesh_blob = jsonObjectClone( temp_blob );
5741 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
5743 links = osrfHashGet( class_meta, "links" );
5745 // Make an osrfStringArray of the names of fields to be fleshed
5746 if( flesh_fields ) {
5747 if( flesh_fields->size == 1 ) {
5748 const char* _t = jsonObjectGetString(
5749 jsonObjectGetIndex( flesh_fields, 0 ) );
5750 if( !strcmp( _t, "*" ))
5751 link_fields = osrfHashKeys( links );
5754 if( !link_fields ) {
5756 link_fields = osrfNewStringArray( 1 );
5757 jsonIterator* _i = jsonNewIterator( flesh_fields );
5758 while ((_f = jsonIteratorNext( _i ))) {
5759 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5761 jsonIteratorFree( _i );
5764 want_flesh = link_fields ? 1 : 0;
5768 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5770 // Iterate over the JSON_ARRAY of rows
5772 unsigned long res_idx = 0;
5773 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5776 const char* link_field;
5778 // Iterate over the list of fleshable fields
5780 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5782 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5784 osrfHash* kid_link = osrfHashGet( links, link_field );
5786 continue; // Not a link field; skip it
5788 osrfHash* field = osrfHashGet( fields, link_field );
5790 continue; // Not a field at all; skip it (IDL is ill-formed)
5792 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5793 osrfHashGet( kid_link, "class" ));
5795 continue; // The class it links to doesn't exist; skip it
5797 const char* reltype = osrfHashGet( kid_link, "reltype" );
5799 continue; // No reltype; skip it (IDL is ill-formed)
5801 osrfHash* value_field = field;
5803 if( !strcmp( reltype, "has_many" )
5804 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5805 value_field = osrfHashGet(
5806 fields, osrfHashGet( class_meta, "primarykey" ) );
5809 int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
5810 // fleshing pcrud case: we require the controller in need_to_verify mode
5811 if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
5812 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
5816 (unsigned long) atoi( osrfHashGet(field, "array_position") ),
5818 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
5824 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5826 if( link_map->size > 0 ) {
5827 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5830 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5835 osrfHashGet( kid_link, "class" ),
5842 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5843 osrfHashGet( kid_link, "field" ),
5844 osrfHashGet( kid_link, "class" ),
5845 osrfHashGet( kid_link, "key" ),
5846 osrfHashGet( kid_link, "reltype" )
5849 const char* search_key = jsonObjectGetString(
5850 jsonObjectGetIndex( cur,
5851 atoi( osrfHashGet( value_field, "array_position" ) )
5856 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5860 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5862 // construct WHERE clause
5863 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5866 osrfHashGet( kid_link, "key" ),
5867 jsonNewObject( search_key )
5870 // construct the rest of the query, mostly
5871 // by copying pieces of the previous level of query
5872 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5873 jsonObjectSetKey( rest_of_query, "flesh",
5874 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5878 jsonObjectSetKey( rest_of_query, "flesh_fields",
5879 jsonObjectClone( flesh_blob ));
5881 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5882 jsonObjectSetKey( rest_of_query, "order_by",
5883 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5887 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5888 jsonObjectSetKey( rest_of_query, "select",
5889 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5893 // do the query, recursively, to expand the fleshable field
5894 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5895 where_clause, rest_of_query, err );
5897 jsonObjectFree( where_clause );
5898 jsonObjectFree( rest_of_query );
5901 osrfStringArrayFree( link_fields );
5902 jsonObjectFree( res_list );
5903 jsonObjectFree( flesh_blob );
5907 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5908 osrfHashGet( kid_link, "class" ), kids->size );
5910 // Traverse the result set
5911 jsonObject* X = NULL;
5912 if( link_map->size > 0 && kids->size > 0 ) {
5914 kids = jsonNewObjectType( JSON_ARRAY );
5916 jsonObject* _k_node;
5917 unsigned long res_idx = 0;
5918 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5924 (unsigned long) atoi(
5930 osrfHashGet( kid_link, "class" )
5934 osrfStringArrayGetString( link_map, 0 )
5942 } // end while loop traversing X
5945 if (kids->size > 0) {
5947 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5948 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
5950 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5951 osrfHashGet( kid_link, "field" ));
5954 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5955 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5960 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5962 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5963 osrfHashGet( kid_link, "field" ) );
5966 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5967 jsonObjectClone( kids )
5972 jsonObjectFree( kids );
5976 jsonObjectFree( kids );
5978 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5979 osrfHashGet( kid_link, "field" ) );
5980 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5982 } // end while loop traversing list of fleshable fields
5985 if( i_respond_directly ) {
5986 if ( *methodtype == 'i' ) {
5987 osrfAppRespond( ctx,
5988 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
5990 osrfAppRespond( ctx, cur );
5993 } // end while loop traversing res_list
5994 jsonObjectFree( flesh_blob );
5995 osrfStringArrayFree( link_fields );
5998 if( i_respond_directly ) {
5999 jsonObjectFree( res_list );
6000 return jsonNewObjectType( JSON_ARRAY );
6007 int doUpdate( osrfMethodContext* ctx ) {
6008 if( osrfMethodVerifyContext( ctx )) {
6009 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6014 timeout_needs_resetting = 1;
6016 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6018 jsonObject* target = NULL;
6020 target = jsonObjectGetIndex( ctx->params, 1 );
6022 target = jsonObjectGetIndex( ctx->params, 0 );
6024 if(!verifyObjectClass( ctx, target )) {
6025 osrfAppRespondComplete( ctx, NULL );
6029 if( getXactId( ctx ) == NULL ) {
6030 osrfAppSessionStatus(
6032 OSRF_STATUS_BADREQUEST,
6033 "osrfMethodException",
6035 "No active transaction -- required for UPDATE"
6037 osrfAppRespondComplete( ctx, NULL );
6041 // The following test is harmless but redundant. If a class is
6042 // readonly, we don't register an update method for it.
6043 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6044 osrfAppSessionStatus(
6046 OSRF_STATUS_BADREQUEST,
6047 "osrfMethodException",
6049 "Cannot UPDATE readonly class"
6051 osrfAppRespondComplete( ctx, NULL );
6055 const char* trans_id = getXactId( ctx );
6057 // Set the last_xact_id
6058 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6060 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6061 trans_id, target->classname, index );
6062 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6065 char* pkey = osrfHashGet( meta, "primarykey" );
6066 osrfHash* fields = osrfHashGet( meta, "fields" );
6068 char* id = oilsFMGetString( target, pkey );
6072 "%s updating %s object with %s = %s",
6074 osrfHashGet( meta, "fieldmapper" ),
6079 dbhandle = writehandle;
6080 growing_buffer* sql = buffer_init( 128 );
6081 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6084 osrfHash* field_def = NULL;
6085 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6086 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6088 // Skip virtual fields, and the primary key
6089 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6092 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6096 const char* field_name = osrfHashIteratorKey( field_itr );
6097 if( ! strcmp( field_name, pkey ) )
6100 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6102 int value_is_numeric = 0; // boolean
6104 if( field_object && field_object->classname ) {
6105 value = oilsFMGetString(
6107 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6109 } else if( field_object && JSON_BOOL == field_object->type ) {
6110 if( jsonBoolIsTrue( field_object ) )
6111 value = strdup( "t" );
6113 value = strdup( "f" );
6115 value = jsonObjectToSimpleString( field_object );
6116 if( field_object && JSON_NUMBER == field_object->type )
6117 value_is_numeric = 1;
6120 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6121 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6123 if( !field_object || field_object->type == JSON_NULL ) {
6124 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6125 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6129 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6130 buffer_fadd( sql, " %s = NULL", field_name );
6133 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6137 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6139 const char* numtype = get_datatype( field_def );
6140 if( !strncmp( numtype, "INT", 3 ) ) {
6141 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6142 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6143 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6145 // Must really be intended as a string, so quote it
6146 if( dbi_conn_quote_string( dbhandle, &value )) {
6147 buffer_fadd( sql, " %s = %s", field_name, value );
6149 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6150 modulename, value );
6151 osrfAppSessionStatus(
6153 OSRF_STATUS_INTERNALSERVERERROR,
6154 "osrfMethodException",
6156 "Error quoting string -- please see the error log for more details"
6160 osrfHashIteratorFree( field_itr );
6162 osrfAppRespondComplete( ctx, NULL );
6167 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6170 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6174 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6175 buffer_fadd( sql, " %s = %s", field_name, value );
6177 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6178 osrfAppSessionStatus(
6180 OSRF_STATUS_INTERNALSERVERERROR,
6181 "osrfMethodException",
6183 "Error quoting string -- please see the error log for more details"
6187 osrfHashIteratorFree( field_itr );
6189 osrfAppRespondComplete( ctx, NULL );
6198 osrfHashIteratorFree( field_itr );
6200 jsonObject* obj = jsonNewObject( id );
6202 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6203 dbi_conn_quote_string( dbhandle, &id );
6205 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6207 char* query = buffer_release( sql );
6208 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6210 dbi_result result = dbi_conn_query( dbhandle, query );
6215 jsonObjectFree( obj );
6216 obj = jsonNewObject( NULL );
6218 int errnum = dbi_conn_error( dbhandle, &msg );
6221 "%s ERROR updating %s object with %s = %s: %d %s",
6223 osrfHashGet( meta, "fieldmapper" ),
6227 msg ? msg : "(No description available)"
6229 osrfAppSessionStatus(
6231 OSRF_STATUS_INTERNALSERVERERROR,
6232 "osrfMethodException",
6234 "Error in updating a row -- please see the error log for more details"
6236 if( !oilsIsDBConnected( dbhandle ))
6237 osrfAppSessionPanic( ctx->session );
6240 dbi_result_free( result );
6243 osrfAppRespondComplete( ctx, obj );
6244 jsonObjectFree( obj );
6248 int doDelete( osrfMethodContext* ctx ) {
6249 if( osrfMethodVerifyContext( ctx )) {
6250 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6255 timeout_needs_resetting = 1;
6257 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6259 if( getXactId( ctx ) == NULL ) {
6260 osrfAppSessionStatus(
6262 OSRF_STATUS_BADREQUEST,
6263 "osrfMethodException",
6265 "No active transaction -- required for DELETE"
6267 osrfAppRespondComplete( ctx, NULL );
6271 // The following test is harmless but redundant. If a class is
6272 // readonly, we don't register a delete method for it.
6273 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6274 osrfAppSessionStatus(
6276 OSRF_STATUS_BADREQUEST,
6277 "osrfMethodException",
6279 "Cannot DELETE readonly class"
6281 osrfAppRespondComplete( ctx, NULL );
6285 dbhandle = writehandle;
6287 char* pkey = osrfHashGet( meta, "primarykey" );
6294 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6295 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6296 osrfAppRespondComplete( ctx, NULL );
6300 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6302 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6303 osrfAppRespondComplete( ctx, NULL );
6306 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6311 "%s deleting %s object with %s = %s",
6313 osrfHashGet( meta, "fieldmapper" ),
6318 jsonObject* obj = jsonNewObject( id );
6320 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6321 dbi_conn_quote_string( writehandle, &id );
6323 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6324 osrfHashGet( meta, "tablename" ), pkey, id );
6329 jsonObjectFree( obj );
6330 obj = jsonNewObject( NULL );
6332 int errnum = dbi_conn_error( writehandle, &msg );
6335 "%s ERROR deleting %s object with %s = %s: %d %s",
6337 osrfHashGet( meta, "fieldmapper" ),
6341 msg ? msg : "(No description available)"
6343 osrfAppSessionStatus(
6345 OSRF_STATUS_INTERNALSERVERERROR,
6346 "osrfMethodException",
6348 "Error in deleting a row -- please see the error log for more details"
6350 if( !oilsIsDBConnected( writehandle ))
6351 osrfAppSessionPanic( ctx->session );
6353 dbi_result_free( result );
6357 osrfAppRespondComplete( ctx, obj );
6358 jsonObjectFree( obj );
6363 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6364 @param result An iterator for a result set; we only look at the current row.
6365 @param @meta Pointer to the class metadata for the core class.
6366 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6368 If a column is not defined in the IDL, or if it has no array_position defined for it in
6369 the IDL, or if it is defined as virtual, ignore it.
6371 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6372 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6373 array_position in the IDL.
6375 A field defined in the IDL but not represented in the returned row will leave a hole
6376 in the JSON_ARRAY. In effect it will be treated as a null value.
6378 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6379 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6380 classname corresponding to the @a meta argument.
6382 The calling code is responsible for freeing the the resulting jsonObject by calling
6385 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6386 if( !( result && meta )) return NULL;
6388 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6389 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6390 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6392 osrfHash* fields = osrfHashGet( meta, "fields" );
6394 int columnIndex = 1;
6395 const char* columnName;
6397 /* cycle through the columns in the row returned from the database */
6398 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6400 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6402 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6404 /* determine the field type and storage attributes */
6405 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6406 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6408 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6409 // or if it has no sequence number there, or if it's virtual, skip it.
6410 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6413 if( str_is_true( osrfHashGet( _f, "virtual" )))
6414 continue; // skip this column: IDL says it's virtual
6416 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6417 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6418 continue; // since we assign sequence numbers dynamically as we load the IDL.
6420 fmIndex = atoi( pos );
6421 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6423 continue; // This field is not defined in the IDL
6426 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6427 // sequence number from the IDL (which is likely to be different from the sequence
6428 // of columns in the SELECT clause).
6429 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6430 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6435 case DBI_TYPE_INTEGER :
6437 if( attr & DBI_INTEGER_SIZE8 )
6438 jsonObjectSetIndex( object, fmIndex,
6439 jsonNewNumberObject(
6440 dbi_result_get_longlong_idx( result, columnIndex )));
6442 jsonObjectSetIndex( object, fmIndex,
6443 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6447 case DBI_TYPE_DECIMAL :
6448 jsonObjectSetIndex( object, fmIndex,
6449 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6452 case DBI_TYPE_STRING :
6457 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6462 case DBI_TYPE_DATETIME : {
6464 char dt_string[ 256 ] = "";
6467 // Fetch the date column as a time_t
6468 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6470 // Translate the time_t to a human-readable string
6471 if( !( attr & DBI_DATETIME_DATE )) {
6472 gmtime_r( &_tmp_dt, &gmdt );
6473 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6474 } else if( !( attr & DBI_DATETIME_TIME )) {
6475 localtime_r( &_tmp_dt, &gmdt );
6476 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6478 localtime_r( &_tmp_dt, &gmdt );
6479 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6482 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6486 case DBI_TYPE_BINARY :
6487 osrfLogError( OSRF_LOG_MARK,
6488 "Can't do binary at column %s : index %d", columnName, columnIndex );
6497 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6498 if( !result ) return NULL;
6500 jsonObject* object = jsonNewObject( NULL );
6503 char dt_string[ 256 ];
6507 int columnIndex = 1;
6509 unsigned short type;
6510 const char* columnName;
6512 /* cycle through the column list */
6513 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6515 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6517 fmIndex = -1; // reset the position
6519 /* determine the field type and storage attributes */
6520 type = dbi_result_get_field_type_idx( result, columnIndex );
6521 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6523 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6524 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6529 case DBI_TYPE_INTEGER :
6531 if( attr & DBI_INTEGER_SIZE8 )
6532 jsonObjectSetKey( object, columnName,
6533 jsonNewNumberObject( dbi_result_get_longlong_idx(
6534 result, columnIndex )) );
6536 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6537 dbi_result_get_int_idx( result, columnIndex )) );
6540 case DBI_TYPE_DECIMAL :
6541 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6542 dbi_result_get_double_idx( result, columnIndex )) );
6545 case DBI_TYPE_STRING :
6546 jsonObjectSetKey( object, columnName,
6547 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6550 case DBI_TYPE_DATETIME :
6552 memset( dt_string, '\0', sizeof( dt_string ));
6553 memset( &gmdt, '\0', sizeof( gmdt ));
6555 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6557 if( !( attr & DBI_DATETIME_DATE )) {
6558 gmtime_r( &_tmp_dt, &gmdt );
6559 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6560 } else if( !( attr & DBI_DATETIME_TIME )) {
6561 localtime_r( &_tmp_dt, &gmdt );
6562 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6564 localtime_r( &_tmp_dt, &gmdt );
6565 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6568 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6571 case DBI_TYPE_BINARY :
6572 osrfLogError( OSRF_LOG_MARK,
6573 "Can't do binary at column %s : index %d", columnName, columnIndex );
6577 } // end while loop traversing result
6582 // Interpret a string as true or false
6583 int str_is_true( const char* str ) {
6584 if( NULL == str || strcasecmp( str, "true" ) )
6590 // Interpret a jsonObject as true or false
6591 static int obj_is_true( const jsonObject* obj ) {
6594 else switch( obj->type )
6602 if( strcasecmp( obj->value.s, "true" ) )
6606 case JSON_NUMBER : // Support 1/0 for perl's sake
6607 if( jsonObjectGetNumber( obj ) == 1.0 )
6616 // Translate a numeric code into a text string identifying a type of
6617 // jsonObject. To be used for building error messages.
6618 static const char* json_type( int code ) {
6624 return "JSON_ARRAY";
6626 return "JSON_STRING";
6628 return "JSON_NUMBER";
6634 return "(unrecognized)";
6638 // Extract the "primitive" attribute from an IDL field definition.
6639 // If we haven't initialized the app, then we must be running in
6640 // some kind of testbed. In that case, default to "string".
6641 static const char* get_primitive( osrfHash* field ) {
6642 const char* s = osrfHashGet( field, "primitive" );
6644 if( child_initialized )
6647 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6649 osrfHashGet( field, "name" )
6657 // Extract the "datatype" attribute from an IDL field definition.
6658 // If we haven't initialized the app, then we must be running in
6659 // some kind of testbed. In that case, default to to NUMERIC,
6660 // since we look at the datatype only for numbers.
6661 static const char* get_datatype( osrfHash* field ) {
6662 const char* s = osrfHashGet( field, "datatype" );
6664 if( child_initialized )
6667 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6669 osrfHashGet( field, "name" )
6678 @brief Determine whether a string is potentially a valid SQL identifier.
6679 @param s The identifier to be tested.
6680 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6682 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6683 need to follow all the rules exactly, such as requiring that the first character not
6686 We allow leading and trailing white space. In between, we do not allow punctuation
6687 (except for underscores and dollar signs), control characters, or embedded white space.
6689 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6690 for the foreseeable future such quoted identifiers are not likely to be an issue.
6692 int is_identifier( const char* s) {
6696 // Skip leading white space
6697 while( isspace( (unsigned char) *s ) )
6701 return 0; // Nothing but white space? Not okay.
6703 // Check each character until we reach white space or
6704 // end-of-string. Letters, digits, underscores, and
6705 // dollar signs are okay. With the exception of periods
6706 // (as in schema.identifier), control characters and other
6707 // punctuation characters are not okay. Anything else
6708 // is okay -- it could for example be part of a multibyte
6709 // UTF8 character such as a letter with diacritical marks,
6710 // and those are allowed.
6712 if( isalnum( (unsigned char) *s )
6716 ; // Fine; keep going
6717 else if( ispunct( (unsigned char) *s )
6718 || iscntrl( (unsigned char) *s ) )
6721 } while( *s && ! isspace( (unsigned char) *s ) );
6723 // If we found any white space in the above loop,
6724 // the rest had better be all white space.
6726 while( isspace( (unsigned char) *s ) )
6730 return 0; // White space was embedded within non-white space
6736 @brief Determine whether to accept a character string as a comparison operator.
6737 @param op The candidate comparison operator.
6738 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6740 We don't validate the operator for real. We just make sure that it doesn't contain
6741 any semicolons or white space (with special exceptions for a few specific operators).
6742 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6743 space but it's still not a valid operator, then the database will complain.
6745 Another approach would be to compare the string against a short list of approved operators.
6746 We don't do that because we want to allow custom operators like ">100*", which at this
6747 writing would be difficult or impossible to express otherwise in a JSON query.
6749 int is_good_operator( const char* op ) {
6750 if( !op ) return 0; // Sanity check
6754 if( isspace( (unsigned char) *s ) ) {
6755 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6756 // and IS NOT DISTINCT FROM.
6757 if( !strcasecmp( op, "similar to" ) )
6759 else if( !strcasecmp( op, "is distinct from" ) )
6761 else if( !strcasecmp( op, "is not distinct from" ) )
6766 else if( ';' == *s )
6774 @name Query Frame Management
6776 The following machinery supports a stack of query frames for use by SELECT().
6778 A query frame caches information about one level of a SELECT query. When we enter
6779 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6781 The query frame stores information about the core class, and about any joined classes
6784 The main purpose is to map table aliases to classes and tables, so that a query can
6785 join to the same table more than once. A secondary goal is to reduce the number of
6786 lookups in the IDL by caching the results.
6790 #define STATIC_CLASS_INFO_COUNT 3
6792 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6795 @brief Allocate a ClassInfo as raw memory.
6796 @return Pointer to the newly allocated ClassInfo.
6798 Except for the in_use flag, which is used only by the allocation and deallocation
6799 logic, we don't initialize the ClassInfo here.
6801 static ClassInfo* allocate_class_info( void ) {
6802 // In order to reduce the number of mallocs and frees, we return a static
6803 // instance of ClassInfo, if we can find one that we're not already using.
6804 // We rely on the fact that the compiler will implicitly initialize the
6805 // static instances so that in_use == 0.
6808 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6809 if( ! static_class_info[ i ].in_use ) {
6810 static_class_info[ i ].in_use = 1;
6811 return static_class_info + i;
6815 // The static ones are all in use. Malloc one.
6817 return safe_malloc( sizeof( ClassInfo ) );
6821 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6822 @param info Pointer to the ClassInfo to be cleared.
6824 static void clear_class_info( ClassInfo* info ) {
6829 // Free any malloc'd strings
6831 if( info->alias != info->alias_store )
6832 free( info->alias );
6834 if( info->class_name != info->class_name_store )
6835 free( info->class_name );
6837 free( info->source_def );
6839 info->alias = info->class_name = info->source_def = NULL;
6844 @brief Free a ClassInfo and everything it owns.
6845 @param info Pointer to the ClassInfo to be freed.
6847 static void free_class_info( ClassInfo* info ) {
6852 clear_class_info( info );
6854 // If it's one of the static instances, just mark it as not in use
6857 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6858 if( info == static_class_info + i ) {
6859 static_class_info[ i ].in_use = 0;
6864 // Otherwise it must have been malloc'd, so free it
6870 @brief Populate an already-allocated ClassInfo.
6871 @param info Pointer to the ClassInfo to be populated.
6872 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6874 @param class Name of the class.
6875 @return Zero if successful, or 1 if not.
6877 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6878 the relevant portions of the IDL for the specified class.
6880 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6883 osrfLogError( OSRF_LOG_MARK,
6884 "%s ERROR: No ClassInfo available to populate", modulename );
6885 info->alias = info->class_name = info->source_def = NULL;
6886 info->class_def = info->fields = info->links = NULL;
6891 osrfLogError( OSRF_LOG_MARK,
6892 "%s ERROR: No class name provided for lookup", modulename );
6893 info->alias = info->class_name = info->source_def = NULL;
6894 info->class_def = info->fields = info->links = NULL;
6898 // Alias defaults to class name if not supplied
6899 if( ! alias || ! alias[ 0 ] )
6902 // Look up class info in the IDL
6903 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6905 osrfLogError( OSRF_LOG_MARK,
6906 "%s ERROR: Class %s not defined in IDL", modulename, class );
6907 info->alias = info->class_name = info->source_def = NULL;
6908 info->class_def = info->fields = info->links = NULL;
6910 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6911 osrfLogError( OSRF_LOG_MARK,
6912 "%s ERROR: Class %s is defined as virtual", modulename, class );
6913 info->alias = info->class_name = info->source_def = NULL;
6914 info->class_def = info->fields = info->links = NULL;
6918 osrfHash* links = osrfHashGet( class_def, "links" );
6920 osrfLogError( OSRF_LOG_MARK,
6921 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6922 info->alias = info->class_name = info->source_def = NULL;
6923 info->class_def = info->fields = info->links = NULL;
6927 osrfHash* fields = osrfHashGet( class_def, "fields" );
6929 osrfLogError( OSRF_LOG_MARK,
6930 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6931 info->alias = info->class_name = info->source_def = NULL;
6932 info->class_def = info->fields = info->links = NULL;
6936 char* source_def = oilsGetRelation( class_def );
6940 // We got everything we need, so populate the ClassInfo
6941 if( strlen( alias ) > ALIAS_STORE_SIZE )
6942 info->alias = strdup( alias );
6944 strcpy( info->alias_store, alias );
6945 info->alias = info->alias_store;
6948 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6949 info->class_name = strdup( class );
6951 strcpy( info->class_name_store, class );
6952 info->class_name = info->class_name_store;
6955 info->source_def = source_def;
6957 info->class_def = class_def;
6958 info->links = links;
6959 info->fields = fields;
6964 #define STATIC_FRAME_COUNT 3
6966 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6969 @brief Allocate a QueryFrame as raw memory.
6970 @return Pointer to the newly allocated QueryFrame.
6972 Except for the in_use flag, which is used only by the allocation and deallocation
6973 logic, we don't initialize the QueryFrame here.
6975 static QueryFrame* allocate_frame( void ) {
6976 // In order to reduce the number of mallocs and frees, we return a static
6977 // instance of QueryFrame, if we can find one that we're not already using.
6978 // We rely on the fact that the compiler will implicitly initialize the
6979 // static instances so that in_use == 0.
6982 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6983 if( ! static_frame[ i ].in_use ) {
6984 static_frame[ i ].in_use = 1;
6985 return static_frame + i;
6989 // The static ones are all in use. Malloc one.
6991 return safe_malloc( sizeof( QueryFrame ) );
6995 @brief Free a QueryFrame, and all the memory it owns.
6996 @param frame Pointer to the QueryFrame to be freed.
6998 static void free_query_frame( QueryFrame* frame ) {
7003 clear_class_info( &frame->core );
7005 // Free the join list
7007 ClassInfo* info = frame->join_list;
7010 free_class_info( info );
7014 frame->join_list = NULL;
7017 // If the frame is a static instance, just mark it as unused
7019 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7020 if( frame == static_frame + i ) {
7021 static_frame[ i ].in_use = 0;
7026 // Otherwise it must have been malloc'd, so free it
7032 @brief Search a given QueryFrame for a specified alias.
7033 @param frame Pointer to the QueryFrame to be searched.
7034 @param target The alias for which to search.
7035 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7037 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7038 if( ! frame || ! target ) {
7042 ClassInfo* found_class = NULL;
7044 if( !strcmp( target, frame->core.alias ) )
7045 return &(frame->core);
7047 ClassInfo* curr_class = frame->join_list;
7048 while( curr_class ) {
7049 if( strcmp( target, curr_class->alias ) )
7050 curr_class = curr_class->next;
7052 found_class = curr_class;
7062 @brief Push a new (blank) QueryFrame onto the stack.
7064 static void push_query_frame( void ) {
7065 QueryFrame* frame = allocate_frame();
7066 frame->join_list = NULL;
7067 frame->next = curr_query;
7069 // Initialize the ClassInfo for the core class
7070 ClassInfo* core = &frame->core;
7071 core->alias = core->class_name = core->source_def = NULL;
7072 core->class_def = core->fields = core->links = NULL;
7078 @brief Pop a QueryFrame off the stack and destroy it.
7080 static void pop_query_frame( void ) {
7085 QueryFrame* popped = curr_query;
7086 curr_query = popped->next;
7088 free_query_frame( popped );
7092 @brief Populate the ClassInfo for the core class.
7093 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7094 class name as an alias.
7095 @param class_name Name of the core class.
7096 @return Zero if successful, or 1 if not.
7098 Populate the ClassInfo of the core class with copies of the alias and class name, and
7099 with pointers to the relevant portions of the IDL for the core class.
7101 static int add_query_core( const char* alias, const char* class_name ) {
7104 if( ! curr_query ) {
7105 osrfLogError( OSRF_LOG_MARK,
7106 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7108 } else if( curr_query->core.alias ) {
7109 osrfLogError( OSRF_LOG_MARK,
7110 "%s ERROR: Core class %s already populated as %s",
7111 modulename, curr_query->core.class_name, curr_query->core.alias );
7115 build_class_info( &curr_query->core, alias, class_name );
7116 if( curr_query->core.alias )
7119 osrfLogError( OSRF_LOG_MARK,
7120 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7126 @brief Search the current QueryFrame for a specified alias.
7127 @param target The alias for which to search.
7128 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7130 static inline ClassInfo* search_alias( const char* target ) {
7131 return search_alias_in_frame( curr_query, target );
7135 @brief Search all levels of query for a specified alias, starting with the current query.
7136 @param target The alias for which to search.
7137 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7139 static ClassInfo* search_all_alias( const char* target ) {
7140 ClassInfo* found_class = NULL;
7141 QueryFrame* curr_frame = curr_query;
7143 while( curr_frame ) {
7144 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7147 curr_frame = curr_frame->next;
7154 @brief Add a class to the list of classes joined to the current query.
7155 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7156 the class name as an alias.
7157 @param classname The name of the class to be added.
7158 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7160 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7162 if( ! classname || ! *classname ) { // sanity check
7163 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7170 const ClassInfo* conflict = search_alias( alias );
7172 osrfLogError( OSRF_LOG_MARK,
7173 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7174 modulename, alias, conflict->class_name );
7178 ClassInfo* info = allocate_class_info();
7180 if( build_class_info( info, alias, classname ) ) {
7181 free_class_info( info );
7185 // Add the new ClassInfo to the join list of the current QueryFrame
7186 info->next = curr_query->join_list;
7187 curr_query->join_list = info;
7193 @brief Destroy all nodes on the query stack.
7195 static void clear_query_stack( void ) {
7201 @brief Implement the set_audit_info method.
7202 @param ctx Pointer to the method context.
7203 @return Zero if successful, or -1 if not.
7205 Issue a SAVEPOINT to the database server.
7210 - workstation id (int)
7212 If user id is not provided the authkey will be used.
7213 For PCRUD the authkey is always used, even if a user is provided.
7215 int setAuditInfo( osrfMethodContext* ctx ) {
7216 if(osrfMethodVerifyContext( ctx )) {
7217 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
7221 // Get the user id from the parameters
7222 const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7224 if( enforce_pcrud || !user_id ) {
7225 timeout_needs_resetting = 1;
7226 const jsonObject* user = verifyUserPCRUD( ctx );
7229 osrfAppRespondComplete( ctx, NULL );
7233 // Not PCRUD and have a user_id?
7234 int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7235 osrfAppRespondComplete( ctx, NULL );
7240 @brief Save a audit info
7241 @param ctx Pointer to the method context.
7242 @param user_id User ID to write as a string
7243 @param ws_id Workstation ID to write as a string
7245 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7246 if( ctx && ctx->session ) {
7247 osrfAppSession* session = ctx->session;
7249 osrfHash* cache = session->userData;
7251 // If the session doesn't already have a hash, create one. Make sure
7252 // that the application session frees the hash when it terminates.
7253 if( NULL == cache ) {
7254 session->userData = cache = osrfNewHash();
7255 osrfHashSetCallback( cache, &sessionDataFree );
7256 ctx->session->userDataFree = &userDataFree;
7259 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7261 osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7263 int errnum = dbi_conn_error( writehandle, &msg );
7266 "%s: Error setting auditor information: %d %s",
7269 msg ? msg : "(No description available)"
7271 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7272 "osrfMethodException", ctx->request, "Error setting auditor info" );
7273 if( !oilsIsDBConnected( writehandle ))
7274 osrfAppSessionPanic( ctx->session );
7277 dbi_result_free( result );
7284 @brief Remove all but safe character from savepoint name
7285 @param sp User-supplied savepoint name
7286 @return sanitized savepoint name, or NULL
7288 The caller is expected to free the returned string. Note that
7289 this function exists only because we can't use PQescapeLiteral
7290 without either forking libdbi or abandoning it.
7292 static char* _sanitize_savepoint_name( const char* sp ) {
7294 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_";
7296 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7297 // and the default value of NAMEDATALEN is 64; that should be long enough
7298 // for our purposes, and it's unlikely that anyone is going to recompile
7299 // PostgreSQL to have a smaller value, so cap the identifier name
7300 // accordingly to avoid the remote chance that someone manages to pass in a
7301 // 12GB savepoint name
7302 const int MAX_LITERAL_NAMELEN = 63;
7305 if (len > MAX_LITERAL_NAMELEN) {
7306 len = MAX_LITERAL_NAMELEN;
7309 char* safeSpName = safe_malloc( len + 1 );
7313 for (j = 0; j < len; j++) {
7314 found = strchr(safe_chars, sp[j]);
7316 safeSpName[ i++ ] = found[0];
7319 safeSpName[ i ] = '\0';