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 );
2472 // If the value is a number and the DB field is numeric, no quotes needed
2473 if( value->type == JSON_NUMBER && !strcmp( get_primitive( field ), "number") ) {
2474 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2476 // Presumably this was really intended to be a string, so quote it
2477 char* str = jsonObjectToSimpleString( value );
2478 if( dbi_conn_quote_string( dbhandle, &str )) {
2479 OSRF_BUFFER_ADD( val_buf, str );
2482 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2484 buffer_free( val_buf );
2489 return buffer_release( val_buf );
2492 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2493 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2494 growing_buffer* sql_buf = buffer_init( 32 );
2500 osrfHashGet( field, "name" )
2504 buffer_add( sql_buf, "IN (" );
2505 } else if( !strcasecmp( op,"not in" )) {
2506 buffer_add( sql_buf, "NOT IN (" );
2508 buffer_add( sql_buf, "IN (" );
2511 if( node->type == JSON_HASH ) {
2512 // subquery predicate
2513 char* subpred = buildQuery( ctx, node, SUBSELECT );
2515 buffer_free( sql_buf );
2519 buffer_add( sql_buf, subpred );
2522 } else if( node->type == JSON_ARRAY ) {
2523 // literal value list
2524 int in_item_index = 0;
2525 int in_item_first = 1;
2526 const jsonObject* in_item;
2527 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2532 buffer_add( sql_buf, ", " );
2535 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2536 osrfLogError( OSRF_LOG_MARK,
2537 "%s: Expected string or number within IN list; found %s",
2538 modulename, json_type( in_item->type ) );
2539 buffer_free( sql_buf );
2543 // Append the literal value -- quoted if not a number
2544 if( JSON_NUMBER == in_item->type ) {
2545 char* val = jsonNumberToDBString( field, in_item );
2546 OSRF_BUFFER_ADD( sql_buf, val );
2549 } else if( !strcmp( get_primitive( field ), "number" )) {
2550 char* val = jsonNumberToDBString( field, in_item );
2551 OSRF_BUFFER_ADD( sql_buf, val );
2555 char* key_string = jsonObjectToSimpleString( in_item );
2556 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2557 OSRF_BUFFER_ADD( sql_buf, key_string );
2560 osrfLogError( OSRF_LOG_MARK,
2561 "%s: Error quoting key string [%s]", modulename, key_string );
2563 buffer_free( sql_buf );
2569 if( in_item_first ) {
2570 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2571 buffer_free( sql_buf );
2575 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2576 modulename, json_type( node->type ));
2577 buffer_free( sql_buf );
2581 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2583 return buffer_release( sql_buf );
2586 // Receive a JSON_ARRAY representing a function call. The first
2587 // entry in the array is the function name. The rest are parameters.
2588 static char* searchValueTransform( const jsonObject* array ) {
2590 if( array->size < 1 ) {
2591 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2595 // Get the function name
2596 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2597 if( func_item->type != JSON_STRING ) {
2598 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2599 modulename, json_type( func_item->type ));
2603 growing_buffer* sql_buf = buffer_init( 32 );
2605 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2606 OSRF_BUFFER_ADD( sql_buf, "( " );
2608 // Get the parameters
2609 int func_item_index = 1; // We already grabbed the zeroth entry
2610 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2612 // Add a separator comma, if we need one
2613 if( func_item_index > 2 )
2614 buffer_add( sql_buf, ", " );
2616 // Add the current parameter
2617 if( func_item->type == JSON_NULL ) {
2618 buffer_add( sql_buf, "NULL" );
2620 if( func_item->type == JSON_BOOL ) {
2621 if( jsonBoolIsTrue(func_item) ) {
2622 buffer_add( sql_buf, "TRUE" );
2624 buffer_add( sql_buf, "FALSE" );
2627 char* val = jsonObjectToSimpleString( func_item );
2628 if( dbi_conn_quote_string( dbhandle, &val )) {
2629 OSRF_BUFFER_ADD( sql_buf, val );
2632 osrfLogError( OSRF_LOG_MARK,
2633 "%s: Error quoting key string [%s]", modulename, val );
2634 buffer_free( sql_buf );
2642 buffer_add( sql_buf, " )" );
2644 return buffer_release( sql_buf );
2647 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2648 const jsonObject* node, const char* op ) {
2650 if( ! is_good_operator( op ) ) {
2651 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2655 char* val = searchValueTransform( node );
2659 growing_buffer* sql_buf = buffer_init( 32 );
2664 osrfHashGet( field, "name" ),
2671 return buffer_release( sql_buf );
2674 // class_alias is a class name or other table alias
2675 // field is a field definition as stored in the IDL
2676 // node comes from the method parameter, and may represent an entry in the SELECT list
2677 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2678 const jsonObject* node ) {
2679 growing_buffer* sql_buf = buffer_init( 32 );
2681 const char* field_transform = jsonObjectGetString(
2682 jsonObjectGetKeyConst( node, "transform" ) );
2683 const char* transform_subcolumn = jsonObjectGetString(
2684 jsonObjectGetKeyConst( node, "result_field" ) );
2686 if( transform_subcolumn ) {
2687 if( ! is_identifier( transform_subcolumn ) ) {
2688 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2689 modulename, transform_subcolumn );
2690 buffer_free( sql_buf );
2693 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2696 if( field_transform ) {
2698 if( ! is_identifier( field_transform ) ) {
2699 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2700 modulename, field_transform );
2701 buffer_free( sql_buf );
2705 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2706 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2707 field_transform, class_alias, osrfHashGet( field, "name" ));
2709 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2710 field_transform, class_alias, osrfHashGet( field, "name" ));
2713 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2716 if( array->type != JSON_ARRAY ) {
2717 osrfLogError( OSRF_LOG_MARK,
2718 "%s: Expected JSON_ARRAY for function params; found %s",
2719 modulename, json_type( array->type ) );
2720 buffer_free( sql_buf );
2723 int func_item_index = 0;
2724 jsonObject* func_item;
2725 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2727 char* val = jsonObjectToSimpleString( func_item );
2730 buffer_add( sql_buf, ",NULL" );
2731 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2732 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2733 OSRF_BUFFER_ADD( sql_buf, val );
2735 osrfLogError( OSRF_LOG_MARK,
2736 "%s: Error quoting key string [%s]", modulename, val );
2738 buffer_free( sql_buf );
2745 buffer_add( sql_buf, " )" );
2748 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2751 if( transform_subcolumn )
2752 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2754 return buffer_release( sql_buf );
2757 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2758 const jsonObject* node, const char* op ) {
2760 if( ! is_good_operator( op ) ) {
2761 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2765 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2766 if( ! field_transform )
2769 int extra_parens = 0; // boolean
2771 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2773 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2775 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2777 free( field_transform );
2781 } else if( value_obj->type == JSON_ARRAY ) {
2782 value = searchValueTransform( value_obj );
2784 osrfLogError( OSRF_LOG_MARK,
2785 "%s: Error building value transform for field transform", modulename );
2786 free( field_transform );
2789 } else if( value_obj->type == JSON_HASH ) {
2790 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2792 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2794 free( field_transform );
2798 } else if( value_obj->type == JSON_NUMBER ) {
2799 value = jsonNumberToDBString( field, value_obj );
2800 } else if( value_obj->type == JSON_NULL ) {
2801 osrfLogError( OSRF_LOG_MARK,
2802 "%s: Error building predicate for field transform: null value", modulename );
2803 free( field_transform );
2805 } else if( value_obj->type == JSON_BOOL ) {
2806 osrfLogError( OSRF_LOG_MARK,
2807 "%s: Error building predicate for field transform: boolean value", modulename );
2808 free( field_transform );
2811 if( !strcmp( get_primitive( field ), "number") ) {
2812 value = jsonNumberToDBString( field, value_obj );
2814 value = jsonObjectToSimpleString( value_obj );
2815 if( !dbi_conn_quote_string( dbhandle, &value )) {
2816 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2817 modulename, value );
2819 free( field_transform );
2825 const char* left_parens = "";
2826 const char* right_parens = "";
2828 if( extra_parens ) {
2833 const char* right_percent = "";
2834 const char* real_op = op;
2836 if( !strcasecmp( op, "startwith") ) {
2838 right_percent = "|| '%'";
2841 growing_buffer* sql_buf = buffer_init( 32 );
2845 "%s%s %s %s %s%s %s%s",
2857 free( field_transform );
2859 return buffer_release( sql_buf );
2862 static char* searchSimplePredicate( const char* op, const char* class_alias,
2863 osrfHash* field, const jsonObject* node ) {
2865 if( ! is_good_operator( op ) ) {
2866 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2872 // Get the value to which we are comparing the specified column
2873 if( node->type != JSON_NULL ) {
2874 if( node->type == JSON_NUMBER ) {
2875 val = jsonNumberToDBString( field, node );
2876 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2877 val = jsonNumberToDBString( field, node );
2879 val = jsonObjectToSimpleString( node );
2884 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2885 // Value is not numeric; enclose it in quotes
2886 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2887 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2894 // Compare to a null value
2895 val = strdup( "NULL" );
2896 if( strcmp( op, "=" ))
2902 growing_buffer* sql_buf = buffer_init( 32 );
2903 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2904 char* pred = buffer_release( sql_buf );
2911 static char* searchBETWEENPredicate( const char* class_alias,
2912 osrfHash* field, const jsonObject* node ) {
2914 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2915 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2917 if( NULL == y_node ) {
2918 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2921 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2922 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2929 if( !strcmp( get_primitive( field ), "number") ) {
2930 x_string = jsonNumberToDBString( field, x_node );
2931 y_string = jsonNumberToDBString( field, y_node );
2934 x_string = jsonObjectToSimpleString( x_node );
2935 y_string = jsonObjectToSimpleString( y_node );
2936 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2937 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2938 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2939 modulename, x_string, y_string );
2946 growing_buffer* sql_buf = buffer_init( 32 );
2947 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2948 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2952 return buffer_release( sql_buf );
2955 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2956 jsonObject* node, osrfMethodContext* ctx ) {
2959 if( node->type == JSON_ARRAY ) { // equality IN search
2960 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2961 } else if( node->type == JSON_HASH ) { // other search
2962 jsonIterator* pred_itr = jsonNewIterator( node );
2963 if( !jsonIteratorHasNext( pred_itr ) ) {
2964 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2965 modulename, osrfHashGet(field, "name" ));
2967 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2969 // Verify that there are no additional predicates
2970 if( jsonIteratorHasNext( pred_itr ) ) {
2971 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2972 modulename, osrfHashGet(field, "name" ));
2973 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2974 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2975 else if( !(strcasecmp( pred_itr->key,"in" ))
2976 || !(strcasecmp( pred_itr->key,"not in" )) )
2977 pred = searchINPredicate(
2978 class_info->alias, field, pred_node, pred_itr->key, ctx );
2979 else if( pred_node->type == JSON_ARRAY )
2980 pred = searchFunctionPredicate(
2981 class_info->alias, field, pred_node, pred_itr->key );
2982 else if( pred_node->type == JSON_HASH )
2983 pred = searchFieldTransformPredicate(
2984 class_info, field, pred_node, pred_itr->key );
2986 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2988 jsonIteratorFree( pred_itr );
2990 } else if( node->type == JSON_NULL ) { // IS NULL search
2991 growing_buffer* _p = buffer_init( 64 );
2994 "\"%s\".%s IS NULL",
2996 osrfHashGet( field, "name" )
2998 pred = buffer_release( _p );
2999 } else { // equality search
3000 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3019 field : call_number,
3035 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3037 const jsonObject* working_hash;
3038 jsonObject* freeable_hash = NULL;
3040 if( join_hash->type == JSON_HASH ) {
3041 working_hash = join_hash;
3042 } else if( join_hash->type == JSON_STRING ) {
3043 // turn it into a JSON_HASH by creating a wrapper
3044 // around a copy of the original
3045 const char* _tmp = jsonObjectGetString( join_hash );
3046 freeable_hash = jsonNewObjectType( JSON_HASH );
3047 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3048 working_hash = freeable_hash;
3052 "%s: JOIN failed; expected JSON object type not found",
3058 growing_buffer* join_buf = buffer_init( 128 );
3059 const char* leftclass = left_info->class_name;
3061 jsonObject* snode = NULL;
3062 jsonIterator* search_itr = jsonNewIterator( working_hash );
3064 while ( (snode = jsonIteratorNext( search_itr )) ) {
3065 const char* right_alias = search_itr->key;
3067 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3069 class = right_alias;
3071 const ClassInfo* right_info = add_joined_class( right_alias, class );
3075 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3079 jsonIteratorFree( search_itr );
3080 buffer_free( join_buf );
3082 jsonObjectFree( freeable_hash );
3085 osrfHash* links = right_info->links;
3086 const char* table = right_info->source_def;
3088 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3089 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3091 if( field && !fkey ) {
3092 // Look up the corresponding join column in the IDL.
3093 // The link must be defined in the child table,
3094 // and point to the right parent table.
3095 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3096 const char* reltype = NULL;
3097 const char* other_class = NULL;
3098 reltype = osrfHashGet( idl_link, "reltype" );
3099 if( reltype && strcmp( reltype, "has_many" ) )
3100 other_class = osrfHashGet( idl_link, "class" );
3101 if( other_class && !strcmp( other_class, leftclass ) )
3102 fkey = osrfHashGet( idl_link, "key" );
3106 "%s: JOIN failed. No link defined from %s.%s to %s",
3112 buffer_free( join_buf );
3114 jsonObjectFree( freeable_hash );
3115 jsonIteratorFree( search_itr );
3119 } else if( !field && fkey ) {
3120 // Look up the corresponding join column in the IDL.
3121 // The link must be defined in the child table,
3122 // and point to the right parent table.
3123 osrfHash* left_links = left_info->links;
3124 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3125 const char* reltype = NULL;
3126 const char* other_class = NULL;
3127 reltype = osrfHashGet( idl_link, "reltype" );
3128 if( reltype && strcmp( reltype, "has_many" ) )
3129 other_class = osrfHashGet( idl_link, "class" );
3130 if( other_class && !strcmp( other_class, class ) )
3131 field = osrfHashGet( idl_link, "key" );
3135 "%s: JOIN failed. No link defined from %s.%s to %s",
3141 buffer_free( join_buf );
3143 jsonObjectFree( freeable_hash );
3144 jsonIteratorFree( search_itr );
3148 } else if( !field && !fkey ) {
3149 osrfHash* left_links = left_info->links;
3151 // For each link defined for the left class:
3152 // see if the link references the joined class
3153 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3154 osrfHash* curr_link = NULL;
3155 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3156 const char* other_class = osrfHashGet( curr_link, "class" );
3157 if( other_class && !strcmp( other_class, class ) ) {
3159 // In the IDL, the parent class doesn't always know then names of the child
3160 // columns that are pointing to it, so don't use that end of the link
3161 const char* reltype = osrfHashGet( curr_link, "reltype" );
3162 if( reltype && strcmp( reltype, "has_many" ) ) {
3163 // Found a link between the classes
3164 fkey = osrfHashIteratorKey( itr );
3165 field = osrfHashGet( curr_link, "key" );
3170 osrfHashIteratorFree( itr );
3172 if( !field || !fkey ) {
3173 // Do another such search, with the classes reversed
3175 // For each link defined for the joined class:
3176 // see if the link references the left class
3177 osrfHashIterator* itr = osrfNewHashIterator( links );
3178 osrfHash* curr_link = NULL;
3179 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3180 const char* other_class = osrfHashGet( curr_link, "class" );
3181 if( other_class && !strcmp( other_class, leftclass ) ) {
3183 // In the IDL, the parent class doesn't know then names of the child
3184 // columns that are pointing to it, so don't use that end of the link
3185 const char* reltype = osrfHashGet( curr_link, "reltype" );
3186 if( reltype && strcmp( reltype, "has_many" ) ) {
3187 // Found a link between the classes
3188 field = osrfHashIteratorKey( itr );
3189 fkey = osrfHashGet( curr_link, "key" );
3194 osrfHashIteratorFree( itr );
3197 if( !field || !fkey ) {
3200 "%s: JOIN failed. No link defined between %s and %s",
3205 buffer_free( join_buf );
3207 jsonObjectFree( freeable_hash );
3208 jsonIteratorFree( search_itr );
3213 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3215 if( !strcasecmp( type,"left" )) {
3216 buffer_add( join_buf, " LEFT JOIN" );
3217 } else if( !strcasecmp( type,"right" )) {
3218 buffer_add( join_buf, " RIGHT JOIN" );
3219 } else if( !strcasecmp( type,"full" )) {
3220 buffer_add( join_buf, " FULL JOIN" );
3222 buffer_add( join_buf, " INNER JOIN" );
3225 buffer_add( join_buf, " INNER JOIN" );
3228 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3229 table, right_alias, right_alias, field, left_info->alias, fkey );
3231 // Add any other join conditions as specified by "filter"
3232 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3234 const char* filter_op = jsonObjectGetString(
3235 jsonObjectGetKeyConst( snode, "filter_op" ) );
3236 if( filter_op && !strcasecmp( "or",filter_op )) {
3237 buffer_add( join_buf, " OR " );
3239 buffer_add( join_buf, " AND " );
3242 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3244 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3245 OSRF_BUFFER_ADD( join_buf, jpred );
3250 "%s: JOIN failed. Invalid conditional expression.",
3253 jsonIteratorFree( search_itr );
3254 buffer_free( join_buf );
3256 jsonObjectFree( freeable_hash );
3261 buffer_add( join_buf, " ) " );
3263 // Recursively add a nested join, if one is present
3264 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3266 char* jpred = searchJOIN( join_filter, right_info );
3268 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3269 OSRF_BUFFER_ADD( join_buf, jpred );
3272 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3273 jsonIteratorFree( search_itr );
3274 buffer_free( join_buf );
3276 jsonObjectFree( freeable_hash );
3283 jsonObjectFree( freeable_hash );
3284 jsonIteratorFree( search_itr );
3286 return buffer_release( join_buf );
3291 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3292 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3293 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3295 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3297 search_hash is the JSON expression of the conditions.
3298 meta is the class definition from the IDL, for the relevant table.
3299 opjoin_type indicates whether multiple conditions, if present, should be
3300 connected by AND or OR.
3301 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3302 to pass it to other functions -- and all they do with it is to use the session
3303 and request members to send error messages back to the client.
3307 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3308 int opjoin_type, osrfMethodContext* ctx ) {
3312 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3313 "opjoin_type = %d, ctx addr = %p",
3316 class_info->class_def,
3321 growing_buffer* sql_buf = buffer_init( 128 );
3323 jsonObject* node = NULL;
3326 if( search_hash->type == JSON_ARRAY ) {
3327 if( 0 == search_hash->size ) {
3330 "%s: Invalid predicate structure: empty JSON array",
3333 buffer_free( sql_buf );
3337 unsigned long i = 0;
3338 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3342 if( opjoin_type == OR_OP_JOIN )
3343 buffer_add( sql_buf, " OR " );
3345 buffer_add( sql_buf, " AND " );
3348 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3350 buffer_free( sql_buf );
3354 buffer_fadd( sql_buf, "( %s )", subpred );
3358 } else if( search_hash->type == JSON_HASH ) {
3359 osrfLogDebug( OSRF_LOG_MARK,
3360 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3361 jsonIterator* search_itr = jsonNewIterator( search_hash );
3362 if( !jsonIteratorHasNext( search_itr ) ) {
3365 "%s: Invalid predicate structure: empty JSON object",
3368 jsonIteratorFree( search_itr );
3369 buffer_free( sql_buf );
3373 while( (node = jsonIteratorNext( search_itr )) ) {
3378 if( opjoin_type == OR_OP_JOIN )
3379 buffer_add( sql_buf, " OR " );
3381 buffer_add( sql_buf, " AND " );
3384 if( '+' == search_itr->key[ 0 ] ) {
3386 // This plus sign prefixes a class name or other table alias;
3387 // make sure the table alias is in scope
3388 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3389 if( ! alias_info ) {
3392 "%s: Invalid table alias \"%s\" in WHERE clause",
3396 jsonIteratorFree( search_itr );
3397 buffer_free( sql_buf );
3401 if( node->type == JSON_STRING ) {
3402 // It's the name of a column; make sure it belongs to the class
3403 const char* fieldname = jsonObjectGetString( node );
3404 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3407 "%s: Invalid column name \"%s\" in WHERE clause "
3408 "for table alias \"%s\"",
3413 jsonIteratorFree( search_itr );
3414 buffer_free( sql_buf );
3418 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3420 // It's something more complicated
3421 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3423 jsonIteratorFree( search_itr );
3424 buffer_free( sql_buf );
3428 buffer_fadd( sql_buf, "( %s )", subpred );
3431 } else if( '-' == search_itr->key[ 0 ] ) {
3432 if( !strcasecmp( "-or", search_itr->key )) {
3433 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3435 jsonIteratorFree( search_itr );
3436 buffer_free( sql_buf );
3440 buffer_fadd( sql_buf, "( %s )", subpred );
3442 } else if( !strcasecmp( "-and", search_itr->key )) {
3443 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3445 jsonIteratorFree( search_itr );
3446 buffer_free( sql_buf );
3450 buffer_fadd( sql_buf, "( %s )", subpred );
3452 } else if( !strcasecmp("-not",search_itr->key) ) {
3453 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3455 jsonIteratorFree( search_itr );
3456 buffer_free( sql_buf );
3460 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3462 } else if( !strcasecmp( "-exists", search_itr->key )) {
3463 char* subpred = buildQuery( ctx, node, SUBSELECT );
3465 jsonIteratorFree( search_itr );
3466 buffer_free( sql_buf );
3470 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3472 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3473 char* subpred = buildQuery( ctx, node, SUBSELECT );
3475 jsonIteratorFree( search_itr );
3476 buffer_free( sql_buf );
3480 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3482 } else { // Invalid "minus" operator
3485 "%s: Invalid operator \"%s\" in WHERE clause",
3489 jsonIteratorFree( search_itr );
3490 buffer_free( sql_buf );
3496 const char* class = class_info->class_name;
3497 osrfHash* fields = class_info->fields;
3498 osrfHash* field = osrfHashGet( fields, search_itr->key );
3501 const char* table = class_info->source_def;
3504 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3507 table ? table : "?",
3510 jsonIteratorFree( search_itr );
3511 buffer_free( sql_buf );
3515 char* subpred = searchPredicate( class_info, field, node, ctx );
3517 buffer_free( sql_buf );
3518 jsonIteratorFree( search_itr );
3522 buffer_add( sql_buf, subpred );
3526 jsonIteratorFree( search_itr );
3529 // ERROR ... only hash and array allowed at this level
3530 char* predicate_string = jsonObjectToJSON( search_hash );
3533 "%s: Invalid predicate structure: %s",
3537 buffer_free( sql_buf );
3538 free( predicate_string );
3542 return buffer_release( sql_buf );
3545 /* Build a JSON_ARRAY of field names for a given table alias
3547 static jsonObject* defaultSelectList( const char* table_alias ) {
3552 ClassInfo* class_info = search_all_alias( table_alias );
3553 if( ! class_info ) {
3556 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3563 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3564 osrfHash* field_def = NULL;
3565 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3566 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3567 const char* field_name = osrfHashIteratorKey( field_itr );
3568 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3569 jsonObjectPush( array, jsonNewObject( field_name ) );
3572 osrfHashIteratorFree( field_itr );
3577 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3578 // The jsonObject must be a JSON_HASH with an single entry for "union",
3579 // "intersect", or "except". The data associated with this key must be an
3580 // array of hashes, each hash being a query.
3581 // Also allowed but currently ignored: entries for "order_by" and "alias".
3582 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3584 if( ! combo || combo->type != JSON_HASH )
3585 return NULL; // should be impossible; validated by caller
3587 const jsonObject* query_array = NULL; // array of subordinate queries
3588 const char* op = NULL; // name of operator, e.g. UNION
3589 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3590 int op_count = 0; // for detecting conflicting operators
3591 int excepting = 0; // boolean
3592 int all = 0; // boolean
3593 jsonObject* order_obj = NULL;
3595 // Identify the elements in the hash
3596 jsonIterator* query_itr = jsonNewIterator( combo );
3597 jsonObject* curr_obj = NULL;
3598 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3599 if( ! strcmp( "union", query_itr->key ) ) {
3602 query_array = curr_obj;
3603 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3606 query_array = curr_obj;
3607 } else if( ! strcmp( "except", query_itr->key ) ) {
3611 query_array = curr_obj;
3612 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3615 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3618 order_obj = curr_obj;
3619 } else if( ! strcmp( "alias", query_itr->key ) ) {
3620 if( curr_obj->type != JSON_STRING ) {
3621 jsonIteratorFree( query_itr );
3624 alias = jsonObjectGetString( curr_obj );
3625 } else if( ! strcmp( "all", query_itr->key ) ) {
3626 if( obj_is_true( curr_obj ) )
3630 osrfAppSessionStatus(
3632 OSRF_STATUS_INTERNALSERVERERROR,
3633 "osrfMethodException",
3635 "Malformed query; unexpected entry in query object"
3639 "%s: Unexpected entry for \"%s\" in%squery",
3644 jsonIteratorFree( query_itr );
3648 jsonIteratorFree( query_itr );
3650 // More sanity checks
3651 if( ! query_array ) {
3653 osrfAppSessionStatus(
3655 OSRF_STATUS_INTERNALSERVERERROR,
3656 "osrfMethodException",
3658 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3662 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3665 return NULL; // should be impossible...
3666 } else if( op_count > 1 ) {
3668 osrfAppSessionStatus(
3670 OSRF_STATUS_INTERNALSERVERERROR,
3671 "osrfMethodException",
3673 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3677 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3681 } if( query_array->type != JSON_ARRAY ) {
3683 osrfAppSessionStatus(
3685 OSRF_STATUS_INTERNALSERVERERROR,
3686 "osrfMethodException",
3688 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3692 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3695 json_type( query_array->type )
3698 } if( query_array->size < 2 ) {
3700 osrfAppSessionStatus(
3702 OSRF_STATUS_INTERNALSERVERERROR,
3703 "osrfMethodException",
3705 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3709 "%s:%srequires multiple queries as operands",
3714 } else if( excepting && query_array->size > 2 ) {
3716 osrfAppSessionStatus(
3718 OSRF_STATUS_INTERNALSERVERERROR,
3719 "osrfMethodException",
3721 "EXCEPT operator has too many queries as operands"
3725 "%s:EXCEPT operator has too many queries as operands",
3729 } else if( order_obj && ! alias ) {
3731 osrfAppSessionStatus(
3733 OSRF_STATUS_INTERNALSERVERERROR,
3734 "osrfMethodException",
3736 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3740 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3746 // So far so good. Now build the SQL.
3747 growing_buffer* sql = buffer_init( 256 );
3749 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3750 // Add a layer of parentheses
3751 if( flags & SUBCOMBO )
3752 OSRF_BUFFER_ADD( sql, "( " );
3754 // Traverse the query array. Each entry should be a hash.
3755 int first = 1; // boolean
3757 jsonObject* query = NULL;
3758 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3759 if( query->type != JSON_HASH ) {
3761 osrfAppSessionStatus(
3763 OSRF_STATUS_INTERNALSERVERERROR,
3764 "osrfMethodException",
3766 "Malformed query under UNION, INTERSECT or EXCEPT"
3770 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3773 json_type( query->type )
3782 OSRF_BUFFER_ADD( sql, op );
3784 OSRF_BUFFER_ADD( sql, "ALL " );
3787 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3791 "%s: Error building query under%s",
3799 OSRF_BUFFER_ADD( sql, query_str );
3802 if( flags & SUBCOMBO )
3803 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3805 if( !(flags & SUBSELECT) )
3806 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3808 return buffer_release( sql );
3811 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3812 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3813 // or "except" to indicate the type of query.
3814 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3818 osrfAppSessionStatus(
3820 OSRF_STATUS_INTERNALSERVERERROR,
3821 "osrfMethodException",
3823 "Malformed query; no query object"
3825 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3827 } else if( query->type != JSON_HASH ) {
3829 osrfAppSessionStatus(
3831 OSRF_STATUS_INTERNALSERVERERROR,
3832 "osrfMethodException",
3834 "Malformed query object"
3838 "%s: Query object is %s instead of JSON_HASH",
3840 json_type( query->type )
3845 // Determine what kind of query it purports to be, and dispatch accordingly.
3846 if( jsonObjectGetKeyConst( query, "union" ) ||
3847 jsonObjectGetKeyConst( query, "intersect" ) ||
3848 jsonObjectGetKeyConst( query, "except" )) {
3849 return doCombo( ctx, query, flags );
3851 // It is presumably a SELECT query
3853 // Push a node onto the stack for the current query. Every level of
3854 // subquery gets its own QueryFrame on the Stack.
3857 // Build an SQL SELECT statement
3860 jsonObjectGetKey( query, "select" ),
3861 jsonObjectGetKeyConst( query, "from" ),
3862 jsonObjectGetKeyConst( query, "where" ),
3863 jsonObjectGetKeyConst( query, "having" ),
3864 jsonObjectGetKeyConst( query, "order_by" ),
3865 jsonObjectGetKeyConst( query, "limit" ),
3866 jsonObjectGetKeyConst( query, "offset" ),
3875 /* method context */ osrfMethodContext* ctx,
3877 /* SELECT */ jsonObject* selhash,
3878 /* FROM */ const jsonObject* join_hash,
3879 /* WHERE */ const jsonObject* search_hash,
3880 /* HAVING */ const jsonObject* having_hash,
3881 /* ORDER BY */ const jsonObject* order_hash,
3882 /* LIMIT */ const jsonObject* limit,
3883 /* OFFSET */ const jsonObject* offset,
3884 /* flags */ int flags
3886 const char* locale = osrf_message_get_last_locale();
3888 // general tmp objects
3889 const jsonObject* tmp_const;
3890 jsonObject* selclass = NULL;
3891 jsonObject* snode = NULL;
3892 jsonObject* onode = NULL;
3894 char* string = NULL;
3895 int from_function = 0;
3900 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3902 // punt if there's no FROM clause
3903 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3906 "%s: FROM clause is missing or empty",
3910 osrfAppSessionStatus(
3912 OSRF_STATUS_INTERNALSERVERERROR,
3913 "osrfMethodException",
3915 "FROM clause is missing or empty in JSON query"
3920 // the core search class
3921 const char* core_class = NULL;
3923 // get the core class -- the only key of the top level FROM clause, or a string
3924 if( join_hash->type == JSON_HASH ) {
3925 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3926 snode = jsonIteratorNext( tmp_itr );
3928 // Populate the current QueryFrame with information
3929 // about the core class
3930 if( add_query_core( NULL, tmp_itr->key ) ) {
3932 osrfAppSessionStatus(
3934 OSRF_STATUS_INTERNALSERVERERROR,
3935 "osrfMethodException",
3937 "Unable to look up core class"
3941 core_class = curr_query->core.class_name;
3944 jsonObject* extra = jsonIteratorNext( tmp_itr );
3946 jsonIteratorFree( tmp_itr );
3949 // There shouldn't be more than one entry in join_hash
3953 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3957 osrfAppSessionStatus(
3959 OSRF_STATUS_INTERNALSERVERERROR,
3960 "osrfMethodException",
3962 "Malformed FROM clause in JSON query"
3964 return NULL; // Malformed join_hash; extra entry
3966 } else if( join_hash->type == JSON_ARRAY ) {
3967 // We're selecting from a function, not from a table
3969 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3972 } else if( join_hash->type == JSON_STRING ) {
3973 // Populate the current QueryFrame with information
3974 // about the core class
3975 core_class = jsonObjectGetString( join_hash );
3977 if( add_query_core( NULL, core_class ) ) {
3979 osrfAppSessionStatus(
3981 OSRF_STATUS_INTERNALSERVERERROR,
3982 "osrfMethodException",
3984 "Unable to look up core class"
3992 "%s: FROM clause is unexpected JSON type: %s",
3994 json_type( join_hash->type )
3997 osrfAppSessionStatus(
3999 OSRF_STATUS_INTERNALSERVERERROR,
4000 "osrfMethodException",
4002 "Ill-formed FROM clause in JSON query"
4007 // Build the join clause, if any, while filling out the list
4008 // of joined classes in the current QueryFrame.
4009 char* join_clause = NULL;
4010 if( join_hash && ! from_function ) {
4012 join_clause = searchJOIN( join_hash, &curr_query->core );
4013 if( ! join_clause ) {
4015 osrfAppSessionStatus(
4017 OSRF_STATUS_INTERNALSERVERERROR,
4018 "osrfMethodException",
4020 "Unable to construct JOIN clause(s)"
4026 // For in case we don't get a select list
4027 jsonObject* defaultselhash = NULL;
4029 // if there is no select list, build a default select list ...
4030 if( !selhash && !from_function ) {
4031 jsonObject* default_list = defaultSelectList( core_class );
4032 if( ! default_list ) {
4034 osrfAppSessionStatus(
4036 OSRF_STATUS_INTERNALSERVERERROR,
4037 "osrfMethodException",
4039 "Unable to build default SELECT clause in JSON query"
4041 free( join_clause );
4046 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4047 jsonObjectSetKey( selhash, core_class, default_list );
4050 // The SELECT clause can be encoded only by a hash
4051 if( !from_function && selhash->type != JSON_HASH ) {
4054 "%s: Expected JSON_HASH for SELECT clause; found %s",
4056 json_type( selhash->type )
4060 osrfAppSessionStatus(
4062 OSRF_STATUS_INTERNALSERVERERROR,
4063 "osrfMethodException",
4065 "Malformed SELECT clause in JSON query"
4067 free( join_clause );
4071 // If you see a null or wild card specifier for the core class, or an
4072 // empty array, replace it with a default SELECT list
4073 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4075 int default_needed = 0; // boolean
4076 if( JSON_STRING == tmp_const->type
4077 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4079 else if( JSON_NULL == tmp_const->type )
4082 if( default_needed ) {
4083 // Build a default SELECT list
4084 jsonObject* default_list = defaultSelectList( core_class );
4085 if( ! default_list ) {
4087 osrfAppSessionStatus(
4089 OSRF_STATUS_INTERNALSERVERERROR,
4090 "osrfMethodException",
4092 "Can't build default SELECT clause in JSON query"
4094 free( join_clause );
4099 jsonObjectSetKey( selhash, core_class, default_list );
4103 // temp buffers for the SELECT list and GROUP BY clause
4104 growing_buffer* select_buf = buffer_init( 128 );
4105 growing_buffer* group_buf = buffer_init( 128 );
4107 int aggregate_found = 0; // boolean
4109 // Build a select list
4110 if( from_function ) // From a function we select everything
4111 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4114 // Build the SELECT list as SQL
4118 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4119 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4121 const char* cname = selclass_itr->key;
4123 // Make sure the target relation is in the FROM clause.
4125 // At this point join_hash is a step down from the join_hash we
4126 // received as a parameter. If the original was a JSON_STRING,
4127 // then json_hash is now NULL. If the original was a JSON_HASH,
4128 // then json_hash is now the first (and only) entry in it,
4129 // denoting the core class. We've already excluded the
4130 // possibility that the original was a JSON_ARRAY, because in
4131 // that case from_function would be non-NULL, and we wouldn't
4134 // If the current table alias isn't in scope, bail out
4135 ClassInfo* class_info = search_alias( cname );
4136 if( ! class_info ) {
4139 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4144 osrfAppSessionStatus(
4146 OSRF_STATUS_INTERNALSERVERERROR,
4147 "osrfMethodException",
4149 "Selected class not in FROM clause in JSON query"
4151 jsonIteratorFree( selclass_itr );
4152 buffer_free( select_buf );
4153 buffer_free( group_buf );
4154 if( defaultselhash )
4155 jsonObjectFree( defaultselhash );
4156 free( join_clause );
4160 if( selclass->type != JSON_ARRAY ) {
4163 "%s: Malformed SELECT list for class \"%s\"; not an array",
4168 osrfAppSessionStatus(
4170 OSRF_STATUS_INTERNALSERVERERROR,
4171 "osrfMethodException",
4173 "Selected class not in FROM clause in JSON query"
4176 jsonIteratorFree( selclass_itr );
4177 buffer_free( select_buf );
4178 buffer_free( group_buf );
4179 if( defaultselhash )
4180 jsonObjectFree( defaultselhash );
4181 free( join_clause );
4185 // Look up some attributes of the current class
4186 osrfHash* idlClass = class_info->class_def;
4187 osrfHash* class_field_set = class_info->fields;
4188 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4189 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4191 if( 0 == selclass->size ) {
4194 "%s: No columns selected from \"%s\"",
4200 // stitch together the column list for the current table alias...
4201 unsigned long field_idx = 0;
4202 jsonObject* selfield = NULL;
4203 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4205 // If we need a separator comma, add one
4209 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4212 // if the field specification is a string, add it to the list
4213 if( selfield->type == JSON_STRING ) {
4215 // Look up the field in the IDL
4216 const char* col_name = jsonObjectGetString( selfield );
4217 osrfHash* field_def;
4219 if (!osrfStringArrayContains(
4221 osrfHashGet( class_field_set, col_name ),
4222 "suppress_controller"),
4225 field_def = osrfHashGet( class_field_set, col_name );
4228 // No such field in current class
4231 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4237 osrfAppSessionStatus(
4239 OSRF_STATUS_INTERNALSERVERERROR,
4240 "osrfMethodException",
4242 "Selected column not defined in JSON query"
4244 jsonIteratorFree( selclass_itr );
4245 buffer_free( select_buf );
4246 buffer_free( group_buf );
4247 if( defaultselhash )
4248 jsonObjectFree( defaultselhash );
4249 free( join_clause );
4251 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4252 // Virtual field not allowed
4255 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4261 osrfAppSessionStatus(
4263 OSRF_STATUS_INTERNALSERVERERROR,
4264 "osrfMethodException",
4266 "Selected column may not be virtual in JSON query"
4268 jsonIteratorFree( selclass_itr );
4269 buffer_free( select_buf );
4270 buffer_free( group_buf );
4271 if( defaultselhash )
4272 jsonObjectFree( defaultselhash );
4273 free( join_clause );
4279 if( flags & DISABLE_I18N )
4282 i18n = osrfHashGet( field_def, "i18n" );
4284 if( str_is_true( i18n ) ) {
4285 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4286 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4287 class_tname, cname, col_name, class_pkey,
4288 cname, class_pkey, locale, col_name );
4290 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4291 cname, col_name, col_name );
4294 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4295 cname, col_name, col_name );
4298 // ... but it could be an object, in which case we check for a Field Transform
4299 } else if( selfield->type == JSON_HASH ) {
4301 const char* col_name = jsonObjectGetString(
4302 jsonObjectGetKeyConst( selfield, "column" ) );
4304 // Get the field definition from the IDL
4305 osrfHash* field_def;
4306 if (!osrfStringArrayContains(
4308 osrfHashGet( class_field_set, col_name ),
4309 "suppress_controller"),
4312 field_def = osrfHashGet( class_field_set, col_name );
4316 // No such field in current class
4319 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4325 osrfAppSessionStatus(
4327 OSRF_STATUS_INTERNALSERVERERROR,
4328 "osrfMethodException",
4330 "Selected column is not defined in JSON query"
4332 jsonIteratorFree( selclass_itr );
4333 buffer_free( select_buf );
4334 buffer_free( group_buf );
4335 if( defaultselhash )
4336 jsonObjectFree( defaultselhash );
4337 free( join_clause );
4339 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4340 // No such field in current class
4343 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4349 osrfAppSessionStatus(
4351 OSRF_STATUS_INTERNALSERVERERROR,
4352 "osrfMethodException",
4354 "Selected column is virtual in JSON query"
4356 jsonIteratorFree( selclass_itr );
4357 buffer_free( select_buf );
4358 buffer_free( group_buf );
4359 if( defaultselhash )
4360 jsonObjectFree( defaultselhash );
4361 free( join_clause );
4365 // Decide what to use as a column alias
4367 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4368 _alias = jsonObjectGetString( tmp_const );
4369 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4370 _alias = jsonObjectGetString( tmp_const );
4371 } else { // Use field name as the alias
4375 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4376 char* transform_str = searchFieldTransform(
4377 class_info->alias, field_def, selfield );
4378 if( transform_str ) {
4379 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4380 free( transform_str );
4383 osrfAppSessionStatus(
4385 OSRF_STATUS_INTERNALSERVERERROR,
4386 "osrfMethodException",
4388 "Unable to generate transform function in JSON query"
4390 jsonIteratorFree( selclass_itr );
4391 buffer_free( select_buf );
4392 buffer_free( group_buf );
4393 if( defaultselhash )
4394 jsonObjectFree( defaultselhash );
4395 free( join_clause );
4402 if( flags & DISABLE_I18N )
4405 i18n = osrfHashGet( field_def, "i18n" );
4407 if( str_is_true( i18n ) ) {
4408 buffer_fadd( select_buf,
4409 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4410 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4411 class_tname, cname, col_name, class_pkey, cname,
4412 class_pkey, locale, _alias );
4414 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4415 cname, col_name, _alias );
4418 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4419 cname, col_name, _alias );
4426 "%s: Selected item is unexpected JSON type: %s",
4428 json_type( selfield->type )
4431 osrfAppSessionStatus(
4433 OSRF_STATUS_INTERNALSERVERERROR,
4434 "osrfMethodException",
4436 "Ill-formed SELECT item in JSON query"
4438 jsonIteratorFree( selclass_itr );
4439 buffer_free( select_buf );
4440 buffer_free( group_buf );
4441 if( defaultselhash )
4442 jsonObjectFree( defaultselhash );
4443 free( join_clause );
4447 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4448 if( obj_is_true( agg_obj ) )
4449 aggregate_found = 1;
4451 // Append a comma (except for the first one)
4452 // and add the column to a GROUP BY clause
4456 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4458 buffer_fadd( group_buf, " %d", sel_pos );
4462 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4464 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( elfield, "aggregate");
4465 if ( ! obj_is_true( aggregate_obj ) ) {
4469 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4472 buffer_fadd(group_buf, " %d", sel_pos);
4475 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4479 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4482 _column = searchFieldTransform(class_info->alias, field, selfield);
4483 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4484 OSRF_BUFFER_ADD(group_buf, _column);
4485 _column = searchFieldTransform(class_info->alias, field, selfield);
4492 } // end while -- iterating across SELECT columns
4494 } // end while -- iterating across classes
4496 jsonIteratorFree( selclass_itr );
4499 char* col_list = buffer_release( select_buf );
4501 // Make sure the SELECT list isn't empty. This can happen, for example,
4502 // if we try to build a default SELECT clause from a non-core table.
4505 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4507 osrfAppSessionStatus(
4509 OSRF_STATUS_INTERNALSERVERERROR,
4510 "osrfMethodException",
4512 "SELECT list is empty"
4515 buffer_free( group_buf );
4516 if( defaultselhash )
4517 jsonObjectFree( defaultselhash );
4518 free( join_clause );
4524 table = searchValueTransform( join_hash );
4526 table = strdup( curr_query->core.source_def );
4530 osrfAppSessionStatus(
4532 OSRF_STATUS_INTERNALSERVERERROR,
4533 "osrfMethodException",
4535 "Unable to identify table for core class"
4538 buffer_free( group_buf );
4539 if( defaultselhash )
4540 jsonObjectFree( defaultselhash );
4541 free( join_clause );
4545 // Put it all together
4546 growing_buffer* sql_buf = buffer_init( 128 );
4547 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4551 // Append the join clause, if any
4553 buffer_add(sql_buf, join_clause );
4554 free( join_clause );
4557 char* order_by_list = NULL;
4558 char* having_buf = NULL;
4560 if( !from_function ) {
4562 // Build a WHERE clause, if there is one
4564 buffer_add( sql_buf, " WHERE " );
4566 // and it's on the WHERE clause
4567 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4570 osrfAppSessionStatus(
4572 OSRF_STATUS_INTERNALSERVERERROR,
4573 "osrfMethodException",
4575 "Severe query error in WHERE predicate -- see error log for more details"
4578 buffer_free( group_buf );
4579 buffer_free( sql_buf );
4580 if( defaultselhash )
4581 jsonObjectFree( defaultselhash );
4585 buffer_add( sql_buf, pred );
4589 // Build a HAVING clause, if there is one
4592 // and it's on the the WHERE clause
4593 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4595 if( ! having_buf ) {
4597 osrfAppSessionStatus(
4599 OSRF_STATUS_INTERNALSERVERERROR,
4600 "osrfMethodException",
4602 "Severe query error in HAVING predicate -- see error log for more details"
4605 buffer_free( group_buf );
4606 buffer_free( sql_buf );
4607 if( defaultselhash )
4608 jsonObjectFree( defaultselhash );
4613 // Build an ORDER BY clause, if there is one
4614 if( NULL == order_hash )
4615 ; // No ORDER BY? do nothing
4616 else if( JSON_ARRAY == order_hash->type ) {
4617 order_by_list = buildOrderByFromArray( ctx, order_hash );
4618 if( !order_by_list ) {
4620 buffer_free( group_buf );
4621 buffer_free( sql_buf );
4622 if( defaultselhash )
4623 jsonObjectFree( defaultselhash );
4626 } else if( JSON_HASH == order_hash->type ) {
4627 // This hash is keyed on class alias. Each class has either
4628 // an array of field names or a hash keyed on field name.
4629 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4630 jsonIterator* class_itr = jsonNewIterator( order_hash );
4631 while( (snode = jsonIteratorNext( class_itr )) ) {
4633 ClassInfo* order_class_info = search_alias( class_itr->key );
4634 if( ! order_class_info ) {
4635 osrfLogError( OSRF_LOG_MARK,
4636 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4637 modulename, class_itr->key );
4639 osrfAppSessionStatus(
4641 OSRF_STATUS_INTERNALSERVERERROR,
4642 "osrfMethodException",
4644 "Invalid class referenced in ORDER BY clause -- "
4645 "see error log for more details"
4647 jsonIteratorFree( class_itr );
4648 buffer_free( order_buf );
4650 buffer_free( group_buf );
4651 buffer_free( sql_buf );
4652 if( defaultselhash )
4653 jsonObjectFree( defaultselhash );
4657 osrfHash* field_list_def = order_class_info->fields;
4659 if( snode->type == JSON_HASH ) {
4661 // Hash is keyed on field names from the current class. For each field
4662 // there is another layer of hash to define the sorting details, if any,
4663 // or a string to indicate direction of sorting.
4664 jsonIterator* order_itr = jsonNewIterator( snode );
4665 while( (onode = jsonIteratorNext( order_itr )) ) {
4667 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4669 osrfLogError( OSRF_LOG_MARK,
4670 "%s: Invalid field \"%s\" in ORDER BY clause",
4671 modulename, order_itr->key );
4673 osrfAppSessionStatus(
4675 OSRF_STATUS_INTERNALSERVERERROR,
4676 "osrfMethodException",
4678 "Invalid field in ORDER BY clause -- "
4679 "see error log for more details"
4681 jsonIteratorFree( order_itr );
4682 jsonIteratorFree( class_itr );
4683 buffer_free( order_buf );
4685 buffer_free( group_buf );
4686 buffer_free( sql_buf );
4687 if( defaultselhash )
4688 jsonObjectFree( defaultselhash );
4690 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4691 osrfLogError( OSRF_LOG_MARK,
4692 "%s: Virtual field \"%s\" in ORDER BY clause",
4693 modulename, order_itr->key );
4695 osrfAppSessionStatus(
4697 OSRF_STATUS_INTERNALSERVERERROR,
4698 "osrfMethodException",
4700 "Virtual field in ORDER BY clause -- "
4701 "see error log for more details"
4703 jsonIteratorFree( order_itr );
4704 jsonIteratorFree( class_itr );
4705 buffer_free( order_buf );
4707 buffer_free( group_buf );
4708 buffer_free( sql_buf );
4709 if( defaultselhash )
4710 jsonObjectFree( defaultselhash );
4714 const char* direction = NULL;
4715 if( onode->type == JSON_HASH ) {
4716 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4717 string = searchFieldTransform(
4719 osrfHashGet( field_list_def, order_itr->key ),
4723 if( ctx ) osrfAppSessionStatus(
4725 OSRF_STATUS_INTERNALSERVERERROR,
4726 "osrfMethodException",
4728 "Severe query error in ORDER BY clause -- "
4729 "see error log for more details"
4731 jsonIteratorFree( order_itr );
4732 jsonIteratorFree( class_itr );
4734 buffer_free( group_buf );
4735 buffer_free( order_buf);
4736 buffer_free( sql_buf );
4737 if( defaultselhash )
4738 jsonObjectFree( defaultselhash );
4742 growing_buffer* field_buf = buffer_init( 16 );
4743 buffer_fadd( field_buf, "\"%s\".%s",
4744 class_itr->key, order_itr->key );
4745 string = buffer_release( field_buf );
4748 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4749 const char* dir = jsonObjectGetString( tmp_const );
4750 if(!strncasecmp( dir, "d", 1 )) {
4751 direction = " DESC";
4757 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4758 osrfLogError( OSRF_LOG_MARK,
4759 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4760 modulename, json_type( onode->type ) );
4762 osrfAppSessionStatus(
4764 OSRF_STATUS_INTERNALSERVERERROR,
4765 "osrfMethodException",
4767 "Malformed ORDER BY clause -- see error log for more details"
4769 jsonIteratorFree( order_itr );
4770 jsonIteratorFree( class_itr );
4772 buffer_free( group_buf );
4773 buffer_free( order_buf );
4774 buffer_free( sql_buf );
4775 if( defaultselhash )
4776 jsonObjectFree( defaultselhash );
4780 string = strdup( order_itr->key );
4781 const char* dir = jsonObjectGetString( onode );
4782 if( !strncasecmp( dir, "d", 1 )) {
4783 direction = " DESC";
4790 OSRF_BUFFER_ADD( order_buf, ", " );
4792 order_buf = buffer_init( 128 );
4794 OSRF_BUFFER_ADD( order_buf, string );
4798 OSRF_BUFFER_ADD( order_buf, direction );
4802 jsonIteratorFree( order_itr );
4804 } else if( snode->type == JSON_ARRAY ) {
4806 // Array is a list of fields from the current class
4807 unsigned long order_idx = 0;
4808 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4810 const char* _f = jsonObjectGetString( onode );
4812 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4814 osrfLogError( OSRF_LOG_MARK,
4815 "%s: Invalid field \"%s\" in ORDER BY clause",
4818 osrfAppSessionStatus(
4820 OSRF_STATUS_INTERNALSERVERERROR,
4821 "osrfMethodException",
4823 "Invalid field in ORDER BY clause -- "
4824 "see error log for more details"
4826 jsonIteratorFree( class_itr );
4827 buffer_free( order_buf );
4829 buffer_free( group_buf );
4830 buffer_free( sql_buf );
4831 if( defaultselhash )
4832 jsonObjectFree( defaultselhash );
4834 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4835 osrfLogError( OSRF_LOG_MARK,
4836 "%s: Virtual field \"%s\" in ORDER BY clause",
4839 osrfAppSessionStatus(
4841 OSRF_STATUS_INTERNALSERVERERROR,
4842 "osrfMethodException",
4844 "Virtual field in ORDER BY clause -- "
4845 "see error log for more details"
4847 jsonIteratorFree( class_itr );
4848 buffer_free( order_buf );
4850 buffer_free( group_buf );
4851 buffer_free( sql_buf );
4852 if( defaultselhash )
4853 jsonObjectFree( defaultselhash );
4858 OSRF_BUFFER_ADD( order_buf, ", " );
4860 order_buf = buffer_init( 128 );
4862 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4866 // IT'S THE OOOOOOOOOOOLD STYLE!
4868 osrfLogError( OSRF_LOG_MARK,
4869 "%s: Possible SQL injection attempt; direct order by is not allowed",
4872 osrfAppSessionStatus(
4874 OSRF_STATUS_INTERNALSERVERERROR,
4875 "osrfMethodException",
4877 "Severe query error -- see error log for more details"
4882 buffer_free( group_buf );
4883 buffer_free( order_buf );
4884 buffer_free( sql_buf );
4885 if( defaultselhash )
4886 jsonObjectFree( defaultselhash );
4887 jsonIteratorFree( class_itr );
4891 jsonIteratorFree( class_itr );
4893 order_by_list = buffer_release( order_buf );
4895 osrfLogError( OSRF_LOG_MARK,
4896 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4897 modulename, json_type( order_hash->type ) );
4899 osrfAppSessionStatus(
4901 OSRF_STATUS_INTERNALSERVERERROR,
4902 "osrfMethodException",
4904 "Malformed ORDER BY clause -- see error log for more details"
4907 buffer_free( group_buf );
4908 buffer_free( sql_buf );
4909 if( defaultselhash )
4910 jsonObjectFree( defaultselhash );
4915 string = buffer_release( group_buf );
4917 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4918 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4919 OSRF_BUFFER_ADD( sql_buf, string );
4924 if( having_buf && *having_buf ) {
4925 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4926 OSRF_BUFFER_ADD( sql_buf, having_buf );
4930 if( order_by_list ) {
4932 if( *order_by_list ) {
4933 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4934 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4937 free( order_by_list );
4941 const char* str = jsonObjectGetString( limit );
4942 if (str) { // limit could be JSON_NULL, etc.
4943 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4948 const char* str = jsonObjectGetString( offset );
4950 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4954 if( !(flags & SUBSELECT) )
4955 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4957 if( defaultselhash )
4958 jsonObjectFree( defaultselhash );
4960 return buffer_release( sql_buf );
4962 } // end of SELECT()
4965 @brief Build a list of ORDER BY expressions.
4966 @param ctx Pointer to the method context.
4967 @param order_array Pointer to a JSON_ARRAY of field specifications.
4968 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
4969 Each expression may be either a column reference or a function call whose first parameter
4970 is a column reference.
4972 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
4973 It may optionally include entries for "direction" and/or "transform".
4975 The calling code is responsible for freeing the returned string.
4977 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
4978 if( ! order_array ) {
4979 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
4982 osrfAppSessionStatus(
4984 OSRF_STATUS_INTERNALSERVERERROR,
4985 "osrfMethodException",
4987 "Logic error: ORDER BY clause expected, not found; "
4988 "see error log for more details"
4991 } else if( order_array->type != JSON_ARRAY ) {
4992 osrfLogError( OSRF_LOG_MARK,
4993 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
4995 osrfAppSessionStatus(
4997 OSRF_STATUS_INTERNALSERVERERROR,
4998 "osrfMethodException",
5000 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
5004 growing_buffer* order_buf = buffer_init( 128 );
5005 int first = 1; // boolean
5007 jsonObject* order_spec;
5008 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
5010 if( JSON_HASH != order_spec->type ) {
5011 osrfLogError( OSRF_LOG_MARK,
5012 "%s: Malformed field specification in ORDER BY clause; "
5013 "expected JSON_HASH, found %s",
5014 modulename, json_type( order_spec->type ) );
5016 osrfAppSessionStatus(
5018 OSRF_STATUS_INTERNALSERVERERROR,
5019 "osrfMethodException",
5021 "Malformed ORDER BY clause -- see error log for more details"
5023 buffer_free( order_buf );
5027 const char* class_alias =
5028 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5030 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5032 jsonObject* compare_to = jsonObjectGetKeyConst( order_spec, "compare" );
5034 if( !field || !class_alias ) {
5035 osrfLogError( OSRF_LOG_MARK,
5036 "%s: Missing class or field name in field specification of ORDER BY clause",
5039 osrfAppSessionStatus(
5041 OSRF_STATUS_INTERNALSERVERERROR,
5042 "osrfMethodException",
5044 "Malformed ORDER BY clause -- see error log for more details"
5046 buffer_free( order_buf );
5050 const ClassInfo* order_class_info = search_alias( class_alias );
5051 if( ! order_class_info ) {
5052 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5053 "not in FROM clause, skipping it", modulename, class_alias );
5057 // Add a separating comma, except at the beginning
5061 OSRF_BUFFER_ADD( order_buf, ", " );
5063 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5065 osrfLogError( OSRF_LOG_MARK,
5066 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5067 modulename, class_alias, field );
5069 osrfAppSessionStatus(
5071 OSRF_STATUS_INTERNALSERVERERROR,
5072 "osrfMethodException",
5074 "Invalid field referenced in ORDER BY clause -- "
5075 "see error log for more details"
5079 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5080 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5081 modulename, field );
5083 osrfAppSessionStatus(
5085 OSRF_STATUS_INTERNALSERVERERROR,
5086 "osrfMethodException",
5088 "Virtual field in ORDER BY clause -- see error log for more details"
5090 buffer_free( order_buf );
5094 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5095 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5096 if( ! transform_str ) {
5098 osrfAppSessionStatus(
5100 OSRF_STATUS_INTERNALSERVERERROR,
5101 "osrfMethodException",
5103 "Severe query error in ORDER BY clause -- "
5104 "see error log for more details"
5106 buffer_free( order_buf );
5110 OSRF_BUFFER_ADD( order_buf, transform_str );
5111 free( transform_str );
5112 } else if( compare_to ) {
5113 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5114 if( ! compare_str ) {
5116 osrfAppSessionStatus(
5118 OSRF_STATUS_INTERNALSERVERERROR,
5119 "osrfMethodException",
5121 "Severe query error in ORDER BY clause -- "
5122 "see error log for more details"
5124 buffer_free( order_buf );
5128 buffer_fadd( order_buf, "(%s)", compare_str );
5129 free( compare_str );
5132 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5134 const char* direction =
5135 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5137 if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5138 OSRF_BUFFER_ADD( order_buf, " DESC" );
5140 OSRF_BUFFER_ADD( order_buf, " ASC" );
5144 return buffer_release( order_buf );
5148 @brief Build a SELECT statement.
5149 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5150 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5151 @param meta Pointer to the class metadata for the core class.
5152 @param ctx Pointer to the method context.
5153 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5155 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5156 "order_by", "limit", and "offset".
5158 The SELECT statements built here are distinct from those built for the json_query method.
5160 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5161 osrfHash* meta, osrfMethodContext* ctx ) {
5163 const char* locale = osrf_message_get_last_locale();
5165 osrfHash* fields = osrfHashGet( meta, "fields" );
5166 const char* core_class = osrfHashGet( meta, "classname" );
5168 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5170 jsonObject* selhash = NULL;
5171 jsonObject* defaultselhash = NULL;
5173 growing_buffer* sql_buf = buffer_init( 128 );
5174 growing_buffer* select_buf = buffer_init( 128 );
5176 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5177 defaultselhash = jsonNewObjectType( JSON_HASH );
5178 selhash = defaultselhash;
5181 // If there's no SELECT list for the core class, build one
5182 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5183 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5185 // Add every non-virtual field to the field list
5186 osrfHash* field_def = NULL;
5187 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5188 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5189 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5190 const char* field = osrfHashIteratorKey( field_itr );
5191 jsonObjectPush( field_list, jsonNewObject( field ) );
5194 osrfHashIteratorFree( field_itr );
5195 jsonObjectSetKey( selhash, core_class, field_list );
5198 // Build a list of columns for the SELECT clause
5200 const jsonObject* snode = NULL;
5201 jsonIterator* class_itr = jsonNewIterator( selhash );
5202 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5204 // If the class isn't in the IDL, ignore it
5205 const char* cname = class_itr->key;
5206 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5210 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5211 if( strcmp( core_class, class_itr->key )) {
5215 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5216 if( !found->size ) {
5217 jsonObjectFree( found );
5221 jsonObjectFree( found );
5224 const jsonObject* node = NULL;
5225 jsonIterator* select_itr = jsonNewIterator( snode );
5226 while( (node = jsonIteratorNext( select_itr )) ) {
5227 const char* item_str = jsonObjectGetString( node );
5228 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5229 char* fname = osrfHashGet( field, "name" );
5234 if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5240 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5245 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5246 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5249 i18n = osrfHashGet( field, "i18n" );
5251 if( str_is_true( i18n ) ) {
5252 char* pkey = osrfHashGet( idlClass, "primarykey" );
5253 char* tname = osrfHashGet( idlClass, "tablename" );
5255 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5256 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5257 tname, cname, fname, pkey, cname, pkey, locale, fname );
5259 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5262 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5266 jsonIteratorFree( select_itr );
5269 jsonIteratorFree( class_itr );
5271 char* col_list = buffer_release( select_buf );
5272 char* table = oilsGetRelation( meta );
5274 table = strdup( "(null)" );
5276 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5280 // Clear the query stack (as a fail-safe precaution against possible
5281 // leftover garbage); then push the first query frame onto the stack.
5282 clear_query_stack();
5284 if( add_query_core( NULL, core_class ) ) {
5286 osrfAppSessionStatus(
5288 OSRF_STATUS_INTERNALSERVERERROR,
5289 "osrfMethodException",
5291 "Unable to build query frame for core class"
5293 buffer_free( sql_buf );
5294 if( defaultselhash )
5295 jsonObjectFree( defaultselhash );
5299 // Add the JOIN clauses, if any
5301 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5302 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5303 OSRF_BUFFER_ADD( sql_buf, join_clause );
5304 free( join_clause );
5307 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5308 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5310 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5312 // Add the conditions in the WHERE clause
5313 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5315 osrfAppSessionStatus(
5317 OSRF_STATUS_INTERNALSERVERERROR,
5318 "osrfMethodException",
5320 "Severe query error -- see error log for more details"
5322 buffer_free( sql_buf );
5323 if( defaultselhash )
5324 jsonObjectFree( defaultselhash );
5325 clear_query_stack();
5328 buffer_add( sql_buf, pred );
5332 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5333 if( rest_of_query ) {
5334 const jsonObject* order_by = NULL;
5335 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5337 char* order_by_list = NULL;
5339 if( JSON_ARRAY == order_by->type ) {
5340 order_by_list = buildOrderByFromArray( ctx, order_by );
5341 if( !order_by_list ) {
5342 buffer_free( sql_buf );
5343 if( defaultselhash )
5344 jsonObjectFree( defaultselhash );
5345 clear_query_stack();
5348 } else if( JSON_HASH == order_by->type ) {
5349 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5350 // and build a list of ORDER BY expressions.
5351 growing_buffer* order_buf = buffer_init( 128 );
5353 jsonIterator* class_itr = jsonNewIterator( order_by );
5354 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5356 ClassInfo* order_class_info = search_alias( class_itr->key );
5357 if( ! order_class_info )
5358 continue; // class not referenced by FROM clause? Ignore it.
5360 if( JSON_HASH == snode->type ) {
5362 // If the data for the current class is a JSON_HASH, then it is
5363 // keyed on field name.
5365 const jsonObject* onode = NULL;
5366 jsonIterator* order_itr = jsonNewIterator( snode );
5367 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5369 osrfHash* field_def = osrfHashGet(
5370 order_class_info->fields, order_itr->key );
5372 continue; // Field not defined in IDL? Ignore it.
5373 if( str_is_true( osrfHashGet( field_def, "virtual")))
5374 continue; // Field is virtual? Ignore it.
5376 char* field_str = NULL;
5377 char* direction = NULL;
5378 if( onode->type == JSON_HASH ) {
5379 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5380 field_str = searchFieldTransform(
5381 class_itr->key, field_def, onode );
5383 osrfAppSessionStatus(
5385 OSRF_STATUS_INTERNALSERVERERROR,
5386 "osrfMethodException",
5388 "Severe query error in ORDER BY clause -- "
5389 "see error log for more details"
5391 jsonIteratorFree( order_itr );
5392 jsonIteratorFree( class_itr );
5393 buffer_free( order_buf );
5394 buffer_free( sql_buf );
5395 if( defaultselhash )
5396 jsonObjectFree( defaultselhash );
5397 clear_query_stack();
5401 growing_buffer* field_buf = buffer_init( 16 );
5402 buffer_fadd( field_buf, "\"%s\".%s",
5403 class_itr->key, order_itr->key );
5404 field_str = buffer_release( field_buf );
5407 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5408 const char* dir = jsonObjectGetString( order_by );
5409 if(!strncasecmp( dir, "d", 1 )) {
5410 direction = " DESC";
5414 field_str = strdup( order_itr->key );
5415 const char* dir = jsonObjectGetString( onode );
5416 if( !strncasecmp( dir, "d", 1 )) {
5417 direction = " DESC";
5426 buffer_add( order_buf, ", " );
5429 buffer_add( order_buf, field_str );
5433 buffer_add( order_buf, direction );
5435 } // end while; looping over ORDER BY expressions
5437 jsonIteratorFree( order_itr );
5439 } else if( JSON_STRING == snode->type ) {
5440 // We expect a comma-separated list of sort fields.
5441 const char* str = jsonObjectGetString( snode );
5442 if( strchr( str, ';' )) {
5443 // No semicolons allowed. It is theoretically possible for a
5444 // legitimate semicolon to occur within quotes, but it's not likely
5445 // to occur in practice in the context of an ORDER BY list.
5446 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5447 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5449 osrfAppSessionStatus(
5451 OSRF_STATUS_INTERNALSERVERERROR,
5452 "osrfMethodException",
5454 "Possible attempt at SOL injection -- "
5455 "semicolon found in ORDER BY list"
5458 jsonIteratorFree( class_itr );
5459 buffer_free( order_buf );
5460 buffer_free( sql_buf );
5461 if( defaultselhash )
5462 jsonObjectFree( defaultselhash );
5463 clear_query_stack();
5466 buffer_add( order_buf, str );
5470 } // end while; looping over order_by classes
5472 jsonIteratorFree( class_itr );
5473 order_by_list = buffer_release( order_buf );
5476 osrfLogWarning( OSRF_LOG_MARK,
5477 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5478 "no ORDER BY generated" );
5481 if( order_by_list && *order_by_list ) {
5482 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5483 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5486 free( order_by_list );
5489 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5491 const char* str = jsonObjectGetString( limit );
5501 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5503 const char* str = jsonObjectGetString( offset );
5514 if( defaultselhash )
5515 jsonObjectFree( defaultselhash );
5516 clear_query_stack();
5518 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5519 return buffer_release( sql_buf );
5522 int doJSONSearch ( osrfMethodContext* ctx ) {
5523 if(osrfMethodVerifyContext( ctx )) {
5524 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5528 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5532 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5536 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5537 flags |= SELECT_DISTINCT;
5539 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5540 flags |= DISABLE_I18N;
5542 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5543 clear_query_stack(); // a possibly needless precaution
5544 char* sql = buildQuery( ctx, hash, flags );
5545 clear_query_stack();
5552 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5555 dbhandle = writehandle;
5557 dbi_result result = dbi_conn_query( dbhandle, sql );
5560 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5562 if( dbi_result_first_row( result )) {
5563 /* JSONify the result */
5564 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5567 jsonObject* return_val = oilsMakeJSONFromResult( result );
5568 osrfAppRespond( ctx, return_val );
5569 jsonObjectFree( return_val );
5570 } while( dbi_result_next_row( result ));
5573 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5576 osrfAppRespondComplete( ctx, NULL );
5578 /* clean up the query */
5579 dbi_result_free( result );
5584 int errnum = dbi_conn_error( dbhandle, &msg );
5585 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5586 modulename, sql, errnum, msg ? msg : "(No description available)" );
5587 osrfAppSessionStatus(
5589 OSRF_STATUS_INTERNALSERVERERROR,
5590 "osrfMethodException",
5592 "Severe query error -- see error log for more details"
5594 if( !oilsIsDBConnected( dbhandle ))
5595 osrfAppSessionPanic( ctx->session );
5602 // The last parameter, err, is used to report an error condition by updating an int owned by
5603 // the calling code.
5605 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5606 // It is the responsibility of the calling code to initialize *err before the
5607 // call, so that it will be able to make sense of the result.
5609 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5610 // redundant anyway.
5611 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5612 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5615 dbhandle = writehandle;
5617 char* core_class = osrfHashGet( class_meta, "classname" );
5618 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5620 char* pkey = osrfHashGet( class_meta, "primarykey" );
5622 if (!ctx->session->userData)
5623 (void) initSessionCache( ctx );
5625 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5626 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5627 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5629 int i_respond_directly = 0;
5630 int flesh_depth = 0;
5632 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5634 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5639 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5641 dbi_result result = dbi_conn_query( dbhandle, sql );
5642 if( NULL == result ) {
5644 int errnum = dbi_conn_error( dbhandle, &msg );
5645 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5646 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5647 msg ? msg : "(No description available)" );
5648 if( !oilsIsDBConnected( dbhandle ))
5649 osrfAppSessionPanic( ctx->session );
5650 osrfAppSessionStatus(
5652 OSRF_STATUS_INTERNALSERVERERROR,
5653 "osrfMethodException",
5655 "Severe query error -- see error log for more details"
5662 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5665 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5666 jsonObject* row_obj = NULL;
5668 // The following two steps are for verifyObjectPCRUD()'s benefit.
5669 // 1. get the flesh depth
5670 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5672 flesh_depth = (int) jsonObjectGetNumber( _tmp );
5673 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5674 flesh_depth = max_flesh_depth;
5677 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
5678 // over the whole life of this request. This means if we've already set
5679 // up a rs_size_req_%d, do nothing.
5680 // a. Incidentally, we can also use this opportunity to set i_respond_directly
5681 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
5682 if( !rs_size ) { // pointer null, so value not set in hash
5683 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
5684 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
5686 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
5687 unsigned long long result_count = dbi_result_get_numrows( result );
5688 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
5689 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
5692 if( dbi_result_first_row( result )) {
5694 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5695 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5696 // eliminate the duplicates.
5697 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5698 osrfHash* dedup = osrfNewHash();
5700 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5701 char* pkey_val = oilsFMGetString( row_obj, pkey );
5702 if( osrfHashGet( dedup, pkey_val ) ) {
5703 jsonObjectFree( row_obj );
5706 if( !enforce_pcrud || !need_to_verify ||
5707 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
5708 osrfHashSet( dedup, pkey_val, pkey_val );
5709 jsonObjectPush( res_list, row_obj );
5712 } while( dbi_result_next_row( result ));
5713 osrfHashFree( dedup );
5716 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5720 /* clean up the query */
5721 dbi_result_free( result );
5724 // If we're asked to flesh, and there's anything to flesh, then flesh it
5725 // (formerly we would skip fleshing if in pcrud mode, but now we support
5726 // fleshing even in PCRUD).
5727 if( res_list->size ) {
5728 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
5729 jsonObject* flesh_fields;
5730 jsonObject* flesh_blob = NULL;
5731 osrfStringArray* link_fields = NULL;
5732 osrfHash* links = NULL;
5736 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
5737 if( temp_blob && flesh_depth > 0 ) {
5739 flesh_blob = jsonObjectClone( temp_blob );
5740 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
5742 links = osrfHashGet( class_meta, "links" );
5744 // Make an osrfStringArray of the names of fields to be fleshed
5745 if( flesh_fields ) {
5746 if( flesh_fields->size == 1 ) {
5747 const char* _t = jsonObjectGetString(
5748 jsonObjectGetIndex( flesh_fields, 0 ) );
5749 if( !strcmp( _t, "*" ))
5750 link_fields = osrfHashKeys( links );
5753 if( !link_fields ) {
5755 link_fields = osrfNewStringArray( 1 );
5756 jsonIterator* _i = jsonNewIterator( flesh_fields );
5757 while ((_f = jsonIteratorNext( _i ))) {
5758 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5760 jsonIteratorFree( _i );
5763 want_flesh = link_fields ? 1 : 0;
5767 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5769 // Iterate over the JSON_ARRAY of rows
5771 unsigned long res_idx = 0;
5772 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5775 const char* link_field;
5777 // Iterate over the list of fleshable fields
5779 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5781 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5783 osrfHash* kid_link = osrfHashGet( links, link_field );
5785 continue; // Not a link field; skip it
5787 osrfHash* field = osrfHashGet( fields, link_field );
5789 continue; // Not a field at all; skip it (IDL is ill-formed)
5791 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5792 osrfHashGet( kid_link, "class" ));
5794 continue; // The class it links to doesn't exist; skip it
5796 const char* reltype = osrfHashGet( kid_link, "reltype" );
5798 continue; // No reltype; skip it (IDL is ill-formed)
5800 osrfHash* value_field = field;
5802 if( !strcmp( reltype, "has_many" )
5803 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5804 value_field = osrfHashGet(
5805 fields, osrfHashGet( class_meta, "primarykey" ) );
5808 int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
5809 // fleshing pcrud case: we require the controller in need_to_verify mode
5810 if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
5811 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
5815 (unsigned long) atoi( osrfHashGet(field, "array_position") ),
5817 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
5823 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5825 if( link_map->size > 0 ) {
5826 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5829 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5834 osrfHashGet( kid_link, "class" ),
5841 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5842 osrfHashGet( kid_link, "field" ),
5843 osrfHashGet( kid_link, "class" ),
5844 osrfHashGet( kid_link, "key" ),
5845 osrfHashGet( kid_link, "reltype" )
5848 const char* search_key = jsonObjectGetString(
5849 jsonObjectGetIndex( cur,
5850 atoi( osrfHashGet( value_field, "array_position" ) )
5855 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5859 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5861 // construct WHERE clause
5862 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5865 osrfHashGet( kid_link, "key" ),
5866 jsonNewObject( search_key )
5869 // construct the rest of the query, mostly
5870 // by copying pieces of the previous level of query
5871 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5872 jsonObjectSetKey( rest_of_query, "flesh",
5873 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5877 jsonObjectSetKey( rest_of_query, "flesh_fields",
5878 jsonObjectClone( flesh_blob ));
5880 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5881 jsonObjectSetKey( rest_of_query, "order_by",
5882 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5886 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5887 jsonObjectSetKey( rest_of_query, "select",
5888 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5892 // do the query, recursively, to expand the fleshable field
5893 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5894 where_clause, rest_of_query, err );
5896 jsonObjectFree( where_clause );
5897 jsonObjectFree( rest_of_query );
5900 osrfStringArrayFree( link_fields );
5901 jsonObjectFree( res_list );
5902 jsonObjectFree( flesh_blob );
5906 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5907 osrfHashGet( kid_link, "class" ), kids->size );
5909 // Traverse the result set
5910 jsonObject* X = NULL;
5911 if( link_map->size > 0 && kids->size > 0 ) {
5913 kids = jsonNewObjectType( JSON_ARRAY );
5915 jsonObject* _k_node;
5916 unsigned long res_idx = 0;
5917 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5923 (unsigned long) atoi(
5929 osrfHashGet( kid_link, "class" )
5933 osrfStringArrayGetString( link_map, 0 )
5941 } // end while loop traversing X
5944 if (kids->size > 0) {
5946 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5947 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
5949 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5950 osrfHashGet( kid_link, "field" ));
5953 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5954 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5959 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5961 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5962 osrfHashGet( kid_link, "field" ) );
5965 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5966 jsonObjectClone( kids )
5971 jsonObjectFree( kids );
5975 jsonObjectFree( kids );
5977 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5978 osrfHashGet( kid_link, "field" ) );
5979 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5981 } // end while loop traversing list of fleshable fields
5984 if( i_respond_directly ) {
5985 if ( *methodtype == 'i' ) {
5986 osrfAppRespond( ctx,
5987 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
5989 osrfAppRespond( ctx, cur );
5992 } // end while loop traversing res_list
5993 jsonObjectFree( flesh_blob );
5994 osrfStringArrayFree( link_fields );
5997 if( i_respond_directly ) {
5998 jsonObjectFree( res_list );
5999 return jsonNewObjectType( JSON_ARRAY );
6006 int doUpdate( osrfMethodContext* ctx ) {
6007 if( osrfMethodVerifyContext( ctx )) {
6008 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6013 timeout_needs_resetting = 1;
6015 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6017 jsonObject* target = NULL;
6019 target = jsonObjectGetIndex( ctx->params, 1 );
6021 target = jsonObjectGetIndex( ctx->params, 0 );
6023 if(!verifyObjectClass( ctx, target )) {
6024 osrfAppRespondComplete( ctx, NULL );
6028 if( getXactId( ctx ) == NULL ) {
6029 osrfAppSessionStatus(
6031 OSRF_STATUS_BADREQUEST,
6032 "osrfMethodException",
6034 "No active transaction -- required for UPDATE"
6036 osrfAppRespondComplete( ctx, NULL );
6040 // The following test is harmless but redundant. If a class is
6041 // readonly, we don't register an update method for it.
6042 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6043 osrfAppSessionStatus(
6045 OSRF_STATUS_BADREQUEST,
6046 "osrfMethodException",
6048 "Cannot UPDATE readonly class"
6050 osrfAppRespondComplete( ctx, NULL );
6054 const char* trans_id = getXactId( ctx );
6056 // Set the last_xact_id
6057 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6059 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6060 trans_id, target->classname, index );
6061 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6064 char* pkey = osrfHashGet( meta, "primarykey" );
6065 osrfHash* fields = osrfHashGet( meta, "fields" );
6067 char* id = oilsFMGetString( target, pkey );
6071 "%s updating %s object with %s = %s",
6073 osrfHashGet( meta, "fieldmapper" ),
6078 dbhandle = writehandle;
6079 growing_buffer* sql = buffer_init( 128 );
6080 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6083 osrfHash* field_def = NULL;
6084 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6085 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6087 // Skip virtual fields, and the primary key
6088 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6091 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6095 const char* field_name = osrfHashIteratorKey( field_itr );
6096 if( ! strcmp( field_name, pkey ) )
6099 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6101 int value_is_numeric = 0; // boolean
6103 if( field_object && field_object->classname ) {
6104 value = oilsFMGetString(
6106 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6108 } else if( field_object && JSON_BOOL == field_object->type ) {
6109 if( jsonBoolIsTrue( field_object ) )
6110 value = strdup( "t" );
6112 value = strdup( "f" );
6114 value = jsonObjectToSimpleString( field_object );
6115 if( field_object && JSON_NUMBER == field_object->type )
6116 value_is_numeric = 1;
6119 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6120 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6122 if( !field_object || field_object->type == JSON_NULL ) {
6123 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6124 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6128 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6129 buffer_fadd( sql, " %s = NULL", field_name );
6132 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6136 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6138 const char* numtype = get_datatype( field_def );
6139 if( !strncmp( numtype, "INT", 3 ) ) {
6140 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6141 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6142 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6144 // Must really be intended as a string, so quote it
6145 if( dbi_conn_quote_string( dbhandle, &value )) {
6146 buffer_fadd( sql, " %s = %s", field_name, value );
6148 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6149 modulename, value );
6150 osrfAppSessionStatus(
6152 OSRF_STATUS_INTERNALSERVERERROR,
6153 "osrfMethodException",
6155 "Error quoting string -- please see the error log for more details"
6159 osrfHashIteratorFree( field_itr );
6161 osrfAppRespondComplete( ctx, NULL );
6166 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6169 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6173 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6174 buffer_fadd( sql, " %s = %s", field_name, value );
6176 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6177 osrfAppSessionStatus(
6179 OSRF_STATUS_INTERNALSERVERERROR,
6180 "osrfMethodException",
6182 "Error quoting string -- please see the error log for more details"
6186 osrfHashIteratorFree( field_itr );
6188 osrfAppRespondComplete( ctx, NULL );
6197 osrfHashIteratorFree( field_itr );
6199 jsonObject* obj = jsonNewObject( id );
6201 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6202 dbi_conn_quote_string( dbhandle, &id );
6204 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6206 char* query = buffer_release( sql );
6207 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6209 dbi_result result = dbi_conn_query( dbhandle, query );
6214 jsonObjectFree( obj );
6215 obj = jsonNewObject( NULL );
6217 int errnum = dbi_conn_error( dbhandle, &msg );
6220 "%s ERROR updating %s object with %s = %s: %d %s",
6222 osrfHashGet( meta, "fieldmapper" ),
6226 msg ? msg : "(No description available)"
6228 osrfAppSessionStatus(
6230 OSRF_STATUS_INTERNALSERVERERROR,
6231 "osrfMethodException",
6233 "Error in updating a row -- please see the error log for more details"
6235 if( !oilsIsDBConnected( dbhandle ))
6236 osrfAppSessionPanic( ctx->session );
6239 dbi_result_free( result );
6242 osrfAppRespondComplete( ctx, obj );
6243 jsonObjectFree( obj );
6247 int doDelete( osrfMethodContext* ctx ) {
6248 if( osrfMethodVerifyContext( ctx )) {
6249 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6254 timeout_needs_resetting = 1;
6256 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6258 if( getXactId( ctx ) == NULL ) {
6259 osrfAppSessionStatus(
6261 OSRF_STATUS_BADREQUEST,
6262 "osrfMethodException",
6264 "No active transaction -- required for DELETE"
6266 osrfAppRespondComplete( ctx, NULL );
6270 // The following test is harmless but redundant. If a class is
6271 // readonly, we don't register a delete method for it.
6272 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6273 osrfAppSessionStatus(
6275 OSRF_STATUS_BADREQUEST,
6276 "osrfMethodException",
6278 "Cannot DELETE readonly class"
6280 osrfAppRespondComplete( ctx, NULL );
6284 dbhandle = writehandle;
6286 char* pkey = osrfHashGet( meta, "primarykey" );
6293 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6294 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6295 osrfAppRespondComplete( ctx, NULL );
6299 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6301 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6302 osrfAppRespondComplete( ctx, NULL );
6305 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6310 "%s deleting %s object with %s = %s",
6312 osrfHashGet( meta, "fieldmapper" ),
6317 jsonObject* obj = jsonNewObject( id );
6319 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6320 dbi_conn_quote_string( writehandle, &id );
6322 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6323 osrfHashGet( meta, "tablename" ), pkey, id );
6328 jsonObjectFree( obj );
6329 obj = jsonNewObject( NULL );
6331 int errnum = dbi_conn_error( writehandle, &msg );
6334 "%s ERROR deleting %s object with %s = %s: %d %s",
6336 osrfHashGet( meta, "fieldmapper" ),
6340 msg ? msg : "(No description available)"
6342 osrfAppSessionStatus(
6344 OSRF_STATUS_INTERNALSERVERERROR,
6345 "osrfMethodException",
6347 "Error in deleting a row -- please see the error log for more details"
6349 if( !oilsIsDBConnected( writehandle ))
6350 osrfAppSessionPanic( ctx->session );
6352 dbi_result_free( result );
6356 osrfAppRespondComplete( ctx, obj );
6357 jsonObjectFree( obj );
6362 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6363 @param result An iterator for a result set; we only look at the current row.
6364 @param @meta Pointer to the class metadata for the core class.
6365 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6367 If a column is not defined in the IDL, or if it has no array_position defined for it in
6368 the IDL, or if it is defined as virtual, ignore it.
6370 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6371 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6372 array_position in the IDL.
6374 A field defined in the IDL but not represented in the returned row will leave a hole
6375 in the JSON_ARRAY. In effect it will be treated as a null value.
6377 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6378 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6379 classname corresponding to the @a meta argument.
6381 The calling code is responsible for freeing the the resulting jsonObject by calling
6384 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6385 if( !( result && meta )) return NULL;
6387 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6388 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6389 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6391 osrfHash* fields = osrfHashGet( meta, "fields" );
6393 int columnIndex = 1;
6394 const char* columnName;
6396 /* cycle through the columns in the row returned from the database */
6397 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6399 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6401 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6403 /* determine the field type and storage attributes */
6404 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6405 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6407 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6408 // or if it has no sequence number there, or if it's virtual, skip it.
6409 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6412 if( str_is_true( osrfHashGet( _f, "virtual" )))
6413 continue; // skip this column: IDL says it's virtual
6415 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6416 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6417 continue; // since we assign sequence numbers dynamically as we load the IDL.
6419 fmIndex = atoi( pos );
6420 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6422 continue; // This field is not defined in the IDL
6425 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6426 // sequence number from the IDL (which is likely to be different from the sequence
6427 // of columns in the SELECT clause).
6428 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6429 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6434 case DBI_TYPE_INTEGER :
6436 if( attr & DBI_INTEGER_SIZE8 )
6437 jsonObjectSetIndex( object, fmIndex,
6438 jsonNewNumberObject(
6439 dbi_result_get_longlong_idx( result, columnIndex )));
6441 jsonObjectSetIndex( object, fmIndex,
6442 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6446 case DBI_TYPE_DECIMAL :
6447 jsonObjectSetIndex( object, fmIndex,
6448 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6451 case DBI_TYPE_STRING :
6456 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6461 case DBI_TYPE_DATETIME : {
6463 char dt_string[ 256 ] = "";
6466 // Fetch the date column as a time_t
6467 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6469 // Translate the time_t to a human-readable string
6470 if( !( attr & DBI_DATETIME_DATE )) {
6471 gmtime_r( &_tmp_dt, &gmdt );
6472 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6473 } else if( !( attr & DBI_DATETIME_TIME )) {
6474 localtime_r( &_tmp_dt, &gmdt );
6475 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6477 localtime_r( &_tmp_dt, &gmdt );
6478 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6481 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6485 case DBI_TYPE_BINARY :
6486 osrfLogError( OSRF_LOG_MARK,
6487 "Can't do binary at column %s : index %d", columnName, columnIndex );
6496 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6497 if( !result ) return NULL;
6499 jsonObject* object = jsonNewObject( NULL );
6502 char dt_string[ 256 ];
6506 int columnIndex = 1;
6508 unsigned short type;
6509 const char* columnName;
6511 /* cycle through the column list */
6512 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6514 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6516 fmIndex = -1; // reset the position
6518 /* determine the field type and storage attributes */
6519 type = dbi_result_get_field_type_idx( result, columnIndex );
6520 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6522 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6523 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6528 case DBI_TYPE_INTEGER :
6530 if( attr & DBI_INTEGER_SIZE8 )
6531 jsonObjectSetKey( object, columnName,
6532 jsonNewNumberObject( dbi_result_get_longlong_idx(
6533 result, columnIndex )) );
6535 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6536 dbi_result_get_int_idx( result, columnIndex )) );
6539 case DBI_TYPE_DECIMAL :
6540 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6541 dbi_result_get_double_idx( result, columnIndex )) );
6544 case DBI_TYPE_STRING :
6545 jsonObjectSetKey( object, columnName,
6546 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6549 case DBI_TYPE_DATETIME :
6551 memset( dt_string, '\0', sizeof( dt_string ));
6552 memset( &gmdt, '\0', sizeof( gmdt ));
6554 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6556 if( !( attr & DBI_DATETIME_DATE )) {
6557 gmtime_r( &_tmp_dt, &gmdt );
6558 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6559 } else if( !( attr & DBI_DATETIME_TIME )) {
6560 localtime_r( &_tmp_dt, &gmdt );
6561 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6563 localtime_r( &_tmp_dt, &gmdt );
6564 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6567 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6570 case DBI_TYPE_BINARY :
6571 osrfLogError( OSRF_LOG_MARK,
6572 "Can't do binary at column %s : index %d", columnName, columnIndex );
6576 } // end while loop traversing result
6581 // Interpret a string as true or false
6582 int str_is_true( const char* str ) {
6583 if( NULL == str || strcasecmp( str, "true" ) )
6589 // Interpret a jsonObject as true or false
6590 static int obj_is_true( const jsonObject* obj ) {
6593 else switch( obj->type )
6601 if( strcasecmp( obj->value.s, "true" ) )
6605 case JSON_NUMBER : // Support 1/0 for perl's sake
6606 if( jsonObjectGetNumber( obj ) == 1.0 )
6615 // Translate a numeric code into a text string identifying a type of
6616 // jsonObject. To be used for building error messages.
6617 static const char* json_type( int code ) {
6623 return "JSON_ARRAY";
6625 return "JSON_STRING";
6627 return "JSON_NUMBER";
6633 return "(unrecognized)";
6637 // Extract the "primitive" attribute from an IDL field definition.
6638 // If we haven't initialized the app, then we must be running in
6639 // some kind of testbed. In that case, default to "string".
6640 static const char* get_primitive( osrfHash* field ) {
6641 const char* s = osrfHashGet( field, "primitive" );
6643 if( child_initialized )
6646 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6648 osrfHashGet( field, "name" )
6656 // Extract the "datatype" attribute from an IDL field definition.
6657 // If we haven't initialized the app, then we must be running in
6658 // some kind of testbed. In that case, default to to NUMERIC,
6659 // since we look at the datatype only for numbers.
6660 static const char* get_datatype( osrfHash* field ) {
6661 const char* s = osrfHashGet( field, "datatype" );
6663 if( child_initialized )
6666 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6668 osrfHashGet( field, "name" )
6677 @brief Determine whether a string is potentially a valid SQL identifier.
6678 @param s The identifier to be tested.
6679 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6681 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6682 need to follow all the rules exactly, such as requiring that the first character not
6685 We allow leading and trailing white space. In between, we do not allow punctuation
6686 (except for underscores and dollar signs), control characters, or embedded white space.
6688 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6689 for the foreseeable future such quoted identifiers are not likely to be an issue.
6691 int is_identifier( const char* s) {
6695 // Skip leading white space
6696 while( isspace( (unsigned char) *s ) )
6700 return 0; // Nothing but white space? Not okay.
6702 // Check each character until we reach white space or
6703 // end-of-string. Letters, digits, underscores, and
6704 // dollar signs are okay. With the exception of periods
6705 // (as in schema.identifier), control characters and other
6706 // punctuation characters are not okay. Anything else
6707 // is okay -- it could for example be part of a multibyte
6708 // UTF8 character such as a letter with diacritical marks,
6709 // and those are allowed.
6711 if( isalnum( (unsigned char) *s )
6715 ; // Fine; keep going
6716 else if( ispunct( (unsigned char) *s )
6717 || iscntrl( (unsigned char) *s ) )
6720 } while( *s && ! isspace( (unsigned char) *s ) );
6722 // If we found any white space in the above loop,
6723 // the rest had better be all white space.
6725 while( isspace( (unsigned char) *s ) )
6729 return 0; // White space was embedded within non-white space
6735 @brief Determine whether to accept a character string as a comparison operator.
6736 @param op The candidate comparison operator.
6737 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6739 We don't validate the operator for real. We just make sure that it doesn't contain
6740 any semicolons or white space (with special exceptions for a few specific operators).
6741 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6742 space but it's still not a valid operator, then the database will complain.
6744 Another approach would be to compare the string against a short list of approved operators.
6745 We don't do that because we want to allow custom operators like ">100*", which at this
6746 writing would be difficult or impossible to express otherwise in a JSON query.
6748 int is_good_operator( const char* op ) {
6749 if( !op ) return 0; // Sanity check
6753 if( isspace( (unsigned char) *s ) ) {
6754 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6755 // and IS NOT DISTINCT FROM.
6756 if( !strcasecmp( op, "similar to" ) )
6758 else if( !strcasecmp( op, "is distinct from" ) )
6760 else if( !strcasecmp( op, "is not distinct from" ) )
6765 else if( ';' == *s )
6773 @name Query Frame Management
6775 The following machinery supports a stack of query frames for use by SELECT().
6777 A query frame caches information about one level of a SELECT query. When we enter
6778 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6780 The query frame stores information about the core class, and about any joined classes
6783 The main purpose is to map table aliases to classes and tables, so that a query can
6784 join to the same table more than once. A secondary goal is to reduce the number of
6785 lookups in the IDL by caching the results.
6789 #define STATIC_CLASS_INFO_COUNT 3
6791 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6794 @brief Allocate a ClassInfo as raw memory.
6795 @return Pointer to the newly allocated ClassInfo.
6797 Except for the in_use flag, which is used only by the allocation and deallocation
6798 logic, we don't initialize the ClassInfo here.
6800 static ClassInfo* allocate_class_info( void ) {
6801 // In order to reduce the number of mallocs and frees, we return a static
6802 // instance of ClassInfo, if we can find one that we're not already using.
6803 // We rely on the fact that the compiler will implicitly initialize the
6804 // static instances so that in_use == 0.
6807 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6808 if( ! static_class_info[ i ].in_use ) {
6809 static_class_info[ i ].in_use = 1;
6810 return static_class_info + i;
6814 // The static ones are all in use. Malloc one.
6816 return safe_malloc( sizeof( ClassInfo ) );
6820 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6821 @param info Pointer to the ClassInfo to be cleared.
6823 static void clear_class_info( ClassInfo* info ) {
6828 // Free any malloc'd strings
6830 if( info->alias != info->alias_store )
6831 free( info->alias );
6833 if( info->class_name != info->class_name_store )
6834 free( info->class_name );
6836 free( info->source_def );
6838 info->alias = info->class_name = info->source_def = NULL;
6843 @brief Free a ClassInfo and everything it owns.
6844 @param info Pointer to the ClassInfo to be freed.
6846 static void free_class_info( ClassInfo* info ) {
6851 clear_class_info( info );
6853 // If it's one of the static instances, just mark it as not in use
6856 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6857 if( info == static_class_info + i ) {
6858 static_class_info[ i ].in_use = 0;
6863 // Otherwise it must have been malloc'd, so free it
6869 @brief Populate an already-allocated ClassInfo.
6870 @param info Pointer to the ClassInfo to be populated.
6871 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6873 @param class Name of the class.
6874 @return Zero if successful, or 1 if not.
6876 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6877 the relevant portions of the IDL for the specified class.
6879 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6882 osrfLogError( OSRF_LOG_MARK,
6883 "%s ERROR: No ClassInfo available to populate", modulename );
6884 info->alias = info->class_name = info->source_def = NULL;
6885 info->class_def = info->fields = info->links = NULL;
6890 osrfLogError( OSRF_LOG_MARK,
6891 "%s ERROR: No class name provided for lookup", modulename );
6892 info->alias = info->class_name = info->source_def = NULL;
6893 info->class_def = info->fields = info->links = NULL;
6897 // Alias defaults to class name if not supplied
6898 if( ! alias || ! alias[ 0 ] )
6901 // Look up class info in the IDL
6902 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6904 osrfLogError( OSRF_LOG_MARK,
6905 "%s ERROR: Class %s not defined in IDL", modulename, class );
6906 info->alias = info->class_name = info->source_def = NULL;
6907 info->class_def = info->fields = info->links = NULL;
6909 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6910 osrfLogError( OSRF_LOG_MARK,
6911 "%s ERROR: Class %s is defined as virtual", modulename, class );
6912 info->alias = info->class_name = info->source_def = NULL;
6913 info->class_def = info->fields = info->links = NULL;
6917 osrfHash* links = osrfHashGet( class_def, "links" );
6919 osrfLogError( OSRF_LOG_MARK,
6920 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6921 info->alias = info->class_name = info->source_def = NULL;
6922 info->class_def = info->fields = info->links = NULL;
6926 osrfHash* fields = osrfHashGet( class_def, "fields" );
6928 osrfLogError( OSRF_LOG_MARK,
6929 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6930 info->alias = info->class_name = info->source_def = NULL;
6931 info->class_def = info->fields = info->links = NULL;
6935 char* source_def = oilsGetRelation( class_def );
6939 // We got everything we need, so populate the ClassInfo
6940 if( strlen( alias ) > ALIAS_STORE_SIZE )
6941 info->alias = strdup( alias );
6943 strcpy( info->alias_store, alias );
6944 info->alias = info->alias_store;
6947 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6948 info->class_name = strdup( class );
6950 strcpy( info->class_name_store, class );
6951 info->class_name = info->class_name_store;
6954 info->source_def = source_def;
6956 info->class_def = class_def;
6957 info->links = links;
6958 info->fields = fields;
6963 #define STATIC_FRAME_COUNT 3
6965 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6968 @brief Allocate a QueryFrame as raw memory.
6969 @return Pointer to the newly allocated QueryFrame.
6971 Except for the in_use flag, which is used only by the allocation and deallocation
6972 logic, we don't initialize the QueryFrame here.
6974 static QueryFrame* allocate_frame( void ) {
6975 // In order to reduce the number of mallocs and frees, we return a static
6976 // instance of QueryFrame, if we can find one that we're not already using.
6977 // We rely on the fact that the compiler will implicitly initialize the
6978 // static instances so that in_use == 0.
6981 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6982 if( ! static_frame[ i ].in_use ) {
6983 static_frame[ i ].in_use = 1;
6984 return static_frame + i;
6988 // The static ones are all in use. Malloc one.
6990 return safe_malloc( sizeof( QueryFrame ) );
6994 @brief Free a QueryFrame, and all the memory it owns.
6995 @param frame Pointer to the QueryFrame to be freed.
6997 static void free_query_frame( QueryFrame* frame ) {
7002 clear_class_info( &frame->core );
7004 // Free the join list
7006 ClassInfo* info = frame->join_list;
7009 free_class_info( info );
7013 frame->join_list = NULL;
7016 // If the frame is a static instance, just mark it as unused
7018 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7019 if( frame == static_frame + i ) {
7020 static_frame[ i ].in_use = 0;
7025 // Otherwise it must have been malloc'd, so free it
7031 @brief Search a given QueryFrame for a specified alias.
7032 @param frame Pointer to the QueryFrame to be searched.
7033 @param target The alias for which to search.
7034 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7036 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7037 if( ! frame || ! target ) {
7041 ClassInfo* found_class = NULL;
7043 if( !strcmp( target, frame->core.alias ) )
7044 return &(frame->core);
7046 ClassInfo* curr_class = frame->join_list;
7047 while( curr_class ) {
7048 if( strcmp( target, curr_class->alias ) )
7049 curr_class = curr_class->next;
7051 found_class = curr_class;
7061 @brief Push a new (blank) QueryFrame onto the stack.
7063 static void push_query_frame( void ) {
7064 QueryFrame* frame = allocate_frame();
7065 frame->join_list = NULL;
7066 frame->next = curr_query;
7068 // Initialize the ClassInfo for the core class
7069 ClassInfo* core = &frame->core;
7070 core->alias = core->class_name = core->source_def = NULL;
7071 core->class_def = core->fields = core->links = NULL;
7077 @brief Pop a QueryFrame off the stack and destroy it.
7079 static void pop_query_frame( void ) {
7084 QueryFrame* popped = curr_query;
7085 curr_query = popped->next;
7087 free_query_frame( popped );
7091 @brief Populate the ClassInfo for the core class.
7092 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7093 class name as an alias.
7094 @param class_name Name of the core class.
7095 @return Zero if successful, or 1 if not.
7097 Populate the ClassInfo of the core class with copies of the alias and class name, and
7098 with pointers to the relevant portions of the IDL for the core class.
7100 static int add_query_core( const char* alias, const char* class_name ) {
7103 if( ! curr_query ) {
7104 osrfLogError( OSRF_LOG_MARK,
7105 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7107 } else if( curr_query->core.alias ) {
7108 osrfLogError( OSRF_LOG_MARK,
7109 "%s ERROR: Core class %s already populated as %s",
7110 modulename, curr_query->core.class_name, curr_query->core.alias );
7114 build_class_info( &curr_query->core, alias, class_name );
7115 if( curr_query->core.alias )
7118 osrfLogError( OSRF_LOG_MARK,
7119 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7125 @brief Search the current QueryFrame for a specified alias.
7126 @param target The alias for which to search.
7127 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7129 static inline ClassInfo* search_alias( const char* target ) {
7130 return search_alias_in_frame( curr_query, target );
7134 @brief Search all levels of query for a specified alias, starting with the current query.
7135 @param target The alias for which to search.
7136 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7138 static ClassInfo* search_all_alias( const char* target ) {
7139 ClassInfo* found_class = NULL;
7140 QueryFrame* curr_frame = curr_query;
7142 while( curr_frame ) {
7143 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7146 curr_frame = curr_frame->next;
7153 @brief Add a class to the list of classes joined to the current query.
7154 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7155 the class name as an alias.
7156 @param classname The name of the class to be added.
7157 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7159 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7161 if( ! classname || ! *classname ) { // sanity check
7162 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7169 const ClassInfo* conflict = search_alias( alias );
7171 osrfLogError( OSRF_LOG_MARK,
7172 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7173 modulename, alias, conflict->class_name );
7177 ClassInfo* info = allocate_class_info();
7179 if( build_class_info( info, alias, classname ) ) {
7180 free_class_info( info );
7184 // Add the new ClassInfo to the join list of the current QueryFrame
7185 info->next = curr_query->join_list;
7186 curr_query->join_list = info;
7192 @brief Destroy all nodes on the query stack.
7194 static void clear_query_stack( void ) {
7200 @brief Implement the set_audit_info method.
7201 @param ctx Pointer to the method context.
7202 @return Zero if successful, or -1 if not.
7204 Issue a SAVEPOINT to the database server.
7209 - workstation id (int)
7211 If user id is not provided the authkey will be used.
7212 For PCRUD the authkey is always used, even if a user is provided.
7214 int setAuditInfo( osrfMethodContext* ctx ) {
7215 if(osrfMethodVerifyContext( ctx )) {
7216 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
7220 // Get the user id from the parameters
7221 const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7223 if( enforce_pcrud || !user_id ) {
7224 timeout_needs_resetting = 1;
7225 const jsonObject* user = verifyUserPCRUD( ctx );
7228 osrfAppRespondComplete( ctx, NULL );
7232 // Not PCRUD and have a user_id?
7233 int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7234 osrfAppRespondComplete( ctx, NULL );
7239 @brief Save a audit info
7240 @param ctx Pointer to the method context.
7241 @param user_id User ID to write as a string
7242 @param ws_id Workstation ID to write as a string
7244 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7245 if( ctx && ctx->session ) {
7246 osrfAppSession* session = ctx->session;
7248 osrfHash* cache = session->userData;
7250 // If the session doesn't already have a hash, create one. Make sure
7251 // that the application session frees the hash when it terminates.
7252 if( NULL == cache ) {
7253 session->userData = cache = osrfNewHash();
7254 osrfHashSetCallback( cache, &sessionDataFree );
7255 ctx->session->userDataFree = &userDataFree;
7258 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7260 osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7262 int errnum = dbi_conn_error( writehandle, &msg );
7265 "%s: Error setting auditor information: %d %s",
7268 msg ? msg : "(No description available)"
7270 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7271 "osrfMethodException", ctx->request, "Error setting auditor info" );
7272 if( !oilsIsDBConnected( writehandle ))
7273 osrfAppSessionPanic( ctx->session );
7276 dbi_result_free( result );
7283 @brief Remove all but safe character from savepoint name
7284 @param sp User-supplied savepoint name
7285 @return sanitized savepoint name, or NULL
7287 The caller is expected to free the returned string. Note that
7288 this function exists only because we can't use PQescapeLiteral
7289 without either forking libdbi or abandoning it.
7291 static char* _sanitize_savepoint_name( const char* sp ) {
7293 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_";
7295 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7296 // and the default value of NAMEDATALEN is 64; that should be long enough
7297 // for our purposes, and it's unlikely that anyone is going to recompile
7298 // PostgreSQL to have a smaller value, so cap the identifier name
7299 // accordingly to avoid the remote chance that someone manages to pass in a
7300 // 12GB savepoint name
7301 const int MAX_LITERAL_NAMELEN = 63;
7304 if (len > MAX_LITERAL_NAMELEN) {
7305 len = MAX_LITERAL_NAMELEN;
7308 char* safeSpName = safe_malloc( len + 1 );
7312 for (j = 0; j < len; j++) {
7313 found = strchr(safe_chars, sp[j]);
7315 safeSpName[ i++ ] = found[0];
7318 safeSpName[ i ] = '\0';