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 const char* right_percent = "";
2660 const char* real_op = op;
2662 if( !strcasecmp( op, "startwith") ) {
2664 right_percent = "|| '%'";
2667 growing_buffer* sql_buf = buffer_init( 32 );
2670 "\"%s\".%s %s %s%s",
2672 osrfHashGet( field, "name" ),
2680 return buffer_release( sql_buf );
2683 // class_alias is a class name or other table alias
2684 // field is a field definition as stored in the IDL
2685 // node comes from the method parameter, and may represent an entry in the SELECT list
2686 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2687 const jsonObject* node ) {
2688 growing_buffer* sql_buf = buffer_init( 32 );
2690 const char* field_transform = jsonObjectGetString(
2691 jsonObjectGetKeyConst( node, "transform" ) );
2692 const char* transform_subcolumn = jsonObjectGetString(
2693 jsonObjectGetKeyConst( node, "result_field" ) );
2695 if( transform_subcolumn ) {
2696 if( ! is_identifier( transform_subcolumn ) ) {
2697 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2698 modulename, transform_subcolumn );
2699 buffer_free( sql_buf );
2702 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2705 if( field_transform ) {
2707 if( ! is_identifier( field_transform ) ) {
2708 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2709 modulename, field_transform );
2710 buffer_free( sql_buf );
2714 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2715 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2716 field_transform, class_alias, osrfHashGet( field, "name" ));
2718 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2719 field_transform, class_alias, osrfHashGet( field, "name" ));
2722 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2725 if( array->type != JSON_ARRAY ) {
2726 osrfLogError( OSRF_LOG_MARK,
2727 "%s: Expected JSON_ARRAY for function params; found %s",
2728 modulename, json_type( array->type ) );
2729 buffer_free( sql_buf );
2732 int func_item_index = 0;
2733 jsonObject* func_item;
2734 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2736 char* val = jsonObjectToSimpleString( func_item );
2739 buffer_add( sql_buf, ",NULL" );
2740 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2741 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2742 OSRF_BUFFER_ADD( sql_buf, val );
2744 osrfLogError( OSRF_LOG_MARK,
2745 "%s: Error quoting key string [%s]", modulename, val );
2747 buffer_free( sql_buf );
2754 buffer_add( sql_buf, " )" );
2757 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2760 if( transform_subcolumn )
2761 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2763 return buffer_release( sql_buf );
2766 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2767 const jsonObject* node, const char* op ) {
2769 if( ! is_good_operator( op ) ) {
2770 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2774 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2775 if( ! field_transform )
2778 int extra_parens = 0; // boolean
2780 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2782 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2784 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2786 free( field_transform );
2790 } else if( value_obj->type == JSON_ARRAY ) {
2791 value = searchValueTransform( value_obj );
2793 osrfLogError( OSRF_LOG_MARK,
2794 "%s: Error building value transform for field transform", modulename );
2795 free( field_transform );
2798 } else if( value_obj->type == JSON_HASH ) {
2799 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2801 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2803 free( field_transform );
2807 } else if( value_obj->type == JSON_NUMBER ) {
2808 value = jsonNumberToDBString( field, value_obj );
2809 } else if( value_obj->type == JSON_NULL ) {
2810 osrfLogError( OSRF_LOG_MARK,
2811 "%s: Error building predicate for field transform: null value", modulename );
2812 free( field_transform );
2814 } else if( value_obj->type == JSON_BOOL ) {
2815 osrfLogError( OSRF_LOG_MARK,
2816 "%s: Error building predicate for field transform: boolean value", modulename );
2817 free( field_transform );
2820 if( !strcmp( get_primitive( field ), "number") ) {
2821 value = jsonNumberToDBString( field, value_obj );
2823 value = jsonObjectToSimpleString( value_obj );
2824 if( !dbi_conn_quote_string( dbhandle, &value )) {
2825 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2826 modulename, value );
2828 free( field_transform );
2834 const char* left_parens = "";
2835 const char* right_parens = "";
2837 if( extra_parens ) {
2842 const char* right_percent = "";
2843 const char* real_op = op;
2845 if( !strcasecmp( op, "startwith") ) {
2847 right_percent = "|| '%'";
2850 growing_buffer* sql_buf = buffer_init( 32 );
2854 "%s%s %s %s %s%s %s%s",
2866 free( field_transform );
2868 return buffer_release( sql_buf );
2871 static char* searchSimplePredicate( const char* op, const char* class_alias,
2872 osrfHash* field, const jsonObject* node ) {
2874 if( ! is_good_operator( op ) ) {
2875 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2881 // Get the value to which we are comparing the specified column
2882 if( node->type != JSON_NULL ) {
2883 if( node->type == JSON_NUMBER ) {
2884 val = jsonNumberToDBString( field, node );
2885 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2886 val = jsonNumberToDBString( field, node );
2888 val = jsonObjectToSimpleString( node );
2893 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2894 // Value is not numeric; enclose it in quotes
2895 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2896 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2903 // Compare to a null value
2904 val = strdup( "NULL" );
2905 if( strcmp( op, "=" ))
2911 const char* right_percent = "";
2912 const char* real_op = op;
2914 if( !strcasecmp( op, "startwith") ) {
2916 right_percent = "|| '%'";
2919 growing_buffer* sql_buf = buffer_init( 32 );
2920 buffer_fadd( sql_buf, "\"%s\".%s %s %s%s", class_alias, osrfHashGet(field, "name"), real_op, val, right_percent );
2921 char* pred = buffer_release( sql_buf );
2928 static char* searchBETWEENPredicate( const char* class_alias,
2929 osrfHash* field, const jsonObject* node ) {
2931 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2932 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2934 if( NULL == y_node ) {
2935 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2938 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2939 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2946 if( !strcmp( get_primitive( field ), "number") ) {
2947 x_string = jsonNumberToDBString( field, x_node );
2948 y_string = jsonNumberToDBString( field, y_node );
2951 x_string = jsonObjectToSimpleString( x_node );
2952 y_string = jsonObjectToSimpleString( y_node );
2953 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2954 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2955 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2956 modulename, x_string, y_string );
2963 growing_buffer* sql_buf = buffer_init( 32 );
2964 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2965 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2969 return buffer_release( sql_buf );
2972 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2973 jsonObject* node, osrfMethodContext* ctx ) {
2976 if( node->type == JSON_ARRAY ) { // equality IN search
2977 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2978 } else if( node->type == JSON_HASH ) { // other search
2979 jsonIterator* pred_itr = jsonNewIterator( node );
2980 if( !jsonIteratorHasNext( pred_itr ) ) {
2981 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2982 modulename, osrfHashGet(field, "name" ));
2984 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2986 // Verify that there are no additional predicates
2987 if( jsonIteratorHasNext( pred_itr ) ) {
2988 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2989 modulename, osrfHashGet(field, "name" ));
2990 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2991 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2992 else if( !(strcasecmp( pred_itr->key,"in" ))
2993 || !(strcasecmp( pred_itr->key,"not in" )) )
2994 pred = searchINPredicate(
2995 class_info->alias, field, pred_node, pred_itr->key, ctx );
2996 else if( pred_node->type == JSON_ARRAY )
2997 pred = searchFunctionPredicate(
2998 class_info->alias, field, pred_node, pred_itr->key );
2999 else if( pred_node->type == JSON_HASH )
3000 pred = searchFieldTransformPredicate(
3001 class_info, field, pred_node, pred_itr->key );
3003 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
3005 jsonIteratorFree( pred_itr );
3007 } else if( node->type == JSON_NULL ) { // IS NULL search
3008 growing_buffer* _p = buffer_init( 64 );
3011 "\"%s\".%s IS NULL",
3013 osrfHashGet( field, "name" )
3015 pred = buffer_release( _p );
3016 } else { // equality search
3017 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3036 field : call_number,
3052 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3054 const jsonObject* working_hash;
3055 jsonObject* freeable_hash = NULL;
3057 if( join_hash->type == JSON_HASH ) {
3058 working_hash = join_hash;
3059 } else if( join_hash->type == JSON_STRING ) {
3060 // turn it into a JSON_HASH by creating a wrapper
3061 // around a copy of the original
3062 const char* _tmp = jsonObjectGetString( join_hash );
3063 freeable_hash = jsonNewObjectType( JSON_HASH );
3064 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3065 working_hash = freeable_hash;
3069 "%s: JOIN failed; expected JSON object type not found",
3075 growing_buffer* join_buf = buffer_init( 128 );
3076 const char* leftclass = left_info->class_name;
3078 jsonObject* snode = NULL;
3079 jsonIterator* search_itr = jsonNewIterator( working_hash );
3081 while ( (snode = jsonIteratorNext( search_itr )) ) {
3082 const char* right_alias = search_itr->key;
3084 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3086 class = right_alias;
3088 const ClassInfo* right_info = add_joined_class( right_alias, class );
3092 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3096 jsonIteratorFree( search_itr );
3097 buffer_free( join_buf );
3099 jsonObjectFree( freeable_hash );
3102 osrfHash* links = right_info->links;
3103 const char* table = right_info->source_def;
3105 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3106 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3108 if( field && !fkey ) {
3109 // Look up the corresponding join column in the IDL.
3110 // The link must be defined in the child table,
3111 // and point to the right parent table.
3112 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3113 const char* reltype = NULL;
3114 const char* other_class = NULL;
3115 reltype = osrfHashGet( idl_link, "reltype" );
3116 if( reltype && strcmp( reltype, "has_many" ) )
3117 other_class = osrfHashGet( idl_link, "class" );
3118 if( other_class && !strcmp( other_class, leftclass ) )
3119 fkey = osrfHashGet( idl_link, "key" );
3123 "%s: JOIN failed. No link defined from %s.%s to %s",
3129 buffer_free( join_buf );
3131 jsonObjectFree( freeable_hash );
3132 jsonIteratorFree( search_itr );
3136 } else if( !field && fkey ) {
3137 // Look up the corresponding join column in the IDL.
3138 // The link must be defined in the child table,
3139 // and point to the right parent table.
3140 osrfHash* left_links = left_info->links;
3141 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3142 const char* reltype = NULL;
3143 const char* other_class = NULL;
3144 reltype = osrfHashGet( idl_link, "reltype" );
3145 if( reltype && strcmp( reltype, "has_many" ) )
3146 other_class = osrfHashGet( idl_link, "class" );
3147 if( other_class && !strcmp( other_class, class ) )
3148 field = osrfHashGet( idl_link, "key" );
3152 "%s: JOIN failed. No link defined from %s.%s to %s",
3158 buffer_free( join_buf );
3160 jsonObjectFree( freeable_hash );
3161 jsonIteratorFree( search_itr );
3165 } else if( !field && !fkey ) {
3166 osrfHash* left_links = left_info->links;
3168 // For each link defined for the left class:
3169 // see if the link references the joined class
3170 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3171 osrfHash* curr_link = NULL;
3172 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3173 const char* other_class = osrfHashGet( curr_link, "class" );
3174 if( other_class && !strcmp( other_class, class ) ) {
3176 // In the IDL, the parent class doesn't always know then names of the child
3177 // columns that are pointing to it, so don't use that end of the link
3178 const char* reltype = osrfHashGet( curr_link, "reltype" );
3179 if( reltype && strcmp( reltype, "has_many" ) ) {
3180 // Found a link between the classes
3181 fkey = osrfHashIteratorKey( itr );
3182 field = osrfHashGet( curr_link, "key" );
3187 osrfHashIteratorFree( itr );
3189 if( !field || !fkey ) {
3190 // Do another such search, with the classes reversed
3192 // For each link defined for the joined class:
3193 // see if the link references the left class
3194 osrfHashIterator* itr = osrfNewHashIterator( links );
3195 osrfHash* curr_link = NULL;
3196 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3197 const char* other_class = osrfHashGet( curr_link, "class" );
3198 if( other_class && !strcmp( other_class, leftclass ) ) {
3200 // In the IDL, the parent class doesn't know then names of the child
3201 // columns that are pointing to it, so don't use that end of the link
3202 const char* reltype = osrfHashGet( curr_link, "reltype" );
3203 if( reltype && strcmp( reltype, "has_many" ) ) {
3204 // Found a link between the classes
3205 field = osrfHashIteratorKey( itr );
3206 fkey = osrfHashGet( curr_link, "key" );
3211 osrfHashIteratorFree( itr );
3214 if( !field || !fkey ) {
3217 "%s: JOIN failed. No link defined between %s and %s",
3222 buffer_free( join_buf );
3224 jsonObjectFree( freeable_hash );
3225 jsonIteratorFree( search_itr );
3230 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3232 if( !strcasecmp( type,"left" )) {
3233 buffer_add( join_buf, " LEFT JOIN" );
3234 } else if( !strcasecmp( type,"right" )) {
3235 buffer_add( join_buf, " RIGHT JOIN" );
3236 } else if( !strcasecmp( type,"full" )) {
3237 buffer_add( join_buf, " FULL JOIN" );
3239 buffer_add( join_buf, " INNER JOIN" );
3242 buffer_add( join_buf, " INNER JOIN" );
3245 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3246 table, right_alias, right_alias, field, left_info->alias, fkey );
3248 // Add any other join conditions as specified by "filter"
3249 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3251 const char* filter_op = jsonObjectGetString(
3252 jsonObjectGetKeyConst( snode, "filter_op" ) );
3253 if( filter_op && !strcasecmp( "or",filter_op )) {
3254 buffer_add( join_buf, " OR " );
3256 buffer_add( join_buf, " AND " );
3259 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3261 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3262 OSRF_BUFFER_ADD( join_buf, jpred );
3267 "%s: JOIN failed. Invalid conditional expression.",
3270 jsonIteratorFree( search_itr );
3271 buffer_free( join_buf );
3273 jsonObjectFree( freeable_hash );
3278 buffer_add( join_buf, " ) " );
3280 // Recursively add a nested join, if one is present
3281 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3283 char* jpred = searchJOIN( join_filter, right_info );
3285 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3286 OSRF_BUFFER_ADD( join_buf, jpred );
3289 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3290 jsonIteratorFree( search_itr );
3291 buffer_free( join_buf );
3293 jsonObjectFree( freeable_hash );
3300 jsonObjectFree( freeable_hash );
3301 jsonIteratorFree( search_itr );
3303 return buffer_release( join_buf );
3308 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3309 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3310 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3312 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3314 search_hash is the JSON expression of the conditions.
3315 meta is the class definition from the IDL, for the relevant table.
3316 opjoin_type indicates whether multiple conditions, if present, should be
3317 connected by AND or OR.
3318 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3319 to pass it to other functions -- and all they do with it is to use the session
3320 and request members to send error messages back to the client.
3324 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3325 int opjoin_type, osrfMethodContext* ctx ) {
3329 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3330 "opjoin_type = %d, ctx addr = %p",
3333 class_info->class_def,
3338 growing_buffer* sql_buf = buffer_init( 128 );
3340 jsonObject* node = NULL;
3343 if( search_hash->type == JSON_ARRAY ) {
3344 if( 0 == search_hash->size ) {
3347 "%s: Invalid predicate structure: empty JSON array",
3350 buffer_free( sql_buf );
3354 unsigned long i = 0;
3355 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3359 if( opjoin_type == OR_OP_JOIN )
3360 buffer_add( sql_buf, " OR " );
3362 buffer_add( sql_buf, " AND " );
3365 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3367 buffer_free( sql_buf );
3371 buffer_fadd( sql_buf, "( %s )", subpred );
3375 } else if( search_hash->type == JSON_HASH ) {
3376 osrfLogDebug( OSRF_LOG_MARK,
3377 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3378 jsonIterator* search_itr = jsonNewIterator( search_hash );
3379 if( !jsonIteratorHasNext( search_itr ) ) {
3382 "%s: Invalid predicate structure: empty JSON object",
3385 jsonIteratorFree( search_itr );
3386 buffer_free( sql_buf );
3390 while( (node = jsonIteratorNext( search_itr )) ) {
3395 if( opjoin_type == OR_OP_JOIN )
3396 buffer_add( sql_buf, " OR " );
3398 buffer_add( sql_buf, " AND " );
3401 if( '+' == search_itr->key[ 0 ] ) {
3403 // This plus sign prefixes a class name or other table alias;
3404 // make sure the table alias is in scope
3405 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3406 if( ! alias_info ) {
3409 "%s: Invalid table alias \"%s\" in WHERE clause",
3413 jsonIteratorFree( search_itr );
3414 buffer_free( sql_buf );
3418 if( node->type == JSON_STRING ) {
3419 // It's the name of a column; make sure it belongs to the class
3420 const char* fieldname = jsonObjectGetString( node );
3421 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3424 "%s: Invalid column name \"%s\" in WHERE clause "
3425 "for table alias \"%s\"",
3430 jsonIteratorFree( search_itr );
3431 buffer_free( sql_buf );
3435 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3437 // It's something more complicated
3438 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3440 jsonIteratorFree( search_itr );
3441 buffer_free( sql_buf );
3445 buffer_fadd( sql_buf, "( %s )", subpred );
3448 } else if( '-' == search_itr->key[ 0 ] ) {
3449 if( !strcasecmp( "-or", search_itr->key )) {
3450 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3452 jsonIteratorFree( search_itr );
3453 buffer_free( sql_buf );
3457 buffer_fadd( sql_buf, "( %s )", subpred );
3459 } else if( !strcasecmp( "-and", search_itr->key )) {
3460 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3462 jsonIteratorFree( search_itr );
3463 buffer_free( sql_buf );
3467 buffer_fadd( sql_buf, "( %s )", subpred );
3469 } else if( !strcasecmp("-not",search_itr->key) ) {
3470 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3472 jsonIteratorFree( search_itr );
3473 buffer_free( sql_buf );
3477 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3479 } else if( !strcasecmp( "-exists", search_itr->key )) {
3480 char* subpred = buildQuery( ctx, node, SUBSELECT );
3482 jsonIteratorFree( search_itr );
3483 buffer_free( sql_buf );
3487 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3489 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3490 char* subpred = buildQuery( ctx, node, SUBSELECT );
3492 jsonIteratorFree( search_itr );
3493 buffer_free( sql_buf );
3497 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3499 } else { // Invalid "minus" operator
3502 "%s: Invalid operator \"%s\" in WHERE clause",
3506 jsonIteratorFree( search_itr );
3507 buffer_free( sql_buf );
3513 const char* class = class_info->class_name;
3514 osrfHash* fields = class_info->fields;
3515 osrfHash* field = osrfHashGet( fields, search_itr->key );
3518 const char* table = class_info->source_def;
3521 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3524 table ? table : "?",
3527 jsonIteratorFree( search_itr );
3528 buffer_free( sql_buf );
3532 char* subpred = searchPredicate( class_info, field, node, ctx );
3534 buffer_free( sql_buf );
3535 jsonIteratorFree( search_itr );
3539 buffer_add( sql_buf, subpred );
3543 jsonIteratorFree( search_itr );
3546 // ERROR ... only hash and array allowed at this level
3547 char* predicate_string = jsonObjectToJSON( search_hash );
3550 "%s: Invalid predicate structure: %s",
3554 buffer_free( sql_buf );
3555 free( predicate_string );
3559 return buffer_release( sql_buf );
3562 /* Build a JSON_ARRAY of field names for a given table alias
3564 static jsonObject* defaultSelectList( const char* table_alias ) {
3569 ClassInfo* class_info = search_all_alias( table_alias );
3570 if( ! class_info ) {
3573 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3580 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3581 osrfHash* field_def = NULL;
3582 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3583 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3584 const char* field_name = osrfHashIteratorKey( field_itr );
3585 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3586 jsonObjectPush( array, jsonNewObject( field_name ) );
3589 osrfHashIteratorFree( field_itr );
3594 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3595 // The jsonObject must be a JSON_HASH with an single entry for "union",
3596 // "intersect", or "except". The data associated with this key must be an
3597 // array of hashes, each hash being a query.
3598 // Also allowed but currently ignored: entries for "order_by" and "alias".
3599 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3601 if( ! combo || combo->type != JSON_HASH )
3602 return NULL; // should be impossible; validated by caller
3604 const jsonObject* query_array = NULL; // array of subordinate queries
3605 const char* op = NULL; // name of operator, e.g. UNION
3606 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3607 int op_count = 0; // for detecting conflicting operators
3608 int excepting = 0; // boolean
3609 int all = 0; // boolean
3610 jsonObject* order_obj = NULL;
3612 // Identify the elements in the hash
3613 jsonIterator* query_itr = jsonNewIterator( combo );
3614 jsonObject* curr_obj = NULL;
3615 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3616 if( ! strcmp( "union", query_itr->key ) ) {
3619 query_array = curr_obj;
3620 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3623 query_array = curr_obj;
3624 } else if( ! strcmp( "except", query_itr->key ) ) {
3628 query_array = curr_obj;
3629 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3632 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3635 order_obj = curr_obj;
3636 } else if( ! strcmp( "alias", query_itr->key ) ) {
3637 if( curr_obj->type != JSON_STRING ) {
3638 jsonIteratorFree( query_itr );
3641 alias = jsonObjectGetString( curr_obj );
3642 } else if( ! strcmp( "all", query_itr->key ) ) {
3643 if( obj_is_true( curr_obj ) )
3647 osrfAppSessionStatus(
3649 OSRF_STATUS_INTERNALSERVERERROR,
3650 "osrfMethodException",
3652 "Malformed query; unexpected entry in query object"
3656 "%s: Unexpected entry for \"%s\" in%squery",
3661 jsonIteratorFree( query_itr );
3665 jsonIteratorFree( query_itr );
3667 // More sanity checks
3668 if( ! query_array ) {
3670 osrfAppSessionStatus(
3672 OSRF_STATUS_INTERNALSERVERERROR,
3673 "osrfMethodException",
3675 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3679 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3682 return NULL; // should be impossible...
3683 } else if( op_count > 1 ) {
3685 osrfAppSessionStatus(
3687 OSRF_STATUS_INTERNALSERVERERROR,
3688 "osrfMethodException",
3690 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3694 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3698 } if( query_array->type != JSON_ARRAY ) {
3700 osrfAppSessionStatus(
3702 OSRF_STATUS_INTERNALSERVERERROR,
3703 "osrfMethodException",
3705 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3709 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3712 json_type( query_array->type )
3715 } if( query_array->size < 2 ) {
3717 osrfAppSessionStatus(
3719 OSRF_STATUS_INTERNALSERVERERROR,
3720 "osrfMethodException",
3722 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3726 "%s:%srequires multiple queries as operands",
3731 } else if( excepting && query_array->size > 2 ) {
3733 osrfAppSessionStatus(
3735 OSRF_STATUS_INTERNALSERVERERROR,
3736 "osrfMethodException",
3738 "EXCEPT operator has too many queries as operands"
3742 "%s:EXCEPT operator has too many queries as operands",
3746 } else if( order_obj && ! alias ) {
3748 osrfAppSessionStatus(
3750 OSRF_STATUS_INTERNALSERVERERROR,
3751 "osrfMethodException",
3753 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3757 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3763 // So far so good. Now build the SQL.
3764 growing_buffer* sql = buffer_init( 256 );
3766 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3767 // Add a layer of parentheses
3768 if( flags & SUBCOMBO )
3769 OSRF_BUFFER_ADD( sql, "( " );
3771 // Traverse the query array. Each entry should be a hash.
3772 int first = 1; // boolean
3774 jsonObject* query = NULL;
3775 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3776 if( query->type != JSON_HASH ) {
3778 osrfAppSessionStatus(
3780 OSRF_STATUS_INTERNALSERVERERROR,
3781 "osrfMethodException",
3783 "Malformed query under UNION, INTERSECT or EXCEPT"
3787 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3790 json_type( query->type )
3799 OSRF_BUFFER_ADD( sql, op );
3801 OSRF_BUFFER_ADD( sql, "ALL " );
3804 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3808 "%s: Error building query under%s",
3816 OSRF_BUFFER_ADD( sql, query_str );
3819 if( flags & SUBCOMBO )
3820 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3822 if( !(flags & SUBSELECT) )
3823 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3825 return buffer_release( sql );
3828 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3829 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3830 // or "except" to indicate the type of query.
3831 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3835 osrfAppSessionStatus(
3837 OSRF_STATUS_INTERNALSERVERERROR,
3838 "osrfMethodException",
3840 "Malformed query; no query object"
3842 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3844 } else if( query->type != JSON_HASH ) {
3846 osrfAppSessionStatus(
3848 OSRF_STATUS_INTERNALSERVERERROR,
3849 "osrfMethodException",
3851 "Malformed query object"
3855 "%s: Query object is %s instead of JSON_HASH",
3857 json_type( query->type )
3862 // Determine what kind of query it purports to be, and dispatch accordingly.
3863 if( jsonObjectGetKeyConst( query, "union" ) ||
3864 jsonObjectGetKeyConst( query, "intersect" ) ||
3865 jsonObjectGetKeyConst( query, "except" )) {
3866 return doCombo( ctx, query, flags );
3868 // It is presumably a SELECT query
3870 // Push a node onto the stack for the current query. Every level of
3871 // subquery gets its own QueryFrame on the Stack.
3874 // Build an SQL SELECT statement
3877 jsonObjectGetKey( query, "select" ),
3878 jsonObjectGetKeyConst( query, "from" ),
3879 jsonObjectGetKeyConst( query, "where" ),
3880 jsonObjectGetKeyConst( query, "having" ),
3881 jsonObjectGetKeyConst( query, "order_by" ),
3882 jsonObjectGetKeyConst( query, "limit" ),
3883 jsonObjectGetKeyConst( query, "offset" ),
3892 /* method context */ osrfMethodContext* ctx,
3894 /* SELECT */ jsonObject* selhash,
3895 /* FROM */ const jsonObject* join_hash,
3896 /* WHERE */ const jsonObject* search_hash,
3897 /* HAVING */ const jsonObject* having_hash,
3898 /* ORDER BY */ const jsonObject* order_hash,
3899 /* LIMIT */ const jsonObject* limit,
3900 /* OFFSET */ const jsonObject* offset,
3901 /* flags */ int flags
3903 const char* locale = osrf_message_get_last_locale();
3905 // general tmp objects
3906 const jsonObject* tmp_const;
3907 jsonObject* selclass = NULL;
3908 jsonObject* snode = NULL;
3909 jsonObject* onode = NULL;
3911 char* string = NULL;
3912 int from_function = 0;
3917 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3919 // punt if there's no FROM clause
3920 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3923 "%s: FROM clause is missing or empty",
3927 osrfAppSessionStatus(
3929 OSRF_STATUS_INTERNALSERVERERROR,
3930 "osrfMethodException",
3932 "FROM clause is missing or empty in JSON query"
3937 // the core search class
3938 const char* core_class = NULL;
3940 // get the core class -- the only key of the top level FROM clause, or a string
3941 if( join_hash->type == JSON_HASH ) {
3942 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3943 snode = jsonIteratorNext( tmp_itr );
3945 // Populate the current QueryFrame with information
3946 // about the core class
3947 if( add_query_core( NULL, tmp_itr->key ) ) {
3949 osrfAppSessionStatus(
3951 OSRF_STATUS_INTERNALSERVERERROR,
3952 "osrfMethodException",
3954 "Unable to look up core class"
3958 core_class = curr_query->core.class_name;
3961 jsonObject* extra = jsonIteratorNext( tmp_itr );
3963 jsonIteratorFree( tmp_itr );
3966 // There shouldn't be more than one entry in join_hash
3970 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3974 osrfAppSessionStatus(
3976 OSRF_STATUS_INTERNALSERVERERROR,
3977 "osrfMethodException",
3979 "Malformed FROM clause in JSON query"
3981 return NULL; // Malformed join_hash; extra entry
3983 } else if( join_hash->type == JSON_ARRAY ) {
3984 // We're selecting from a function, not from a table
3986 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3989 } else if( join_hash->type == JSON_STRING ) {
3990 // Populate the current QueryFrame with information
3991 // about the core class
3992 core_class = jsonObjectGetString( join_hash );
3994 if( add_query_core( NULL, core_class ) ) {
3996 osrfAppSessionStatus(
3998 OSRF_STATUS_INTERNALSERVERERROR,
3999 "osrfMethodException",
4001 "Unable to look up core class"
4009 "%s: FROM clause is unexpected JSON type: %s",
4011 json_type( join_hash->type )
4014 osrfAppSessionStatus(
4016 OSRF_STATUS_INTERNALSERVERERROR,
4017 "osrfMethodException",
4019 "Ill-formed FROM clause in JSON query"
4024 // Build the join clause, if any, while filling out the list
4025 // of joined classes in the current QueryFrame.
4026 char* join_clause = NULL;
4027 if( join_hash && ! from_function ) {
4029 join_clause = searchJOIN( join_hash, &curr_query->core );
4030 if( ! join_clause ) {
4032 osrfAppSessionStatus(
4034 OSRF_STATUS_INTERNALSERVERERROR,
4035 "osrfMethodException",
4037 "Unable to construct JOIN clause(s)"
4043 // For in case we don't get a select list
4044 jsonObject* defaultselhash = NULL;
4046 // if there is no select list, build a default select list ...
4047 if( !selhash && !from_function ) {
4048 jsonObject* default_list = defaultSelectList( core_class );
4049 if( ! default_list ) {
4051 osrfAppSessionStatus(
4053 OSRF_STATUS_INTERNALSERVERERROR,
4054 "osrfMethodException",
4056 "Unable to build default SELECT clause in JSON query"
4058 free( join_clause );
4063 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4064 jsonObjectSetKey( selhash, core_class, default_list );
4067 // The SELECT clause can be encoded only by a hash
4068 if( !from_function && selhash->type != JSON_HASH ) {
4071 "%s: Expected JSON_HASH for SELECT clause; found %s",
4073 json_type( selhash->type )
4077 osrfAppSessionStatus(
4079 OSRF_STATUS_INTERNALSERVERERROR,
4080 "osrfMethodException",
4082 "Malformed SELECT clause in JSON query"
4084 free( join_clause );
4088 // If you see a null or wild card specifier for the core class, or an
4089 // empty array, replace it with a default SELECT list
4090 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4092 int default_needed = 0; // boolean
4093 if( JSON_STRING == tmp_const->type
4094 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4096 else if( JSON_NULL == tmp_const->type )
4099 if( default_needed ) {
4100 // Build a default SELECT list
4101 jsonObject* default_list = defaultSelectList( core_class );
4102 if( ! default_list ) {
4104 osrfAppSessionStatus(
4106 OSRF_STATUS_INTERNALSERVERERROR,
4107 "osrfMethodException",
4109 "Can't build default SELECT clause in JSON query"
4111 free( join_clause );
4116 jsonObjectSetKey( selhash, core_class, default_list );
4120 // temp buffers for the SELECT list and GROUP BY clause
4121 growing_buffer* select_buf = buffer_init( 128 );
4122 growing_buffer* group_buf = buffer_init( 128 );
4124 int aggregate_found = 0; // boolean
4126 // Build a select list
4127 if( from_function ) // From a function we select everything
4128 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4131 // Build the SELECT list as SQL
4135 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4136 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4138 const char* cname = selclass_itr->key;
4140 // Make sure the target relation is in the FROM clause.
4142 // At this point join_hash is a step down from the join_hash we
4143 // received as a parameter. If the original was a JSON_STRING,
4144 // then json_hash is now NULL. If the original was a JSON_HASH,
4145 // then json_hash is now the first (and only) entry in it,
4146 // denoting the core class. We've already excluded the
4147 // possibility that the original was a JSON_ARRAY, because in
4148 // that case from_function would be non-NULL, and we wouldn't
4151 // If the current table alias isn't in scope, bail out
4152 ClassInfo* class_info = search_alias( cname );
4153 if( ! class_info ) {
4156 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4161 osrfAppSessionStatus(
4163 OSRF_STATUS_INTERNALSERVERERROR,
4164 "osrfMethodException",
4166 "Selected class not in FROM clause in JSON query"
4168 jsonIteratorFree( selclass_itr );
4169 buffer_free( select_buf );
4170 buffer_free( group_buf );
4171 if( defaultselhash )
4172 jsonObjectFree( defaultselhash );
4173 free( join_clause );
4177 if( selclass->type != JSON_ARRAY ) {
4180 "%s: Malformed SELECT list for class \"%s\"; not an array",
4185 osrfAppSessionStatus(
4187 OSRF_STATUS_INTERNALSERVERERROR,
4188 "osrfMethodException",
4190 "Selected class not in FROM clause in JSON query"
4193 jsonIteratorFree( selclass_itr );
4194 buffer_free( select_buf );
4195 buffer_free( group_buf );
4196 if( defaultselhash )
4197 jsonObjectFree( defaultselhash );
4198 free( join_clause );
4202 // Look up some attributes of the current class
4203 osrfHash* idlClass = class_info->class_def;
4204 osrfHash* class_field_set = class_info->fields;
4205 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4206 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4208 if( 0 == selclass->size ) {
4211 "%s: No columns selected from \"%s\"",
4217 // stitch together the column list for the current table alias...
4218 unsigned long field_idx = 0;
4219 jsonObject* selfield = NULL;
4220 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4222 // If we need a separator comma, add one
4226 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4229 // if the field specification is a string, add it to the list
4230 if( selfield->type == JSON_STRING ) {
4232 // Look up the field in the IDL
4233 const char* col_name = jsonObjectGetString( selfield );
4234 osrfHash* field_def;
4236 if (!osrfStringArrayContains(
4238 osrfHashGet( class_field_set, col_name ),
4239 "suppress_controller"),
4242 field_def = osrfHashGet( class_field_set, col_name );
4245 // No such field in current class
4248 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4254 osrfAppSessionStatus(
4256 OSRF_STATUS_INTERNALSERVERERROR,
4257 "osrfMethodException",
4259 "Selected column not defined in JSON query"
4261 jsonIteratorFree( selclass_itr );
4262 buffer_free( select_buf );
4263 buffer_free( group_buf );
4264 if( defaultselhash )
4265 jsonObjectFree( defaultselhash );
4266 free( join_clause );
4268 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4269 // Virtual field not allowed
4272 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4278 osrfAppSessionStatus(
4280 OSRF_STATUS_INTERNALSERVERERROR,
4281 "osrfMethodException",
4283 "Selected column may not be virtual in JSON query"
4285 jsonIteratorFree( selclass_itr );
4286 buffer_free( select_buf );
4287 buffer_free( group_buf );
4288 if( defaultselhash )
4289 jsonObjectFree( defaultselhash );
4290 free( join_clause );
4296 if( flags & DISABLE_I18N )
4299 i18n = osrfHashGet( field_def, "i18n" );
4301 if( str_is_true( i18n ) ) {
4302 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4303 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4304 class_tname, cname, col_name, class_pkey,
4305 cname, class_pkey, locale, col_name );
4307 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4308 cname, col_name, col_name );
4311 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4312 cname, col_name, col_name );
4315 // ... but it could be an object, in which case we check for a Field Transform
4316 } else if( selfield->type == JSON_HASH ) {
4318 const char* col_name = jsonObjectGetString(
4319 jsonObjectGetKeyConst( selfield, "column" ) );
4321 // Get the field definition from the IDL
4322 osrfHash* field_def;
4323 if (!osrfStringArrayContains(
4325 osrfHashGet( class_field_set, col_name ),
4326 "suppress_controller"),
4329 field_def = osrfHashGet( class_field_set, col_name );
4333 // No such field in current class
4336 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4342 osrfAppSessionStatus(
4344 OSRF_STATUS_INTERNALSERVERERROR,
4345 "osrfMethodException",
4347 "Selected column is not defined in JSON query"
4349 jsonIteratorFree( selclass_itr );
4350 buffer_free( select_buf );
4351 buffer_free( group_buf );
4352 if( defaultselhash )
4353 jsonObjectFree( defaultselhash );
4354 free( join_clause );
4356 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4357 // No such field in current class
4360 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4366 osrfAppSessionStatus(
4368 OSRF_STATUS_INTERNALSERVERERROR,
4369 "osrfMethodException",
4371 "Selected column is virtual in JSON query"
4373 jsonIteratorFree( selclass_itr );
4374 buffer_free( select_buf );
4375 buffer_free( group_buf );
4376 if( defaultselhash )
4377 jsonObjectFree( defaultselhash );
4378 free( join_clause );
4382 // Decide what to use as a column alias
4384 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4385 _alias = jsonObjectGetString( tmp_const );
4386 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4387 _alias = jsonObjectGetString( tmp_const );
4388 } else { // Use field name as the alias
4392 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4393 char* transform_str = searchFieldTransform(
4394 class_info->alias, field_def, selfield );
4395 if( transform_str ) {
4396 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4397 free( transform_str );
4400 osrfAppSessionStatus(
4402 OSRF_STATUS_INTERNALSERVERERROR,
4403 "osrfMethodException",
4405 "Unable to generate transform function in JSON query"
4407 jsonIteratorFree( selclass_itr );
4408 buffer_free( select_buf );
4409 buffer_free( group_buf );
4410 if( defaultselhash )
4411 jsonObjectFree( defaultselhash );
4412 free( join_clause );
4419 if( flags & DISABLE_I18N )
4422 i18n = osrfHashGet( field_def, "i18n" );
4424 if( str_is_true( i18n ) ) {
4425 buffer_fadd( select_buf,
4426 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4427 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4428 class_tname, cname, col_name, class_pkey, cname,
4429 class_pkey, locale, _alias );
4431 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4432 cname, col_name, _alias );
4435 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4436 cname, col_name, _alias );
4443 "%s: Selected item is unexpected JSON type: %s",
4445 json_type( selfield->type )
4448 osrfAppSessionStatus(
4450 OSRF_STATUS_INTERNALSERVERERROR,
4451 "osrfMethodException",
4453 "Ill-formed SELECT item in JSON query"
4455 jsonIteratorFree( selclass_itr );
4456 buffer_free( select_buf );
4457 buffer_free( group_buf );
4458 if( defaultselhash )
4459 jsonObjectFree( defaultselhash );
4460 free( join_clause );
4464 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4465 if( obj_is_true( agg_obj ) )
4466 aggregate_found = 1;
4468 // Append a comma (except for the first one)
4469 // and add the column to a GROUP BY clause
4473 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4475 buffer_fadd( group_buf, " %d", sel_pos );
4479 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4481 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( selfield, "aggregate");
4482 if ( ! obj_is_true( aggregate_obj ) ) {
4486 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4489 buffer_fadd(group_buf, " %d", sel_pos);
4492 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4496 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4499 _column = searchFieldTransform(class_info->alias, field, selfield);
4500 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4501 OSRF_BUFFER_ADD(group_buf, _column);
4502 _column = searchFieldTransform(class_info->alias, field, selfield);
4509 } // end while -- iterating across SELECT columns
4511 } // end while -- iterating across classes
4513 jsonIteratorFree( selclass_itr );
4516 char* col_list = buffer_release( select_buf );
4518 // Make sure the SELECT list isn't empty. This can happen, for example,
4519 // if we try to build a default SELECT clause from a non-core table.
4522 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4524 osrfAppSessionStatus(
4526 OSRF_STATUS_INTERNALSERVERERROR,
4527 "osrfMethodException",
4529 "SELECT list is empty"
4532 buffer_free( group_buf );
4533 if( defaultselhash )
4534 jsonObjectFree( defaultselhash );
4535 free( join_clause );
4541 table = searchValueTransform( join_hash );
4543 table = strdup( curr_query->core.source_def );
4547 osrfAppSessionStatus(
4549 OSRF_STATUS_INTERNALSERVERERROR,
4550 "osrfMethodException",
4552 "Unable to identify table for core class"
4555 buffer_free( group_buf );
4556 if( defaultselhash )
4557 jsonObjectFree( defaultselhash );
4558 free( join_clause );
4562 // Put it all together
4563 growing_buffer* sql_buf = buffer_init( 128 );
4564 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4568 // Append the join clause, if any
4570 buffer_add(sql_buf, join_clause );
4571 free( join_clause );
4574 char* order_by_list = NULL;
4575 char* having_buf = NULL;
4577 if( !from_function ) {
4579 // Build a WHERE clause, if there is one
4581 buffer_add( sql_buf, " WHERE " );
4583 // and it's on the WHERE clause
4584 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4587 osrfAppSessionStatus(
4589 OSRF_STATUS_INTERNALSERVERERROR,
4590 "osrfMethodException",
4592 "Severe query error in WHERE predicate -- see error log for more details"
4595 buffer_free( group_buf );
4596 buffer_free( sql_buf );
4597 if( defaultselhash )
4598 jsonObjectFree( defaultselhash );
4602 buffer_add( sql_buf, pred );
4606 // Build a HAVING clause, if there is one
4609 // and it's on the the WHERE clause
4610 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4612 if( ! having_buf ) {
4614 osrfAppSessionStatus(
4616 OSRF_STATUS_INTERNALSERVERERROR,
4617 "osrfMethodException",
4619 "Severe query error in HAVING predicate -- see error log for more details"
4622 buffer_free( group_buf );
4623 buffer_free( sql_buf );
4624 if( defaultselhash )
4625 jsonObjectFree( defaultselhash );
4630 // Build an ORDER BY clause, if there is one
4631 if( NULL == order_hash )
4632 ; // No ORDER BY? do nothing
4633 else if( JSON_ARRAY == order_hash->type ) {
4634 order_by_list = buildOrderByFromArray( ctx, order_hash );
4635 if( !order_by_list ) {
4637 buffer_free( group_buf );
4638 buffer_free( sql_buf );
4639 if( defaultselhash )
4640 jsonObjectFree( defaultselhash );
4643 } else if( JSON_HASH == order_hash->type ) {
4644 // This hash is keyed on class alias. Each class has either
4645 // an array of field names or a hash keyed on field name.
4646 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4647 jsonIterator* class_itr = jsonNewIterator( order_hash );
4648 while( (snode = jsonIteratorNext( class_itr )) ) {
4650 ClassInfo* order_class_info = search_alias( class_itr->key );
4651 if( ! order_class_info ) {
4652 osrfLogError( OSRF_LOG_MARK,
4653 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4654 modulename, class_itr->key );
4656 osrfAppSessionStatus(
4658 OSRF_STATUS_INTERNALSERVERERROR,
4659 "osrfMethodException",
4661 "Invalid class referenced in ORDER BY clause -- "
4662 "see error log for more details"
4664 jsonIteratorFree( class_itr );
4665 buffer_free( order_buf );
4667 buffer_free( group_buf );
4668 buffer_free( sql_buf );
4669 if( defaultselhash )
4670 jsonObjectFree( defaultselhash );
4674 osrfHash* field_list_def = order_class_info->fields;
4676 if( snode->type == JSON_HASH ) {
4678 // Hash is keyed on field names from the current class. For each field
4679 // there is another layer of hash to define the sorting details, if any,
4680 // or a string to indicate direction of sorting.
4681 jsonIterator* order_itr = jsonNewIterator( snode );
4682 while( (onode = jsonIteratorNext( order_itr )) ) {
4684 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4686 osrfLogError( OSRF_LOG_MARK,
4687 "%s: Invalid field \"%s\" in ORDER BY clause",
4688 modulename, order_itr->key );
4690 osrfAppSessionStatus(
4692 OSRF_STATUS_INTERNALSERVERERROR,
4693 "osrfMethodException",
4695 "Invalid field in ORDER BY clause -- "
4696 "see error log for more details"
4698 jsonIteratorFree( order_itr );
4699 jsonIteratorFree( class_itr );
4700 buffer_free( order_buf );
4702 buffer_free( group_buf );
4703 buffer_free( sql_buf );
4704 if( defaultselhash )
4705 jsonObjectFree( defaultselhash );
4707 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4708 osrfLogError( OSRF_LOG_MARK,
4709 "%s: Virtual field \"%s\" in ORDER BY clause",
4710 modulename, order_itr->key );
4712 osrfAppSessionStatus(
4714 OSRF_STATUS_INTERNALSERVERERROR,
4715 "osrfMethodException",
4717 "Virtual field in ORDER BY clause -- "
4718 "see error log for more details"
4720 jsonIteratorFree( order_itr );
4721 jsonIteratorFree( class_itr );
4722 buffer_free( order_buf );
4724 buffer_free( group_buf );
4725 buffer_free( sql_buf );
4726 if( defaultselhash )
4727 jsonObjectFree( defaultselhash );
4731 const char* direction = NULL;
4732 if( onode->type == JSON_HASH ) {
4733 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4734 string = searchFieldTransform(
4736 osrfHashGet( field_list_def, order_itr->key ),
4740 if( ctx ) osrfAppSessionStatus(
4742 OSRF_STATUS_INTERNALSERVERERROR,
4743 "osrfMethodException",
4745 "Severe query error in ORDER BY clause -- "
4746 "see error log for more details"
4748 jsonIteratorFree( order_itr );
4749 jsonIteratorFree( class_itr );
4751 buffer_free( group_buf );
4752 buffer_free( order_buf);
4753 buffer_free( sql_buf );
4754 if( defaultselhash )
4755 jsonObjectFree( defaultselhash );
4759 growing_buffer* field_buf = buffer_init( 16 );
4760 buffer_fadd( field_buf, "\"%s\".%s",
4761 class_itr->key, order_itr->key );
4762 string = buffer_release( field_buf );
4765 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4766 const char* dir = jsonObjectGetString( tmp_const );
4767 if(!strncasecmp( dir, "d", 1 )) {
4768 direction = " DESC";
4774 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4775 osrfLogError( OSRF_LOG_MARK,
4776 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4777 modulename, json_type( onode->type ) );
4779 osrfAppSessionStatus(
4781 OSRF_STATUS_INTERNALSERVERERROR,
4782 "osrfMethodException",
4784 "Malformed ORDER BY clause -- see error log for more details"
4786 jsonIteratorFree( order_itr );
4787 jsonIteratorFree( class_itr );
4789 buffer_free( group_buf );
4790 buffer_free( order_buf );
4791 buffer_free( sql_buf );
4792 if( defaultselhash )
4793 jsonObjectFree( defaultselhash );
4797 string = strdup( order_itr->key );
4798 const char* dir = jsonObjectGetString( onode );
4799 if( !strncasecmp( dir, "d", 1 )) {
4800 direction = " DESC";
4807 OSRF_BUFFER_ADD( order_buf, ", " );
4809 order_buf = buffer_init( 128 );
4811 OSRF_BUFFER_ADD( order_buf, string );
4815 OSRF_BUFFER_ADD( order_buf, direction );
4819 jsonIteratorFree( order_itr );
4821 } else if( snode->type == JSON_ARRAY ) {
4823 // Array is a list of fields from the current class
4824 unsigned long order_idx = 0;
4825 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4827 const char* _f = jsonObjectGetString( onode );
4829 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4831 osrfLogError( OSRF_LOG_MARK,
4832 "%s: Invalid field \"%s\" in ORDER BY clause",
4835 osrfAppSessionStatus(
4837 OSRF_STATUS_INTERNALSERVERERROR,
4838 "osrfMethodException",
4840 "Invalid field in ORDER BY clause -- "
4841 "see error log for more details"
4843 jsonIteratorFree( class_itr );
4844 buffer_free( order_buf );
4846 buffer_free( group_buf );
4847 buffer_free( sql_buf );
4848 if( defaultselhash )
4849 jsonObjectFree( defaultselhash );
4851 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4852 osrfLogError( OSRF_LOG_MARK,
4853 "%s: Virtual field \"%s\" in ORDER BY clause",
4856 osrfAppSessionStatus(
4858 OSRF_STATUS_INTERNALSERVERERROR,
4859 "osrfMethodException",
4861 "Virtual field in ORDER BY clause -- "
4862 "see error log for more details"
4864 jsonIteratorFree( class_itr );
4865 buffer_free( order_buf );
4867 buffer_free( group_buf );
4868 buffer_free( sql_buf );
4869 if( defaultselhash )
4870 jsonObjectFree( defaultselhash );
4875 OSRF_BUFFER_ADD( order_buf, ", " );
4877 order_buf = buffer_init( 128 );
4879 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4883 // IT'S THE OOOOOOOOOOOLD STYLE!
4885 osrfLogError( OSRF_LOG_MARK,
4886 "%s: Possible SQL injection attempt; direct order by is not allowed",
4889 osrfAppSessionStatus(
4891 OSRF_STATUS_INTERNALSERVERERROR,
4892 "osrfMethodException",
4894 "Severe query error -- see error log for more details"
4899 buffer_free( group_buf );
4900 buffer_free( order_buf );
4901 buffer_free( sql_buf );
4902 if( defaultselhash )
4903 jsonObjectFree( defaultselhash );
4904 jsonIteratorFree( class_itr );
4908 jsonIteratorFree( class_itr );
4910 order_by_list = buffer_release( order_buf );
4912 osrfLogError( OSRF_LOG_MARK,
4913 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4914 modulename, json_type( order_hash->type ) );
4916 osrfAppSessionStatus(
4918 OSRF_STATUS_INTERNALSERVERERROR,
4919 "osrfMethodException",
4921 "Malformed ORDER BY clause -- see error log for more details"
4924 buffer_free( group_buf );
4925 buffer_free( sql_buf );
4926 if( defaultselhash )
4927 jsonObjectFree( defaultselhash );
4932 string = buffer_release( group_buf );
4934 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4935 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4936 OSRF_BUFFER_ADD( sql_buf, string );
4941 if( having_buf && *having_buf ) {
4942 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4943 OSRF_BUFFER_ADD( sql_buf, having_buf );
4947 if( order_by_list ) {
4949 if( *order_by_list ) {
4950 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4951 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4954 free( order_by_list );
4958 const char* str = jsonObjectGetString( limit );
4959 if (str) { // limit could be JSON_NULL, etc.
4960 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4965 const char* str = jsonObjectGetString( offset );
4967 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4971 if( !(flags & SUBSELECT) )
4972 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4974 if( defaultselhash )
4975 jsonObjectFree( defaultselhash );
4977 return buffer_release( sql_buf );
4979 } // end of SELECT()
4982 @brief Build a list of ORDER BY expressions.
4983 @param ctx Pointer to the method context.
4984 @param order_array Pointer to a JSON_ARRAY of field specifications.
4985 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
4986 Each expression may be either a column reference or a function call whose first parameter
4987 is a column reference.
4989 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
4990 It may optionally include entries for "direction" and/or "transform".
4992 The calling code is responsible for freeing the returned string.
4994 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
4995 if( ! order_array ) {
4996 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
4999 osrfAppSessionStatus(
5001 OSRF_STATUS_INTERNALSERVERERROR,
5002 "osrfMethodException",
5004 "Logic error: ORDER BY clause expected, not found; "
5005 "see error log for more details"
5008 } else if( order_array->type != JSON_ARRAY ) {
5009 osrfLogError( OSRF_LOG_MARK,
5010 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
5012 osrfAppSessionStatus(
5014 OSRF_STATUS_INTERNALSERVERERROR,
5015 "osrfMethodException",
5017 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
5021 growing_buffer* order_buf = buffer_init( 128 );
5022 int first = 1; // boolean
5024 jsonObject* order_spec;
5025 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
5027 if( JSON_HASH != order_spec->type ) {
5028 osrfLogError( OSRF_LOG_MARK,
5029 "%s: Malformed field specification in ORDER BY clause; "
5030 "expected JSON_HASH, found %s",
5031 modulename, json_type( order_spec->type ) );
5033 osrfAppSessionStatus(
5035 OSRF_STATUS_INTERNALSERVERERROR,
5036 "osrfMethodException",
5038 "Malformed ORDER BY clause -- see error log for more details"
5040 buffer_free( order_buf );
5044 const char* class_alias =
5045 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5047 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5049 jsonObject* compare_to = jsonObjectGetKeyConst( order_spec, "compare" );
5051 if( !field || !class_alias ) {
5052 osrfLogError( OSRF_LOG_MARK,
5053 "%s: Missing class or field name in field specification of ORDER BY clause",
5056 osrfAppSessionStatus(
5058 OSRF_STATUS_INTERNALSERVERERROR,
5059 "osrfMethodException",
5061 "Malformed ORDER BY clause -- see error log for more details"
5063 buffer_free( order_buf );
5067 const ClassInfo* order_class_info = search_alias( class_alias );
5068 if( ! order_class_info ) {
5069 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5070 "not in FROM clause, skipping it", modulename, class_alias );
5074 // Add a separating comma, except at the beginning
5078 OSRF_BUFFER_ADD( order_buf, ", " );
5080 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5082 osrfLogError( OSRF_LOG_MARK,
5083 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5084 modulename, class_alias, field );
5086 osrfAppSessionStatus(
5088 OSRF_STATUS_INTERNALSERVERERROR,
5089 "osrfMethodException",
5091 "Invalid field referenced in ORDER BY clause -- "
5092 "see error log for more details"
5096 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5097 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5098 modulename, field );
5100 osrfAppSessionStatus(
5102 OSRF_STATUS_INTERNALSERVERERROR,
5103 "osrfMethodException",
5105 "Virtual field in ORDER BY clause -- see error log for more details"
5107 buffer_free( order_buf );
5111 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5112 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5113 if( ! transform_str ) {
5115 osrfAppSessionStatus(
5117 OSRF_STATUS_INTERNALSERVERERROR,
5118 "osrfMethodException",
5120 "Severe query error in ORDER BY clause -- "
5121 "see error log for more details"
5123 buffer_free( order_buf );
5127 OSRF_BUFFER_ADD( order_buf, transform_str );
5128 free( transform_str );
5129 } else if( compare_to ) {
5130 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5131 if( ! compare_str ) {
5133 osrfAppSessionStatus(
5135 OSRF_STATUS_INTERNALSERVERERROR,
5136 "osrfMethodException",
5138 "Severe query error in ORDER BY clause -- "
5139 "see error log for more details"
5141 buffer_free( order_buf );
5145 buffer_fadd( order_buf, "(%s)", compare_str );
5146 free( compare_str );
5149 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5151 const char* direction =
5152 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5154 if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5155 OSRF_BUFFER_ADD( order_buf, " DESC" );
5157 OSRF_BUFFER_ADD( order_buf, " ASC" );
5161 return buffer_release( order_buf );
5165 @brief Build a SELECT statement.
5166 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5167 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5168 @param meta Pointer to the class metadata for the core class.
5169 @param ctx Pointer to the method context.
5170 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5172 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5173 "order_by", "limit", and "offset".
5175 The SELECT statements built here are distinct from those built for the json_query method.
5177 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5178 osrfHash* meta, osrfMethodContext* ctx ) {
5180 const char* locale = osrf_message_get_last_locale();
5182 osrfHash* fields = osrfHashGet( meta, "fields" );
5183 const char* core_class = osrfHashGet( meta, "classname" );
5185 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5187 jsonObject* selhash = NULL;
5188 jsonObject* defaultselhash = NULL;
5190 growing_buffer* sql_buf = buffer_init( 128 );
5191 growing_buffer* select_buf = buffer_init( 128 );
5193 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5194 defaultselhash = jsonNewObjectType( JSON_HASH );
5195 selhash = defaultselhash;
5198 // If there's no SELECT list for the core class, build one
5199 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5200 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5202 // Add every non-virtual field to the field list
5203 osrfHash* field_def = NULL;
5204 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5205 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5206 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5207 const char* field = osrfHashIteratorKey( field_itr );
5208 jsonObjectPush( field_list, jsonNewObject( field ) );
5211 osrfHashIteratorFree( field_itr );
5212 jsonObjectSetKey( selhash, core_class, field_list );
5215 // Build a list of columns for the SELECT clause
5217 const jsonObject* snode = NULL;
5218 jsonIterator* class_itr = jsonNewIterator( selhash );
5219 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5221 // If the class isn't in the IDL, ignore it
5222 const char* cname = class_itr->key;
5223 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5227 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5228 if( strcmp( core_class, class_itr->key )) {
5232 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5233 if( !found->size ) {
5234 jsonObjectFree( found );
5238 jsonObjectFree( found );
5241 const jsonObject* node = NULL;
5242 jsonIterator* select_itr = jsonNewIterator( snode );
5243 while( (node = jsonIteratorNext( select_itr )) ) {
5244 const char* item_str = jsonObjectGetString( node );
5245 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5246 char* fname = osrfHashGet( field, "name" );
5251 if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5257 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5262 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5263 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5266 i18n = osrfHashGet( field, "i18n" );
5268 if( str_is_true( i18n ) ) {
5269 char* pkey = osrfHashGet( idlClass, "primarykey" );
5270 char* tname = osrfHashGet( idlClass, "tablename" );
5272 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5273 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5274 tname, cname, fname, pkey, cname, pkey, locale, fname );
5276 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5279 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5283 jsonIteratorFree( select_itr );
5286 jsonIteratorFree( class_itr );
5288 char* col_list = buffer_release( select_buf );
5289 char* table = oilsGetRelation( meta );
5291 table = strdup( "(null)" );
5293 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5297 // Clear the query stack (as a fail-safe precaution against possible
5298 // leftover garbage); then push the first query frame onto the stack.
5299 clear_query_stack();
5301 if( add_query_core( NULL, core_class ) ) {
5303 osrfAppSessionStatus(
5305 OSRF_STATUS_INTERNALSERVERERROR,
5306 "osrfMethodException",
5308 "Unable to build query frame for core class"
5310 buffer_free( sql_buf );
5311 if( defaultselhash )
5312 jsonObjectFree( defaultselhash );
5316 // Add the JOIN clauses, if any
5318 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5319 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5320 OSRF_BUFFER_ADD( sql_buf, join_clause );
5321 free( join_clause );
5324 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5325 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5327 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5329 // Add the conditions in the WHERE clause
5330 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5332 osrfAppSessionStatus(
5334 OSRF_STATUS_INTERNALSERVERERROR,
5335 "osrfMethodException",
5337 "Severe query error -- see error log for more details"
5339 buffer_free( sql_buf );
5340 if( defaultselhash )
5341 jsonObjectFree( defaultselhash );
5342 clear_query_stack();
5345 buffer_add( sql_buf, pred );
5349 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5350 if( rest_of_query ) {
5351 const jsonObject* order_by = NULL;
5352 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5354 char* order_by_list = NULL;
5356 if( JSON_ARRAY == order_by->type ) {
5357 order_by_list = buildOrderByFromArray( ctx, order_by );
5358 if( !order_by_list ) {
5359 buffer_free( sql_buf );
5360 if( defaultselhash )
5361 jsonObjectFree( defaultselhash );
5362 clear_query_stack();
5365 } else if( JSON_HASH == order_by->type ) {
5366 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5367 // and build a list of ORDER BY expressions.
5368 growing_buffer* order_buf = buffer_init( 128 );
5370 jsonIterator* class_itr = jsonNewIterator( order_by );
5371 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5373 ClassInfo* order_class_info = search_alias( class_itr->key );
5374 if( ! order_class_info )
5375 continue; // class not referenced by FROM clause? Ignore it.
5377 if( JSON_HASH == snode->type ) {
5379 // If the data for the current class is a JSON_HASH, then it is
5380 // keyed on field name.
5382 const jsonObject* onode = NULL;
5383 jsonIterator* order_itr = jsonNewIterator( snode );
5384 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5386 osrfHash* field_def = osrfHashGet(
5387 order_class_info->fields, order_itr->key );
5389 continue; // Field not defined in IDL? Ignore it.
5390 if( str_is_true( osrfHashGet( field_def, "virtual")))
5391 continue; // Field is virtual? Ignore it.
5393 char* field_str = NULL;
5394 char* direction = NULL;
5395 if( onode->type == JSON_HASH ) {
5396 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5397 field_str = searchFieldTransform(
5398 class_itr->key, field_def, onode );
5400 osrfAppSessionStatus(
5402 OSRF_STATUS_INTERNALSERVERERROR,
5403 "osrfMethodException",
5405 "Severe query error in ORDER BY clause -- "
5406 "see error log for more details"
5408 jsonIteratorFree( order_itr );
5409 jsonIteratorFree( class_itr );
5410 buffer_free( order_buf );
5411 buffer_free( sql_buf );
5412 if( defaultselhash )
5413 jsonObjectFree( defaultselhash );
5414 clear_query_stack();
5418 growing_buffer* field_buf = buffer_init( 16 );
5419 buffer_fadd( field_buf, "\"%s\".%s",
5420 class_itr->key, order_itr->key );
5421 field_str = buffer_release( field_buf );
5424 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5425 const char* dir = jsonObjectGetString( order_by );
5426 if(!strncasecmp( dir, "d", 1 )) {
5427 direction = " DESC";
5431 field_str = strdup( order_itr->key );
5432 const char* dir = jsonObjectGetString( onode );
5433 if( !strncasecmp( dir, "d", 1 )) {
5434 direction = " DESC";
5443 buffer_add( order_buf, ", " );
5446 buffer_add( order_buf, field_str );
5450 buffer_add( order_buf, direction );
5452 } // end while; looping over ORDER BY expressions
5454 jsonIteratorFree( order_itr );
5456 } else if( JSON_STRING == snode->type ) {
5457 // We expect a comma-separated list of sort fields.
5458 const char* str = jsonObjectGetString( snode );
5459 if( strchr( str, ';' )) {
5460 // No semicolons allowed. It is theoretically possible for a
5461 // legitimate semicolon to occur within quotes, but it's not likely
5462 // to occur in practice in the context of an ORDER BY list.
5463 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5464 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5466 osrfAppSessionStatus(
5468 OSRF_STATUS_INTERNALSERVERERROR,
5469 "osrfMethodException",
5471 "Possible attempt at SOL injection -- "
5472 "semicolon found in ORDER BY list"
5475 jsonIteratorFree( class_itr );
5476 buffer_free( order_buf );
5477 buffer_free( sql_buf );
5478 if( defaultselhash )
5479 jsonObjectFree( defaultselhash );
5480 clear_query_stack();
5483 buffer_add( order_buf, str );
5487 } // end while; looping over order_by classes
5489 jsonIteratorFree( class_itr );
5490 order_by_list = buffer_release( order_buf );
5493 osrfLogWarning( OSRF_LOG_MARK,
5494 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5495 "no ORDER BY generated" );
5498 if( order_by_list && *order_by_list ) {
5499 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5500 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5503 free( order_by_list );
5506 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5508 const char* str = jsonObjectGetString( limit );
5518 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5520 const char* str = jsonObjectGetString( offset );
5531 if( defaultselhash )
5532 jsonObjectFree( defaultselhash );
5533 clear_query_stack();
5535 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5536 return buffer_release( sql_buf );
5539 int doJSONSearch ( osrfMethodContext* ctx ) {
5540 if(osrfMethodVerifyContext( ctx )) {
5541 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5545 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5549 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5553 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5554 flags |= SELECT_DISTINCT;
5556 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5557 flags |= DISABLE_I18N;
5559 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5560 clear_query_stack(); // a possibly needless precaution
5561 char* sql = buildQuery( ctx, hash, flags );
5562 clear_query_stack();
5569 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5572 dbhandle = writehandle;
5574 dbi_result result = dbi_conn_query( dbhandle, sql );
5577 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5579 if( dbi_result_first_row( result )) {
5580 /* JSONify the result */
5581 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5584 jsonObject* return_val = oilsMakeJSONFromResult( result );
5585 osrfAppRespond( ctx, return_val );
5586 jsonObjectFree( return_val );
5587 } while( dbi_result_next_row( result ));
5590 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5593 osrfAppRespondComplete( ctx, NULL );
5595 /* clean up the query */
5596 dbi_result_free( result );
5601 int errnum = dbi_conn_error( dbhandle, &msg );
5602 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5603 modulename, sql, errnum, msg ? msg : "(No description available)" );
5604 osrfAppSessionStatus(
5606 OSRF_STATUS_INTERNALSERVERERROR,
5607 "osrfMethodException",
5609 "Severe query error -- see error log for more details"
5611 if( !oilsIsDBConnected( dbhandle ))
5612 osrfAppSessionPanic( ctx->session );
5619 // The last parameter, err, is used to report an error condition by updating an int owned by
5620 // the calling code.
5622 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5623 // It is the responsibility of the calling code to initialize *err before the
5624 // call, so that it will be able to make sense of the result.
5626 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5627 // redundant anyway.
5628 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5629 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5632 dbhandle = writehandle;
5634 char* core_class = osrfHashGet( class_meta, "classname" );
5635 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5637 char* pkey = osrfHashGet( class_meta, "primarykey" );
5639 if (!ctx->session->userData)
5640 (void) initSessionCache( ctx );
5642 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5643 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5644 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5646 int i_respond_directly = 0;
5647 int flesh_depth = 0;
5649 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5651 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5656 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5658 dbi_result result = dbi_conn_query( dbhandle, sql );
5659 if( NULL == result ) {
5661 int errnum = dbi_conn_error( dbhandle, &msg );
5662 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5663 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5664 msg ? msg : "(No description available)" );
5665 if( !oilsIsDBConnected( dbhandle ))
5666 osrfAppSessionPanic( ctx->session );
5667 osrfAppSessionStatus(
5669 OSRF_STATUS_INTERNALSERVERERROR,
5670 "osrfMethodException",
5672 "Severe query error -- see error log for more details"
5679 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5682 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5683 jsonObject* row_obj = NULL;
5685 // The following two steps are for verifyObjectPCRUD()'s benefit.
5686 // 1. get the flesh depth
5687 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5689 flesh_depth = (int) jsonObjectGetNumber( _tmp );
5690 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5691 flesh_depth = max_flesh_depth;
5694 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
5695 // over the whole life of this request. This means if we've already set
5696 // up a rs_size_req_%d, do nothing.
5697 // a. Incidentally, we can also use this opportunity to set i_respond_directly
5698 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
5699 if( !rs_size ) { // pointer null, so value not set in hash
5700 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
5701 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
5703 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
5704 unsigned long long result_count = dbi_result_get_numrows( result );
5705 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
5706 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
5709 if( dbi_result_first_row( result )) {
5711 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5712 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5713 // eliminate the duplicates.
5714 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5715 osrfHash* dedup = osrfNewHash();
5717 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5718 char* pkey_val = oilsFMGetString( row_obj, pkey );
5719 if( osrfHashGet( dedup, pkey_val ) ) {
5720 jsonObjectFree( row_obj );
5723 if( !enforce_pcrud || !need_to_verify ||
5724 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
5725 osrfHashSet( dedup, pkey_val, pkey_val );
5726 jsonObjectPush( res_list, row_obj );
5729 } while( dbi_result_next_row( result ));
5730 osrfHashFree( dedup );
5733 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5737 /* clean up the query */
5738 dbi_result_free( result );
5741 // If we're asked to flesh, and there's anything to flesh, then flesh it
5742 // (formerly we would skip fleshing if in pcrud mode, but now we support
5743 // fleshing even in PCRUD).
5744 if( res_list->size ) {
5745 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
5746 jsonObject* flesh_fields;
5747 jsonObject* flesh_blob = NULL;
5748 osrfStringArray* link_fields = NULL;
5749 osrfHash* links = NULL;
5753 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
5754 if( temp_blob && flesh_depth > 0 ) {
5756 flesh_blob = jsonObjectClone( temp_blob );
5757 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
5759 links = osrfHashGet( class_meta, "links" );
5761 // Make an osrfStringArray of the names of fields to be fleshed
5762 if( flesh_fields ) {
5763 if( flesh_fields->size == 1 ) {
5764 const char* _t = jsonObjectGetString(
5765 jsonObjectGetIndex( flesh_fields, 0 ) );
5766 if( !strcmp( _t, "*" ))
5767 link_fields = osrfHashKeys( links );
5770 if( !link_fields ) {
5772 link_fields = osrfNewStringArray( 1 );
5773 jsonIterator* _i = jsonNewIterator( flesh_fields );
5774 while ((_f = jsonIteratorNext( _i ))) {
5775 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5777 jsonIteratorFree( _i );
5780 want_flesh = link_fields ? 1 : 0;
5784 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5786 // Iterate over the JSON_ARRAY of rows
5788 unsigned long res_idx = 0;
5789 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5792 const char* link_field;
5794 // Iterate over the list of fleshable fields
5796 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5798 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5800 osrfHash* kid_link = osrfHashGet( links, link_field );
5802 continue; // Not a link field; skip it
5804 osrfHash* field = osrfHashGet( fields, link_field );
5806 continue; // Not a field at all; skip it (IDL is ill-formed)
5808 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5809 osrfHashGet( kid_link, "class" ));
5811 continue; // The class it links to doesn't exist; skip it
5813 const char* reltype = osrfHashGet( kid_link, "reltype" );
5815 continue; // No reltype; skip it (IDL is ill-formed)
5817 osrfHash* value_field = field;
5819 if( !strcmp( reltype, "has_many" )
5820 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5821 value_field = osrfHashGet(
5822 fields, osrfHashGet( class_meta, "primarykey" ) );
5825 int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
5826 // fleshing pcrud case: we require the controller in need_to_verify mode
5827 if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
5828 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
5832 (unsigned long) atoi( osrfHashGet(field, "array_position") ),
5834 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
5840 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5842 if( link_map->size > 0 ) {
5843 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5846 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5851 osrfHashGet( kid_link, "class" ),
5858 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5859 osrfHashGet( kid_link, "field" ),
5860 osrfHashGet( kid_link, "class" ),
5861 osrfHashGet( kid_link, "key" ),
5862 osrfHashGet( kid_link, "reltype" )
5865 const char* search_key = jsonObjectGetString(
5866 jsonObjectGetIndex( cur,
5867 atoi( osrfHashGet( value_field, "array_position" ) )
5872 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5876 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5878 // construct WHERE clause
5879 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5882 osrfHashGet( kid_link, "key" ),
5883 jsonNewObject( search_key )
5886 // construct the rest of the query, mostly
5887 // by copying pieces of the previous level of query
5888 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5889 jsonObjectSetKey( rest_of_query, "flesh",
5890 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5894 jsonObjectSetKey( rest_of_query, "flesh_fields",
5895 jsonObjectClone( flesh_blob ));
5897 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5898 jsonObjectSetKey( rest_of_query, "order_by",
5899 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5903 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5904 jsonObjectSetKey( rest_of_query, "select",
5905 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5909 // do the query, recursively, to expand the fleshable field
5910 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5911 where_clause, rest_of_query, err );
5913 jsonObjectFree( where_clause );
5914 jsonObjectFree( rest_of_query );
5917 osrfStringArrayFree( link_fields );
5918 jsonObjectFree( res_list );
5919 jsonObjectFree( flesh_blob );
5923 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5924 osrfHashGet( kid_link, "class" ), kids->size );
5926 // Traverse the result set
5927 jsonObject* X = NULL;
5928 if( link_map->size > 0 && kids->size > 0 ) {
5930 kids = jsonNewObjectType( JSON_ARRAY );
5932 jsonObject* _k_node;
5933 unsigned long res_idx = 0;
5934 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5940 (unsigned long) atoi(
5946 osrfHashGet( kid_link, "class" )
5950 osrfStringArrayGetString( link_map, 0 )
5958 } // end while loop traversing X
5961 if (kids->size > 0) {
5963 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5964 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
5966 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5967 osrfHashGet( kid_link, "field" ));
5970 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5971 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5976 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5978 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5979 osrfHashGet( kid_link, "field" ) );
5982 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5983 jsonObjectClone( kids )
5988 jsonObjectFree( kids );
5992 jsonObjectFree( kids );
5994 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5995 osrfHashGet( kid_link, "field" ) );
5996 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5998 } // end while loop traversing list of fleshable fields
6001 if( i_respond_directly ) {
6002 if ( *methodtype == 'i' ) {
6003 osrfAppRespond( ctx,
6004 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
6006 osrfAppRespond( ctx, cur );
6009 } // end while loop traversing res_list
6010 jsonObjectFree( flesh_blob );
6011 osrfStringArrayFree( link_fields );
6014 if( i_respond_directly ) {
6015 jsonObjectFree( res_list );
6016 return jsonNewObjectType( JSON_ARRAY );
6023 int doUpdate( osrfMethodContext* ctx ) {
6024 if( osrfMethodVerifyContext( ctx )) {
6025 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6030 timeout_needs_resetting = 1;
6032 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6034 jsonObject* target = NULL;
6036 target = jsonObjectGetIndex( ctx->params, 1 );
6038 target = jsonObjectGetIndex( ctx->params, 0 );
6040 if(!verifyObjectClass( ctx, target )) {
6041 osrfAppRespondComplete( ctx, NULL );
6045 if( getXactId( ctx ) == NULL ) {
6046 osrfAppSessionStatus(
6048 OSRF_STATUS_BADREQUEST,
6049 "osrfMethodException",
6051 "No active transaction -- required for UPDATE"
6053 osrfAppRespondComplete( ctx, NULL );
6057 // The following test is harmless but redundant. If a class is
6058 // readonly, we don't register an update method for it.
6059 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6060 osrfAppSessionStatus(
6062 OSRF_STATUS_BADREQUEST,
6063 "osrfMethodException",
6065 "Cannot UPDATE readonly class"
6067 osrfAppRespondComplete( ctx, NULL );
6071 const char* trans_id = getXactId( ctx );
6073 // Set the last_xact_id
6074 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6076 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6077 trans_id, target->classname, index );
6078 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6081 char* pkey = osrfHashGet( meta, "primarykey" );
6082 osrfHash* fields = osrfHashGet( meta, "fields" );
6084 char* id = oilsFMGetString( target, pkey );
6088 "%s updating %s object with %s = %s",
6090 osrfHashGet( meta, "fieldmapper" ),
6095 dbhandle = writehandle;
6096 growing_buffer* sql = buffer_init( 128 );
6097 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6100 osrfHash* field_def = NULL;
6101 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6102 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6104 // Skip virtual fields, and the primary key
6105 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6108 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6112 const char* field_name = osrfHashIteratorKey( field_itr );
6113 if( ! strcmp( field_name, pkey ) )
6116 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6118 int value_is_numeric = 0; // boolean
6120 if( field_object && field_object->classname ) {
6121 value = oilsFMGetString(
6123 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6125 } else if( field_object && JSON_BOOL == field_object->type ) {
6126 if( jsonBoolIsTrue( field_object ) )
6127 value = strdup( "t" );
6129 value = strdup( "f" );
6131 value = jsonObjectToSimpleString( field_object );
6132 if( field_object && JSON_NUMBER == field_object->type )
6133 value_is_numeric = 1;
6136 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6137 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6139 if( !field_object || field_object->type == JSON_NULL ) {
6140 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6141 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6145 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6146 buffer_fadd( sql, " %s = NULL", field_name );
6149 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6153 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6155 const char* numtype = get_datatype( field_def );
6156 if( !strncmp( numtype, "INT", 3 ) ) {
6157 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6158 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6159 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6161 // Must really be intended as a string, so quote it
6162 if( dbi_conn_quote_string( dbhandle, &value )) {
6163 buffer_fadd( sql, " %s = %s", field_name, value );
6165 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6166 modulename, value );
6167 osrfAppSessionStatus(
6169 OSRF_STATUS_INTERNALSERVERERROR,
6170 "osrfMethodException",
6172 "Error quoting string -- please see the error log for more details"
6176 osrfHashIteratorFree( field_itr );
6178 osrfAppRespondComplete( ctx, NULL );
6183 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6186 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6190 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6191 buffer_fadd( sql, " %s = %s", field_name, value );
6193 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6194 osrfAppSessionStatus(
6196 OSRF_STATUS_INTERNALSERVERERROR,
6197 "osrfMethodException",
6199 "Error quoting string -- please see the error log for more details"
6203 osrfHashIteratorFree( field_itr );
6205 osrfAppRespondComplete( ctx, NULL );
6214 osrfHashIteratorFree( field_itr );
6216 jsonObject* obj = jsonNewObject( id );
6218 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6219 dbi_conn_quote_string( dbhandle, &id );
6221 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6223 char* query = buffer_release( sql );
6224 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6226 dbi_result result = dbi_conn_query( dbhandle, query );
6231 jsonObjectFree( obj );
6232 obj = jsonNewObject( NULL );
6234 int errnum = dbi_conn_error( dbhandle, &msg );
6237 "%s ERROR updating %s object with %s = %s: %d %s",
6239 osrfHashGet( meta, "fieldmapper" ),
6243 msg ? msg : "(No description available)"
6245 osrfAppSessionStatus(
6247 OSRF_STATUS_INTERNALSERVERERROR,
6248 "osrfMethodException",
6250 "Error in updating a row -- please see the error log for more details"
6252 if( !oilsIsDBConnected( dbhandle ))
6253 osrfAppSessionPanic( ctx->session );
6256 dbi_result_free( result );
6259 osrfAppRespondComplete( ctx, obj );
6260 jsonObjectFree( obj );
6264 int doDelete( osrfMethodContext* ctx ) {
6265 if( osrfMethodVerifyContext( ctx )) {
6266 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6271 timeout_needs_resetting = 1;
6273 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6275 if( getXactId( ctx ) == NULL ) {
6276 osrfAppSessionStatus(
6278 OSRF_STATUS_BADREQUEST,
6279 "osrfMethodException",
6281 "No active transaction -- required for DELETE"
6283 osrfAppRespondComplete( ctx, NULL );
6287 // The following test is harmless but redundant. If a class is
6288 // readonly, we don't register a delete method for it.
6289 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6290 osrfAppSessionStatus(
6292 OSRF_STATUS_BADREQUEST,
6293 "osrfMethodException",
6295 "Cannot DELETE readonly class"
6297 osrfAppRespondComplete( ctx, NULL );
6301 dbhandle = writehandle;
6303 char* pkey = osrfHashGet( meta, "primarykey" );
6310 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6311 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6312 osrfAppRespondComplete( ctx, NULL );
6316 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6318 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6319 osrfAppRespondComplete( ctx, NULL );
6322 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6327 "%s deleting %s object with %s = %s",
6329 osrfHashGet( meta, "fieldmapper" ),
6334 jsonObject* obj = jsonNewObject( id );
6336 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6337 dbi_conn_quote_string( writehandle, &id );
6339 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6340 osrfHashGet( meta, "tablename" ), pkey, id );
6345 jsonObjectFree( obj );
6346 obj = jsonNewObject( NULL );
6348 int errnum = dbi_conn_error( writehandle, &msg );
6351 "%s ERROR deleting %s object with %s = %s: %d %s",
6353 osrfHashGet( meta, "fieldmapper" ),
6357 msg ? msg : "(No description available)"
6359 osrfAppSessionStatus(
6361 OSRF_STATUS_INTERNALSERVERERROR,
6362 "osrfMethodException",
6364 "Error in deleting a row -- please see the error log for more details"
6366 if( !oilsIsDBConnected( writehandle ))
6367 osrfAppSessionPanic( ctx->session );
6369 dbi_result_free( result );
6373 osrfAppRespondComplete( ctx, obj );
6374 jsonObjectFree( obj );
6379 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6380 @param result An iterator for a result set; we only look at the current row.
6381 @param @meta Pointer to the class metadata for the core class.
6382 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6384 If a column is not defined in the IDL, or if it has no array_position defined for it in
6385 the IDL, or if it is defined as virtual, ignore it.
6387 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6388 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6389 array_position in the IDL.
6391 A field defined in the IDL but not represented in the returned row will leave a hole
6392 in the JSON_ARRAY. In effect it will be treated as a null value.
6394 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6395 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6396 classname corresponding to the @a meta argument.
6398 The calling code is responsible for freeing the the resulting jsonObject by calling
6401 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6402 if( !( result && meta )) return NULL;
6404 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6405 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6406 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6408 osrfHash* fields = osrfHashGet( meta, "fields" );
6410 int columnIndex = 1;
6411 const char* columnName;
6413 /* cycle through the columns in the row returned from the database */
6414 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6416 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6418 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6420 /* determine the field type and storage attributes */
6421 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6422 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6424 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6425 // or if it has no sequence number there, or if it's virtual, skip it.
6426 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6429 if( str_is_true( osrfHashGet( _f, "virtual" )))
6430 continue; // skip this column: IDL says it's virtual
6432 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6433 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6434 continue; // since we assign sequence numbers dynamically as we load the IDL.
6436 fmIndex = atoi( pos );
6437 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6439 continue; // This field is not defined in the IDL
6442 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6443 // sequence number from the IDL (which is likely to be different from the sequence
6444 // of columns in the SELECT clause).
6445 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6446 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6451 case DBI_TYPE_INTEGER :
6453 if( attr & DBI_INTEGER_SIZE8 )
6454 jsonObjectSetIndex( object, fmIndex,
6455 jsonNewNumberObject(
6456 dbi_result_get_longlong_idx( result, columnIndex )));
6458 jsonObjectSetIndex( object, fmIndex,
6459 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6463 case DBI_TYPE_DECIMAL :
6464 jsonObjectSetIndex( object, fmIndex,
6465 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6468 case DBI_TYPE_STRING :
6473 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6478 case DBI_TYPE_DATETIME : {
6480 char dt_string[ 256 ] = "";
6483 // Fetch the date column as a time_t
6484 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6486 // Translate the time_t to a human-readable string
6487 if( !( attr & DBI_DATETIME_DATE )) {
6488 gmtime_r( &_tmp_dt, &gmdt );
6489 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6490 } else if( !( attr & DBI_DATETIME_TIME )) {
6491 localtime_r( &_tmp_dt, &gmdt );
6492 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6494 localtime_r( &_tmp_dt, &gmdt );
6495 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6498 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6502 case DBI_TYPE_BINARY :
6503 osrfLogError( OSRF_LOG_MARK,
6504 "Can't do binary at column %s : index %d", columnName, columnIndex );
6513 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6514 if( !result ) return NULL;
6516 jsonObject* object = jsonNewObject( NULL );
6519 char dt_string[ 256 ];
6523 int columnIndex = 1;
6525 unsigned short type;
6526 const char* columnName;
6528 /* cycle through the column list */
6529 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6531 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6533 fmIndex = -1; // reset the position
6535 /* determine the field type and storage attributes */
6536 type = dbi_result_get_field_type_idx( result, columnIndex );
6537 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6539 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6540 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6545 case DBI_TYPE_INTEGER :
6547 if( attr & DBI_INTEGER_SIZE8 )
6548 jsonObjectSetKey( object, columnName,
6549 jsonNewNumberObject( dbi_result_get_longlong_idx(
6550 result, columnIndex )) );
6552 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6553 dbi_result_get_int_idx( result, columnIndex )) );
6556 case DBI_TYPE_DECIMAL :
6557 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6558 dbi_result_get_double_idx( result, columnIndex )) );
6561 case DBI_TYPE_STRING :
6562 jsonObjectSetKey( object, columnName,
6563 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6566 case DBI_TYPE_DATETIME :
6568 memset( dt_string, '\0', sizeof( dt_string ));
6569 memset( &gmdt, '\0', sizeof( gmdt ));
6571 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6573 if( !( attr & DBI_DATETIME_DATE )) {
6574 gmtime_r( &_tmp_dt, &gmdt );
6575 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6576 } else if( !( attr & DBI_DATETIME_TIME )) {
6577 localtime_r( &_tmp_dt, &gmdt );
6578 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6580 localtime_r( &_tmp_dt, &gmdt );
6581 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6584 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6587 case DBI_TYPE_BINARY :
6588 osrfLogError( OSRF_LOG_MARK,
6589 "Can't do binary at column %s : index %d", columnName, columnIndex );
6593 } // end while loop traversing result
6598 // Interpret a string as true or false
6599 int str_is_true( const char* str ) {
6600 if( NULL == str || strcasecmp( str, "true" ) )
6606 // Interpret a jsonObject as true or false
6607 static int obj_is_true( const jsonObject* obj ) {
6610 else switch( obj->type )
6618 if( strcasecmp( obj->value.s, "true" ) )
6622 case JSON_NUMBER : // Support 1/0 for perl's sake
6623 if( jsonObjectGetNumber( obj ) == 1.0 )
6632 // Translate a numeric code into a text string identifying a type of
6633 // jsonObject. To be used for building error messages.
6634 static const char* json_type( int code ) {
6640 return "JSON_ARRAY";
6642 return "JSON_STRING";
6644 return "JSON_NUMBER";
6650 return "(unrecognized)";
6654 // Extract the "primitive" attribute from an IDL field definition.
6655 // If we haven't initialized the app, then we must be running in
6656 // some kind of testbed. In that case, default to "string".
6657 static const char* get_primitive( osrfHash* field ) {
6658 const char* s = osrfHashGet( field, "primitive" );
6660 if( child_initialized )
6663 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6665 osrfHashGet( field, "name" )
6673 // Extract the "datatype" attribute from an IDL field definition.
6674 // If we haven't initialized the app, then we must be running in
6675 // some kind of testbed. In that case, default to to NUMERIC,
6676 // since we look at the datatype only for numbers.
6677 static const char* get_datatype( osrfHash* field ) {
6678 const char* s = osrfHashGet( field, "datatype" );
6680 if( child_initialized )
6683 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6685 osrfHashGet( field, "name" )
6694 @brief Determine whether a string is potentially a valid SQL identifier.
6695 @param s The identifier to be tested.
6696 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6698 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6699 need to follow all the rules exactly, such as requiring that the first character not
6702 We allow leading and trailing white space. In between, we do not allow punctuation
6703 (except for underscores and dollar signs), control characters, or embedded white space.
6705 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6706 for the foreseeable future such quoted identifiers are not likely to be an issue.
6708 int is_identifier( const char* s) {
6712 // Skip leading white space
6713 while( isspace( (unsigned char) *s ) )
6717 return 0; // Nothing but white space? Not okay.
6719 // Check each character until we reach white space or
6720 // end-of-string. Letters, digits, underscores, and
6721 // dollar signs are okay. With the exception of periods
6722 // (as in schema.identifier), control characters and other
6723 // punctuation characters are not okay. Anything else
6724 // is okay -- it could for example be part of a multibyte
6725 // UTF8 character such as a letter with diacritical marks,
6726 // and those are allowed.
6728 if( isalnum( (unsigned char) *s )
6732 ; // Fine; keep going
6733 else if( ispunct( (unsigned char) *s )
6734 || iscntrl( (unsigned char) *s ) )
6737 } while( *s && ! isspace( (unsigned char) *s ) );
6739 // If we found any white space in the above loop,
6740 // the rest had better be all white space.
6742 while( isspace( (unsigned char) *s ) )
6746 return 0; // White space was embedded within non-white space
6752 @brief Determine whether to accept a character string as a comparison operator.
6753 @param op The candidate comparison operator.
6754 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6756 We don't validate the operator for real. We just make sure that it doesn't contain
6757 any semicolons or white space (with special exceptions for a few specific operators).
6758 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6759 space but it's still not a valid operator, then the database will complain.
6761 Another approach would be to compare the string against a short list of approved operators.
6762 We don't do that because we want to allow custom operators like ">100*", which at this
6763 writing would be difficult or impossible to express otherwise in a JSON query.
6765 int is_good_operator( const char* op ) {
6766 if( !op ) return 0; // Sanity check
6770 if( isspace( (unsigned char) *s ) ) {
6771 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6772 // and IS NOT DISTINCT FROM.
6773 if( !strcasecmp( op, "similar to" ) )
6775 else if( !strcasecmp( op, "is distinct from" ) )
6777 else if( !strcasecmp( op, "is not distinct from" ) )
6782 else if( ';' == *s )
6790 @name Query Frame Management
6792 The following machinery supports a stack of query frames for use by SELECT().
6794 A query frame caches information about one level of a SELECT query. When we enter
6795 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6797 The query frame stores information about the core class, and about any joined classes
6800 The main purpose is to map table aliases to classes and tables, so that a query can
6801 join to the same table more than once. A secondary goal is to reduce the number of
6802 lookups in the IDL by caching the results.
6806 #define STATIC_CLASS_INFO_COUNT 3
6808 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6811 @brief Allocate a ClassInfo as raw memory.
6812 @return Pointer to the newly allocated ClassInfo.
6814 Except for the in_use flag, which is used only by the allocation and deallocation
6815 logic, we don't initialize the ClassInfo here.
6817 static ClassInfo* allocate_class_info( void ) {
6818 // In order to reduce the number of mallocs and frees, we return a static
6819 // instance of ClassInfo, if we can find one that we're not already using.
6820 // We rely on the fact that the compiler will implicitly initialize the
6821 // static instances so that in_use == 0.
6824 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6825 if( ! static_class_info[ i ].in_use ) {
6826 static_class_info[ i ].in_use = 1;
6827 return static_class_info + i;
6831 // The static ones are all in use. Malloc one.
6833 return safe_malloc( sizeof( ClassInfo ) );
6837 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6838 @param info Pointer to the ClassInfo to be cleared.
6840 static void clear_class_info( ClassInfo* info ) {
6845 // Free any malloc'd strings
6847 if( info->alias != info->alias_store )
6848 free( info->alias );
6850 if( info->class_name != info->class_name_store )
6851 free( info->class_name );
6853 free( info->source_def );
6855 info->alias = info->class_name = info->source_def = NULL;
6860 @brief Free a ClassInfo and everything it owns.
6861 @param info Pointer to the ClassInfo to be freed.
6863 static void free_class_info( ClassInfo* info ) {
6868 clear_class_info( info );
6870 // If it's one of the static instances, just mark it as not in use
6873 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6874 if( info == static_class_info + i ) {
6875 static_class_info[ i ].in_use = 0;
6880 // Otherwise it must have been malloc'd, so free it
6886 @brief Populate an already-allocated ClassInfo.
6887 @param info Pointer to the ClassInfo to be populated.
6888 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6890 @param class Name of the class.
6891 @return Zero if successful, or 1 if not.
6893 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6894 the relevant portions of the IDL for the specified class.
6896 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6899 osrfLogError( OSRF_LOG_MARK,
6900 "%s ERROR: No ClassInfo available to populate", modulename );
6901 info->alias = info->class_name = info->source_def = NULL;
6902 info->class_def = info->fields = info->links = NULL;
6907 osrfLogError( OSRF_LOG_MARK,
6908 "%s ERROR: No class name provided for lookup", modulename );
6909 info->alias = info->class_name = info->source_def = NULL;
6910 info->class_def = info->fields = info->links = NULL;
6914 // Alias defaults to class name if not supplied
6915 if( ! alias || ! alias[ 0 ] )
6918 // Look up class info in the IDL
6919 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6921 osrfLogError( OSRF_LOG_MARK,
6922 "%s ERROR: Class %s not defined in IDL", modulename, class );
6923 info->alias = info->class_name = info->source_def = NULL;
6924 info->class_def = info->fields = info->links = NULL;
6926 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6927 osrfLogError( OSRF_LOG_MARK,
6928 "%s ERROR: Class %s is defined as virtual", modulename, class );
6929 info->alias = info->class_name = info->source_def = NULL;
6930 info->class_def = info->fields = info->links = NULL;
6934 osrfHash* links = osrfHashGet( class_def, "links" );
6936 osrfLogError( OSRF_LOG_MARK,
6937 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6938 info->alias = info->class_name = info->source_def = NULL;
6939 info->class_def = info->fields = info->links = NULL;
6943 osrfHash* fields = osrfHashGet( class_def, "fields" );
6945 osrfLogError( OSRF_LOG_MARK,
6946 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6947 info->alias = info->class_name = info->source_def = NULL;
6948 info->class_def = info->fields = info->links = NULL;
6952 char* source_def = oilsGetRelation( class_def );
6956 // We got everything we need, so populate the ClassInfo
6957 if( strlen( alias ) > ALIAS_STORE_SIZE )
6958 info->alias = strdup( alias );
6960 strcpy( info->alias_store, alias );
6961 info->alias = info->alias_store;
6964 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6965 info->class_name = strdup( class );
6967 strcpy( info->class_name_store, class );
6968 info->class_name = info->class_name_store;
6971 info->source_def = source_def;
6973 info->class_def = class_def;
6974 info->links = links;
6975 info->fields = fields;
6980 #define STATIC_FRAME_COUNT 3
6982 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6985 @brief Allocate a QueryFrame as raw memory.
6986 @return Pointer to the newly allocated QueryFrame.
6988 Except for the in_use flag, which is used only by the allocation and deallocation
6989 logic, we don't initialize the QueryFrame here.
6991 static QueryFrame* allocate_frame( void ) {
6992 // In order to reduce the number of mallocs and frees, we return a static
6993 // instance of QueryFrame, if we can find one that we're not already using.
6994 // We rely on the fact that the compiler will implicitly initialize the
6995 // static instances so that in_use == 0.
6998 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6999 if( ! static_frame[ i ].in_use ) {
7000 static_frame[ i ].in_use = 1;
7001 return static_frame + i;
7005 // The static ones are all in use. Malloc one.
7007 return safe_malloc( sizeof( QueryFrame ) );
7011 @brief Free a QueryFrame, and all the memory it owns.
7012 @param frame Pointer to the QueryFrame to be freed.
7014 static void free_query_frame( QueryFrame* frame ) {
7019 clear_class_info( &frame->core );
7021 // Free the join list
7023 ClassInfo* info = frame->join_list;
7026 free_class_info( info );
7030 frame->join_list = NULL;
7033 // If the frame is a static instance, just mark it as unused
7035 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7036 if( frame == static_frame + i ) {
7037 static_frame[ i ].in_use = 0;
7042 // Otherwise it must have been malloc'd, so free it
7048 @brief Search a given QueryFrame for a specified alias.
7049 @param frame Pointer to the QueryFrame to be searched.
7050 @param target The alias for which to search.
7051 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7053 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7054 if( ! frame || ! target ) {
7058 ClassInfo* found_class = NULL;
7060 if( !strcmp( target, frame->core.alias ) )
7061 return &(frame->core);
7063 ClassInfo* curr_class = frame->join_list;
7064 while( curr_class ) {
7065 if( strcmp( target, curr_class->alias ) )
7066 curr_class = curr_class->next;
7068 found_class = curr_class;
7078 @brief Push a new (blank) QueryFrame onto the stack.
7080 static void push_query_frame( void ) {
7081 QueryFrame* frame = allocate_frame();
7082 frame->join_list = NULL;
7083 frame->next = curr_query;
7085 // Initialize the ClassInfo for the core class
7086 ClassInfo* core = &frame->core;
7087 core->alias = core->class_name = core->source_def = NULL;
7088 core->class_def = core->fields = core->links = NULL;
7094 @brief Pop a QueryFrame off the stack and destroy it.
7096 static void pop_query_frame( void ) {
7101 QueryFrame* popped = curr_query;
7102 curr_query = popped->next;
7104 free_query_frame( popped );
7108 @brief Populate the ClassInfo for the core class.
7109 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7110 class name as an alias.
7111 @param class_name Name of the core class.
7112 @return Zero if successful, or 1 if not.
7114 Populate the ClassInfo of the core class with copies of the alias and class name, and
7115 with pointers to the relevant portions of the IDL for the core class.
7117 static int add_query_core( const char* alias, const char* class_name ) {
7120 if( ! curr_query ) {
7121 osrfLogError( OSRF_LOG_MARK,
7122 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7124 } else if( curr_query->core.alias ) {
7125 osrfLogError( OSRF_LOG_MARK,
7126 "%s ERROR: Core class %s already populated as %s",
7127 modulename, curr_query->core.class_name, curr_query->core.alias );
7131 build_class_info( &curr_query->core, alias, class_name );
7132 if( curr_query->core.alias )
7135 osrfLogError( OSRF_LOG_MARK,
7136 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7142 @brief Search the current QueryFrame for a specified alias.
7143 @param target The alias for which to search.
7144 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7146 static inline ClassInfo* search_alias( const char* target ) {
7147 return search_alias_in_frame( curr_query, target );
7151 @brief Search all levels of query for a specified alias, starting with the current query.
7152 @param target The alias for which to search.
7153 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7155 static ClassInfo* search_all_alias( const char* target ) {
7156 ClassInfo* found_class = NULL;
7157 QueryFrame* curr_frame = curr_query;
7159 while( curr_frame ) {
7160 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7163 curr_frame = curr_frame->next;
7170 @brief Add a class to the list of classes joined to the current query.
7171 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7172 the class name as an alias.
7173 @param classname The name of the class to be added.
7174 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7176 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7178 if( ! classname || ! *classname ) { // sanity check
7179 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7186 const ClassInfo* conflict = search_alias( alias );
7188 osrfLogError( OSRF_LOG_MARK,
7189 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7190 modulename, alias, conflict->class_name );
7194 ClassInfo* info = allocate_class_info();
7196 if( build_class_info( info, alias, classname ) ) {
7197 free_class_info( info );
7201 // Add the new ClassInfo to the join list of the current QueryFrame
7202 info->next = curr_query->join_list;
7203 curr_query->join_list = info;
7209 @brief Destroy all nodes on the query stack.
7211 static void clear_query_stack( void ) {
7217 @brief Implement the set_audit_info method.
7218 @param ctx Pointer to the method context.
7219 @return Zero if successful, or -1 if not.
7221 Issue a SAVEPOINT to the database server.
7226 - workstation id (int)
7228 If user id is not provided the authkey will be used.
7229 For PCRUD the authkey is always used, even if a user is provided.
7231 int setAuditInfo( osrfMethodContext* ctx ) {
7232 if(osrfMethodVerifyContext( ctx )) {
7233 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
7237 // Get the user id from the parameters
7238 const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7240 if( enforce_pcrud || !user_id ) {
7241 timeout_needs_resetting = 1;
7242 const jsonObject* user = verifyUserPCRUD( ctx );
7245 osrfAppRespondComplete( ctx, NULL );
7249 // Not PCRUD and have a user_id?
7250 int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7251 osrfAppRespondComplete( ctx, NULL );
7256 @brief Save a audit info
7257 @param ctx Pointer to the method context.
7258 @param user_id User ID to write as a string
7259 @param ws_id Workstation ID to write as a string
7261 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7262 if( ctx && ctx->session ) {
7263 osrfAppSession* session = ctx->session;
7265 osrfHash* cache = session->userData;
7267 // If the session doesn't already have a hash, create one. Make sure
7268 // that the application session frees the hash when it terminates.
7269 if( NULL == cache ) {
7270 session->userData = cache = osrfNewHash();
7271 osrfHashSetCallback( cache, &sessionDataFree );
7272 ctx->session->userDataFree = &userDataFree;
7275 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7277 osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7279 int errnum = dbi_conn_error( writehandle, &msg );
7282 "%s: Error setting auditor information: %d %s",
7285 msg ? msg : "(No description available)"
7287 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7288 "osrfMethodException", ctx->request, "Error setting auditor info" );
7289 if( !oilsIsDBConnected( writehandle ))
7290 osrfAppSessionPanic( ctx->session );
7293 dbi_result_free( result );
7300 @brief Remove all but safe character from savepoint name
7301 @param sp User-supplied savepoint name
7302 @return sanitized savepoint name, or NULL
7304 The caller is expected to free the returned string. Note that
7305 this function exists only because we can't use PQescapeLiteral
7306 without either forking libdbi or abandoning it.
7308 static char* _sanitize_savepoint_name( const char* sp ) {
7310 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_";
7312 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7313 // and the default value of NAMEDATALEN is 64; that should be long enough
7314 // for our purposes, and it's unlikely that anyone is going to recompile
7315 // PostgreSQL to have a smaller value, so cap the identifier name
7316 // accordingly to avoid the remote chance that someone manages to pass in a
7317 // 12GB savepoint name
7318 const int MAX_LITERAL_NAMELEN = 63;
7321 if (len > MAX_LITERAL_NAMELEN) {
7322 len = MAX_LITERAL_NAMELEN;
7325 char* safeSpName = safe_malloc( len + 1 );
7329 for (j = 0; j < len; j++) {
7330 found = strchr(safe_chars, sp[j]);
7332 safeSpName[ i++ ] = found[0];
7335 safeSpName[ i ] = '\0';