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) );
972 char *safeSpName = _sanitize_savepoint_name( spName );
974 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", safeSpName );
978 int errnum = dbi_conn_error( writehandle, &msg );
981 "%s: Error releasing savepoint %s in transaction %s: %d %s",
986 msg ? msg : "(No description available)"
988 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
989 "osrfMethodException", ctx->request, "Error releasing savepoint" );
990 if( !oilsIsDBConnected( writehandle ))
991 osrfAppSessionPanic( ctx->session );
994 dbi_result_free( result );
995 jsonObject* ret = jsonNewObject( spName );
996 osrfAppRespondComplete( ctx, ret );
997 jsonObjectFree( ret );
1003 @brief Implement the savepoint.rollback method.
1004 @param ctx Pointer to the method context.
1005 @return Zero if successful, or -1 if not.
1007 Issue a ROLLBACK TO SAVEPOINT to the database server.
1010 - authkey (PCRUD only)
1013 Return to client: Savepoint name
1015 int rollbackSavepoint( osrfMethodContext* ctx ) {
1016 if(osrfMethodVerifyContext( ctx )) {
1017 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1022 if( enforce_pcrud ) {
1024 timeout_needs_resetting = 1;
1025 const jsonObject* user = verifyUserPCRUD( ctx );
1030 // Verify that a transaction is pending
1031 const char* trans_id = getXactId( ctx );
1032 if( NULL == trans_id ) {
1033 osrfAppSessionStatus(
1035 OSRF_STATUS_INTERNALSERVERERROR,
1036 "osrfMethodException",
1038 "No active transaction -- required for savepoints"
1043 // Get the savepoint name from the method params
1044 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1045 char *safeSpName = _sanitize_savepoint_name( spName );
1047 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", safeSpName );
1051 int errnum = dbi_conn_error( writehandle, &msg );
1054 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
1059 msg ? msg : "(No description available)"
1061 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1062 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1063 if( !oilsIsDBConnected( writehandle ))
1064 osrfAppSessionPanic( ctx->session );
1067 dbi_result_free( result );
1068 jsonObject* ret = jsonNewObject( spName );
1069 osrfAppRespondComplete( ctx, ret );
1070 jsonObjectFree( ret );
1076 @brief Implement the transaction.commit method.
1077 @param ctx Pointer to the method context.
1078 @return Zero if successful, or -1 if not.
1080 Issue a COMMIT to the database server.
1083 - authkey (PCRUD only)
1085 Return to client: Transaction ID.
1087 int commitTransaction( osrfMethodContext* ctx ) {
1088 if(osrfMethodVerifyContext( ctx )) {
1089 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1093 if( enforce_pcrud ) {
1094 timeout_needs_resetting = 1;
1095 const jsonObject* user = verifyUserPCRUD( ctx );
1100 // Verify that a transaction is pending
1101 const char* trans_id = getXactId( ctx );
1102 if( NULL == trans_id ) {
1103 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1104 "osrfMethodException", ctx->request, "No active transaction to commit" );
1108 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1111 int errnum = dbi_conn_error( writehandle, &msg );
1112 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1113 modulename, errnum, msg ? msg : "(No description available)" );
1114 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1115 "osrfMethodException", ctx->request, "Error committing transaction" );
1116 if( !oilsIsDBConnected( writehandle ))
1117 osrfAppSessionPanic( ctx->session );
1120 dbi_result_free( result );
1121 jsonObject* ret = jsonNewObject( trans_id );
1122 osrfAppRespondComplete( ctx, ret );
1123 jsonObjectFree( ret );
1130 @brief Implement the transaction.rollback method.
1131 @param ctx Pointer to the method context.
1132 @return Zero if successful, or -1 if not.
1134 Issue a ROLLBACK to the database server.
1137 - authkey (PCRUD only)
1139 Return to client: Transaction ID
1141 int rollbackTransaction( osrfMethodContext* ctx ) {
1142 if( osrfMethodVerifyContext( ctx )) {
1143 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1147 if( enforce_pcrud ) {
1148 timeout_needs_resetting = 1;
1149 const jsonObject* user = verifyUserPCRUD( ctx );
1154 // Verify that a transaction is pending
1155 const char* trans_id = getXactId( ctx );
1156 if( NULL == trans_id ) {
1157 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1158 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1162 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1165 int errnum = dbi_conn_error( writehandle, &msg );
1166 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1167 modulename, errnum, msg ? msg : "(No description available)" );
1168 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1169 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1170 if( !oilsIsDBConnected( writehandle ))
1171 osrfAppSessionPanic( ctx->session );
1174 dbi_result_free( result );
1175 jsonObject* ret = jsonNewObject( trans_id );
1176 osrfAppRespondComplete( ctx, ret );
1177 jsonObjectFree( ret );
1184 @brief Implement the "search" method.
1185 @param ctx Pointer to the method context.
1186 @return Zero if successful, or -1 if not.
1189 - authkey (PCRUD only)
1190 - WHERE clause, as jsonObject
1191 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1193 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1194 Optionally flesh linked fields.
1196 int doSearch( osrfMethodContext* ctx ) {
1197 if( osrfMethodVerifyContext( ctx )) {
1198 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1203 timeout_needs_resetting = 1;
1205 jsonObject* where_clause;
1206 jsonObject* rest_of_query;
1208 if( enforce_pcrud ) {
1209 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1210 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1212 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1213 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1216 if( !where_clause ) {
1217 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1221 // Get the class metadata
1222 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1223 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1227 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1229 osrfAppRespondComplete( ctx, NULL );
1233 // doFieldmapperSearch() now takes care of our responding for us
1234 // // Return each row to the client
1235 // jsonObject* cur = 0;
1236 // unsigned long res_idx = 0;
1238 // while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1239 // // We used to discard based on perms here, but now that's
1240 // // inside doFieldmapperSearch()
1241 // osrfAppRespond( ctx, cur );
1244 jsonObjectFree( obj );
1246 osrfAppRespondComplete( ctx, NULL );
1251 @brief Implement the "id_list" method.
1252 @param ctx Pointer to the method context.
1253 @param err Pointer through which to return an error code.
1254 @return Zero if successful, or -1 if not.
1257 - authkey (PCRUD only)
1258 - WHERE clause, as jsonObject
1259 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1261 Return to client: The primary key values for all rows of the relevant class that
1262 satisfy a specified WHERE clause.
1264 This method relies on the assumption that every class has a primary key consisting of
1267 int doIdList( osrfMethodContext* ctx ) {
1268 if( osrfMethodVerifyContext( ctx )) {
1269 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1274 timeout_needs_resetting = 1;
1276 jsonObject* where_clause;
1277 jsonObject* rest_of_query;
1279 // We use the where clause without change. But we need to massage the rest of the
1280 // query, so we work with a copy of it instead of modifying the original.
1282 if( enforce_pcrud ) {
1283 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1284 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1286 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1287 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1290 if( !where_clause ) {
1291 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1295 // Eliminate certain SQL clauses, if present.
1296 if( rest_of_query ) {
1297 jsonObjectRemoveKey( rest_of_query, "select" );
1298 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1299 jsonObjectRemoveKey( rest_of_query, "flesh" );
1300 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1302 rest_of_query = jsonNewObjectType( JSON_HASH );
1305 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1307 // Get the class metadata
1308 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1309 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1311 // Build a SELECT list containing just the primary key,
1312 // i.e. like { "classname":["keyname"] }
1313 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1315 // Load array with name of primary key
1316 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1317 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1318 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1320 jsonObjectSetKey( rest_of_query, "select", select_clause );
1325 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1327 jsonObjectFree( rest_of_query );
1329 osrfAppRespondComplete( ctx, NULL );
1333 // Return each primary key value to the client
1335 unsigned long res_idx = 0;
1336 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1337 // We used to discard based on perms here, but now that's
1338 // inside doFieldmapperSearch()
1339 osrfAppRespond( ctx,
1340 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1343 jsonObjectFree( obj );
1344 osrfAppRespondComplete( ctx, NULL );
1349 @brief Verify that we have a valid class reference.
1350 @param ctx Pointer to the method context.
1351 @param param Pointer to the method parameters.
1352 @return 1 if the class reference is valid, or zero if it isn't.
1354 The class of the method params must match the class to which the method id devoted.
1355 For PCRUD there are additional restrictions.
1357 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1359 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1360 osrfHash* class = osrfHashGet( method_meta, "class" );
1362 // Compare the method's class to the parameters' class
1363 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1365 // Oops -- they don't match. Complain.
1366 growing_buffer* msg = buffer_init( 128 );
1369 "%s: %s method for type %s was passed a %s",
1371 osrfHashGet( method_meta, "methodtype" ),
1372 osrfHashGet( class, "classname" ),
1373 param->classname ? param->classname : "(null)"
1376 char* m = buffer_release( msg );
1377 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1385 return verifyObjectPCRUD( ctx, class, param, 1 );
1391 @brief (PCRUD only) Verify that the user is properly logged in.
1392 @param ctx Pointer to the method context.
1393 @return If the user is logged in, a pointer to the user object from the authentication
1394 server; otherwise NULL.
1396 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1398 // Get the authkey (the first method parameter)
1399 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1401 // See if we have the same authkey, and a user object,
1402 // locally cached from a previous call
1403 const char* cached_authkey = getAuthkey( ctx );
1404 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1405 const jsonObject* cached_user = getUserLogin( ctx );
1410 // We have no matching authentication data in the cache. Authenticate from scratch.
1411 jsonObject* auth_object = jsonNewObject( auth );
1413 // Fetch the user object from the authentication server
1414 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1416 jsonObjectFree( auth_object );
1418 if( !user->classname || strcmp(user->classname, "au" )) {
1420 growing_buffer* msg = buffer_init( 128 );
1423 "%s: permacrud received a bad auth token: %s",
1428 char* m = buffer_release( msg );
1429 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1433 jsonObjectFree( user );
1435 } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
1436 // Failed to set audit information - But note that write_audit_info already set error information.
1437 jsonObjectFree( user );
1441 setUserLogin( ctx, user );
1442 setAuthkey( ctx, auth );
1444 // Allow ourselves up to a second before we have to reset the login timeout.
1445 // It would be nice to use some fraction of the timeout interval enforced by the
1446 // authentication server, but that value is not readily available at this point.
1447 // Instead, we use a conservative default interval.
1448 time_next_reset = time( NULL ) + 1;
1454 @brief For PCRUD: Determine whether the current user may access the current row.
1455 @param ctx Pointer to the method context.
1456 @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1457 @param obj Pointer to the row being potentially accessed.
1458 @return 1 if access is permitted, or 0 if it isn't.
1460 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1462 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1464 dbhandle = writehandle;
1466 // Figure out what class and method are involved
1467 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1468 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1471 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1472 if (rs_size_from_hash) {
1473 rs_size = *rs_size_from_hash;
1474 osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1478 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1479 // contexts we will do another lookup of the current row, even if we already have a
1480 // previously fetched row image, because the row image in hand may not include the
1481 // foreign key(s) that we need.
1483 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1484 // but they aren't implemented yet.
1487 if( *method_type == 's' || *method_type == 'i' ) {
1488 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1490 } else if( *method_type == 'u' || *method_type == 'd' ) {
1491 fetch = 1; // MUST go to the db for the object for update and delete
1494 // Get the appropriate permacrud entry from the IDL, depending on method type
1495 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1497 // No permacrud for this method type on this class
1499 growing_buffer* msg = buffer_init( 128 );
1502 "%s: %s on class %s has no permacrud IDL entry",
1504 osrfHashGet( method_metadata, "methodtype" ),
1505 osrfHashGet( class, "classname" )
1508 char* m = buffer_release( msg );
1509 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1510 "osrfMethodException", ctx->request, m );
1517 // Get the user id, and make sure the user is logged in
1518 const jsonObject* user = verifyUserPCRUD( ctx );
1520 return 0; // Not logged in? No access.
1522 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1524 // Get a list of permissions from the permacrud entry.
1525 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1526 if( permission->size == 0 ) {
1529 "No permissions required for this action (class %s), passing through",
1530 osrfHashGet(class, "classname")
1535 // Build a list of org units that own the row. This is fairly convoluted because there
1536 // are several different ways that an org unit may own the row, as defined by the
1539 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1540 // identifying an owning org_unit..
1541 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1543 // Foreign context adds a layer of indirection. The row points to some other row that
1544 // an org unit may own. The "jump" attribute, if present, adds another layer of
1546 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1548 // The following string array stores the list of org units. (We don't have a thingie
1549 // for storing lists of integers, so we fake it with a list of strings.)
1550 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1553 const char* pkey_value = NULL;
1554 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1555 // If the global_required attribute is present and true, then the only owning
1556 // org unit is the root org unit, i.e. the one with no parent.
1557 osrfLogDebug( OSRF_LOG_MARK,
1558 "global-level permissions required, fetching top of the org tree" );
1560 // no need to check perms for org tree root retrieval
1561 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1562 // check for perm at top of org tree
1563 const char* org_tree_root_id = org_tree_root( ctx );
1564 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1566 if( org_tree_root_id ) {
1567 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1568 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1570 osrfStringArrayFree( context_org_array );
1575 // If the global_required attribute is absent or false, then we look for
1576 // local and/or foreign context. In order to find the relevant foreign
1577 // keys, we must either read the relevant row from the database, or look at
1578 // the image of the row that we already have in memory.
1580 // Even if we have an image of the row in memory, that image may not include the
1581 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1582 // of the row to make sure that we have what we need.
1584 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1585 "fetching context org ids" );
1586 const char* pkey = osrfHashGet( class, "primarykey" );
1587 jsonObject *param = NULL;
1590 // There is no primary key, so we can't do a fresh lookup. Use the row
1591 // image that we already have. If it doesn't have everything we need, too bad.
1593 param = jsonObjectClone( obj );
1594 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1595 } else if( obj->classname ) {
1596 pkey_value = oilsFMGetStringConst( obj, pkey );
1598 param = jsonObjectClone( obj );
1599 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1602 pkey_value = jsonObjectGetString( obj );
1604 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1605 "of %s and retrieving from the database", pkey_value );
1609 // Fetch the row so that we can look at the foreign key(s)
1610 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1611 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1612 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1613 jsonObjectFree( _tmp_params );
1614 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1616 param = jsonObjectExtractIndex( _list, 0 );
1617 jsonObjectFree( _list );
1621 // The row doesn't exist. Complain, and deny access.
1622 osrfLogDebug( OSRF_LOG_MARK,
1623 "Object not found in the database with primary key %s of %s",
1626 growing_buffer* msg = buffer_init( 128 );
1629 "%s: no object found with primary key %s of %s",
1635 char* m = buffer_release( msg );
1636 osrfAppSessionStatus(
1638 OSRF_STATUS_INTERNALSERVERERROR,
1639 "osrfMethodException",
1648 if( local_context && local_context->size > 0 ) {
1649 // The IDL provides a list of column names for the foreign keys denoting
1650 // local context, i.e. columns identifying owing org units directly. Look up
1651 // the value of each one, and if it isn't null, add it to the list of org units.
1652 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1653 local_context->size );
1655 const char* lcontext = NULL;
1656 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1657 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1658 if( fkey_value ) { // if not null
1659 osrfStringArrayAdd( context_org_array, fkey_value );
1662 "adding class-local field %s (value: %s) to the context org list",
1664 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1670 if( foreign_context ) {
1671 unsigned long class_count = osrfHashGetCount( foreign_context );
1672 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1674 if( class_count > 0 ) {
1676 // The IDL provides a list of foreign key columns pointing to rows that
1677 // an org unit may own. Follow each link, identify the owning org unit,
1678 // and add it to the list.
1679 osrfHash* fcontext = NULL;
1680 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1681 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1682 // For each class to which a foreign key points:
1683 const char* class_name = osrfHashIteratorKey( class_itr );
1684 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1688 "%d foreign context fields(s) specified for class %s",
1689 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1693 // Get the name of the key field in the foreign table
1694 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1696 // Get the value of the foreign key pointing to the foreign table
1697 char* foreign_pkey_value =
1698 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1699 if( !foreign_pkey_value )
1700 continue; // Foreign key value is null; skip it
1702 // Look up the row to which the foreign key points
1703 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1705 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1706 jsonObject* _list = doFieldmapperSearch(
1707 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1708 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1710 jsonObject* _fparam = NULL;
1711 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1712 _fparam = jsonObjectExtractIndex( _list, 0 );
1714 jsonObjectFree( _tmp_params );
1715 jsonObjectFree( _list );
1717 // At this point _fparam either points to the row identified by the
1718 // foreign key, or it's NULL (no such row found).
1720 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1722 const char* bad_class = NULL; // For noting failed lookups
1724 bad_class = class_name; // Referenced row not found
1725 else if( jump_list ) {
1726 // Follow a chain of rows, linked by foreign keys, to find an owner
1727 const char* flink = NULL;
1729 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1730 // For each entry in the jump list. Each entry (i.e. flink) is
1731 // the name of a foreign key column in the current row.
1733 // From the IDL, get the linkage information for the next jump
1734 osrfHash* foreign_link_hash =
1735 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1737 // Get the class metadata for the class
1738 // to which the foreign key points
1739 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1740 osrfHashGet( foreign_link_hash, "class" ));
1742 // Get the name of the referenced key of that class
1743 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1745 // Get the value of the foreign key pointing to that class
1746 free( foreign_pkey_value );
1747 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1748 if( !foreign_pkey_value )
1749 break; // Foreign key is null; quit looking
1751 // Build a WHERE clause for the lookup
1752 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1755 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1756 _tmp_params, NULL, &err );
1758 // Get the resulting row
1759 jsonObjectFree( _fparam );
1760 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1761 _fparam = jsonObjectExtractIndex( _list, 0 );
1763 // Referenced row not found
1765 bad_class = osrfHashGet( foreign_link_hash, "class" );
1768 jsonObjectFree( _tmp_params );
1769 jsonObjectFree( _list );
1775 // We had a foreign key pointing to such-and-such a row, but then
1776 // we couldn't fetch that row. The data in the database are in an
1777 // inconsistent state; the database itself may even be corrupted.
1778 growing_buffer* msg = buffer_init( 128 );
1781 "%s: no object of class %s found with primary key %s of %s",
1785 foreign_pkey_value ? foreign_pkey_value : "(null)"
1788 char* m = buffer_release( msg );
1789 osrfAppSessionStatus(
1791 OSRF_STATUS_INTERNALSERVERERROR,
1792 "osrfMethodException",
1798 osrfHashIteratorFree( class_itr );
1799 free( foreign_pkey_value );
1800 jsonObjectFree( param );
1805 free( foreign_pkey_value );
1808 // Examine each context column of the foreign row,
1809 // and add its value to the list of org units.
1811 const char* foreign_field = NULL;
1812 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1813 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1814 osrfStringArrayAdd( context_org_array,
1815 oilsFMGetStringConst( _fparam, foreign_field ));
1816 osrfLogDebug( OSRF_LOG_MARK,
1817 "adding foreign class %s field %s (value: %s) "
1818 "to the context org list",
1821 osrfStringArrayGetString(
1822 context_org_array, context_org_array->size - 1 )
1826 jsonObjectFree( _fparam );
1830 osrfHashIteratorFree( class_itr );
1834 jsonObjectFree( param );
1837 const char* context_org = NULL;
1838 const char* perm = NULL;
1841 // For every combination of permission and context org unit: call a stored procedure
1842 // to determine if the user has this permission in the context of this org unit.
1843 // If the answer is yes at any point, then we're done, and the user has permission.
1844 // In other words permissions are additive.
1846 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1849 osrfStringArray* pcache = NULL;
1850 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
1851 pcache = getPermLocationCache(ctx, perm);
1854 pcache = osrfNewStringArray(0);
1856 result = dbi_conn_queryf(
1858 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
1866 "Received a result for permission [%s] for user %d",
1871 if( dbi_result_first_row( result )) {
1873 jsonObject* return_val = oilsMakeJSONFromResult( result );
1874 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
1875 jsonObjectFree( return_val );
1876 } while( dbi_result_next_row( result ));
1878 setPermLocationCache(ctx, perm, pcache);
1881 dbi_result_free( result );
1887 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1889 if (rs_size > perm_at_threshold) {
1890 if (osrfStringArrayContains( pcache, context_org )) {
1899 "Checking object permission [%s] for user %d "
1900 "on object %s (class %s) at org %d",
1904 osrfHashGet( class, "classname" ),
1908 result = dbi_conn_queryf(
1910 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1913 osrfHashGet( class, "classname" ),
1921 "Received a result for object permission [%s] "
1922 "for user %d on object %s (class %s) at org %d",
1926 osrfHashGet( class, "classname" ),
1930 if( dbi_result_first_row( result )) {
1931 jsonObject* return_val = oilsMakeJSONFromResult( result );
1932 const char* has_perm = jsonObjectGetString(
1933 jsonObjectGetKeyConst( return_val, "has_perm" ));
1937 "Status of object permission [%s] for user %d "
1938 "on object %s (class %s) at org %d is %s",
1942 osrfHashGet(class, "classname"),
1947 if( *has_perm == 't' )
1949 jsonObjectFree( return_val );
1952 dbi_result_free( result );
1957 int errnum = dbi_conn_error( writehandle, &msg );
1958 osrfLogWarning( OSRF_LOG_MARK,
1959 "Unable to call check object permissions: %d, %s",
1960 errnum, msg ? msg : "(No description available)" );
1961 if( !oilsIsDBConnected( writehandle ))
1962 osrfAppSessionPanic( ctx->session );
1966 if (rs_size > perm_at_threshold) break;
1968 osrfLogDebug( OSRF_LOG_MARK,
1969 "Checking non-object permission [%s] for user %d at org %d",
1970 perm, userid, atoi(context_org) );
1971 result = dbi_conn_queryf(
1973 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1980 osrfLogDebug( OSRF_LOG_MARK,
1981 "Received a result for permission [%s] for user %d at org %d",
1982 perm, userid, atoi( context_org ));
1983 if( dbi_result_first_row( result )) {
1984 jsonObject* return_val = oilsMakeJSONFromResult( result );
1985 const char* has_perm = jsonObjectGetString(
1986 jsonObjectGetKeyConst( return_val, "has_perm" ));
1987 osrfLogDebug( OSRF_LOG_MARK,
1988 "Status of permission [%s] for user %d at org %d is [%s]",
1989 perm, userid, atoi( context_org ), has_perm );
1990 if( *has_perm == 't' )
1992 jsonObjectFree( return_val );
1995 dbi_result_free( result );
2000 int errnum = dbi_conn_error( writehandle, &msg );
2001 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
2002 errnum, msg ? msg : "(No description available)" );
2003 if( !oilsIsDBConnected( writehandle ))
2004 osrfAppSessionPanic( ctx->session );
2013 osrfStringArrayFree( context_org_array );
2019 @brief Look up the root of the org_unit tree.
2020 @param ctx Pointer to the method context.
2021 @return The id of the root org unit, as a character string.
2023 Query actor.org_unit where parent_ou is null, and return the id as a string.
2025 This function assumes that there is only one root org unit, i.e. that we
2026 have a single tree, not a forest.
2028 The calling code is responsible for freeing the returned string.
2030 static const char* org_tree_root( osrfMethodContext* ctx ) {
2032 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
2033 static time_t last_lookup_time = 0;
2034 time_t current_time = time( NULL );
2036 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2037 // We successfully looked this up less than an hour ago.
2038 // It's not likely to have changed since then.
2039 return strdup( cached_root_id );
2041 last_lookup_time = current_time;
2044 jsonObject* where_clause = single_hash( "parent_ou", NULL );
2045 jsonObject* result = doFieldmapperSearch(
2046 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2047 jsonObjectFree( where_clause );
2049 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2052 jsonObjectFree( result );
2054 growing_buffer* msg = buffer_init( 128 );
2055 OSRF_BUFFER_ADD( msg, modulename );
2056 OSRF_BUFFER_ADD( msg,
2057 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2059 char* m = buffer_release( msg );
2060 osrfAppSessionStatus( ctx->session,
2061 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2064 cached_root_id[ 0 ] = '\0';
2068 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2069 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2071 strcpy( cached_root_id, root_org_unit_id );
2072 jsonObjectFree( result );
2073 return cached_root_id;
2077 @brief Create a JSON_HASH with a single key/value pair.
2078 @param key The key of the key/value pair.
2079 @param value the value of the key/value pair.
2080 @return Pointer to a newly created jsonObject of type JSON_HASH.
2082 The value of the key/value is either a string or (if @a value is NULL) a null.
2084 static jsonObject* single_hash( const char* key, const char* value ) {
2086 if( ! key ) key = "";
2088 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2089 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2094 int doCreate( osrfMethodContext* ctx ) {
2095 if(osrfMethodVerifyContext( ctx )) {
2096 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2101 timeout_needs_resetting = 1;
2103 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2104 jsonObject* target = NULL;
2105 jsonObject* options = NULL;
2107 if( enforce_pcrud ) {
2108 target = jsonObjectGetIndex( ctx->params, 1 );
2109 options = jsonObjectGetIndex( ctx->params, 2 );
2111 target = jsonObjectGetIndex( ctx->params, 0 );
2112 options = jsonObjectGetIndex( ctx->params, 1 );
2115 if( !verifyObjectClass( ctx, target )) {
2116 osrfAppRespondComplete( ctx, NULL );
2120 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2122 const char* trans_id = getXactId( ctx );
2124 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2126 osrfAppSessionStatus(
2128 OSRF_STATUS_BADREQUEST,
2129 "osrfMethodException",
2131 "No active transaction -- required for CREATE"
2133 osrfAppRespondComplete( ctx, NULL );
2137 // The following test is harmless but redundant. If a class is
2138 // readonly, we don't register a create method for it.
2139 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2140 osrfAppSessionStatus(
2142 OSRF_STATUS_BADREQUEST,
2143 "osrfMethodException",
2145 "Cannot INSERT readonly class"
2147 osrfAppRespondComplete( ctx, NULL );
2151 // Set the last_xact_id
2152 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2154 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2155 trans_id, target->classname, index);
2156 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2159 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2161 dbhandle = writehandle;
2163 osrfHash* fields = osrfHashGet( meta, "fields" );
2164 char* pkey = osrfHashGet( meta, "primarykey" );
2165 char* seq = osrfHashGet( meta, "sequence" );
2167 growing_buffer* table_buf = buffer_init( 128 );
2168 growing_buffer* col_buf = buffer_init( 128 );
2169 growing_buffer* val_buf = buffer_init( 128 );
2171 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2172 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2173 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2174 buffer_add( val_buf,"VALUES (" );
2178 osrfHash* field = NULL;
2179 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2180 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2182 const char* field_name = osrfHashIteratorKey( field_itr );
2184 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2187 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2190 if( field_object && field_object->classname ) {
2191 value = oilsFMGetString(
2193 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2195 } else if( field_object && JSON_BOOL == field_object->type ) {
2196 if( jsonBoolIsTrue( field_object ) )
2197 value = strdup( "t" );
2199 value = strdup( "f" );
2201 value = jsonObjectToSimpleString( field_object );
2207 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2208 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2211 buffer_add( col_buf, field_name );
2213 if( !field_object || field_object->type == JSON_NULL ) {
2214 buffer_add( val_buf, "DEFAULT" );
2216 } else if( !strcmp( get_primitive( field ), "number" )) {
2217 const char* numtype = get_datatype( field );
2218 if( !strcmp( numtype, "INT8" )) {
2219 buffer_fadd( val_buf, "%lld", atoll( value ));
2221 } else if( !strcmp( numtype, "INT" )) {
2222 buffer_fadd( val_buf, "%d", atoi( value ));
2224 } else if( !strcmp( numtype, "NUMERIC" )) {
2225 buffer_fadd( val_buf, "%f", atof( value ));
2228 if( dbi_conn_quote_string( writehandle, &value )) {
2229 OSRF_BUFFER_ADD( val_buf, value );
2232 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2233 osrfAppSessionStatus(
2235 OSRF_STATUS_INTERNALSERVERERROR,
2236 "osrfMethodException",
2238 "Error quoting string -- please see the error log for more details"
2241 buffer_free( table_buf );
2242 buffer_free( col_buf );
2243 buffer_free( val_buf );
2244 osrfAppRespondComplete( ctx, NULL );
2252 osrfHashIteratorFree( field_itr );
2254 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2255 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2257 char* table_str = buffer_release( table_buf );
2258 char* col_str = buffer_release( col_buf );
2259 char* val_str = buffer_release( val_buf );
2260 growing_buffer* sql = buffer_init( 128 );
2261 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2266 char* query = buffer_release( sql );
2268 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2270 jsonObject* obj = NULL;
2273 dbi_result result = dbi_conn_query( writehandle, query );
2275 obj = jsonNewObject( NULL );
2277 int errnum = dbi_conn_error( writehandle, &msg );
2280 "%s ERROR inserting %s object using query [%s]: %d %s",
2282 osrfHashGet(meta, "fieldmapper"),
2285 msg ? msg : "(No description available)"
2287 osrfAppSessionStatus(
2289 OSRF_STATUS_INTERNALSERVERERROR,
2290 "osrfMethodException",
2292 "INSERT error -- please see the error log for more details"
2294 if( !oilsIsDBConnected( writehandle ))
2295 osrfAppSessionPanic( ctx->session );
2298 dbi_result_free( result );
2300 char* id = oilsFMGetString( target, pkey );
2302 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2303 growing_buffer* _id = buffer_init( 10 );
2304 buffer_fadd( _id, "%lld", new_id );
2305 id = buffer_release( _id );
2308 // Find quietness specification, if present
2309 const char* quiet_str = NULL;
2311 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2313 quiet_str = jsonObjectGetString( quiet_obj );
2316 if( str_is_true( quiet_str )) { // if quietness is specified
2317 obj = jsonNewObject( id );
2321 // Fetch the row that we just inserted, so that we can return it to the client
2322 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2323 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2326 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2330 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2332 jsonObjectFree( list );
2333 jsonObjectFree( where_clause );
2340 osrfAppRespondComplete( ctx, obj );
2341 jsonObjectFree( obj );
2346 @brief Implement the retrieve method.
2347 @param ctx Pointer to the method context.
2348 @param err Pointer through which to return an error code.
2349 @return If successful, a pointer to the result to be returned to the client;
2352 From the method's class, fetch a row with a specified value in the primary key. This
2353 method relies on the database design convention that a primary key consists of a single
2357 - authkey (PCRUD only)
2358 - value of the primary key for the desired row, for building the WHERE clause
2359 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2361 Return to client: One row from the query.
2363 int doRetrieve( osrfMethodContext* ctx ) {
2364 if(osrfMethodVerifyContext( ctx )) {
2365 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2370 timeout_needs_resetting = 1;
2375 if( enforce_pcrud ) {
2380 // Get the class metadata
2381 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2383 // Get the value of the primary key, from a method parameter
2384 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2388 "%s retrieving %s object with primary key value of %s",
2390 osrfHashGet( class_def, "fieldmapper" ),
2391 jsonObjectGetString( id_obj )
2394 // Build a WHERE clause based on the key value
2395 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2398 osrfHashGet( class_def, "primarykey" ), // name of key column
2399 jsonObjectClone( id_obj ) // value of key column
2402 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2406 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2408 jsonObjectFree( where_clause );
2410 osrfAppRespondComplete( ctx, NULL );
2414 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2415 jsonObjectFree( list );
2417 if( enforce_pcrud ) {
2418 // no result, skip this entirely
2419 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2420 jsonObjectFree( obj );
2422 growing_buffer* msg = buffer_init( 128 );
2423 OSRF_BUFFER_ADD( msg, modulename );
2424 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2426 char* m = buffer_release( msg );
2427 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2431 osrfAppRespondComplete( ctx, NULL );
2436 // doFieldmapperSearch() now does the responding for us
2437 //osrfAppRespondComplete( ctx, obj );
2438 osrfAppRespondComplete( ctx, NULL );
2440 jsonObjectFree( obj );
2445 @brief Translate a numeric value to a string representation for the database.
2446 @param field Pointer to the IDL field definition.
2447 @param value Pointer to a jsonObject holding the value of a field.
2448 @return Pointer to a newly allocated string.
2450 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2451 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2452 or (what is worse) valid SQL that is wrong.
2454 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2456 The calling code is responsible for freeing the resulting string by calling free().
2458 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2459 growing_buffer* val_buf = buffer_init( 32 );
2460 const char* numtype = get_datatype( field );
2462 // For historical reasons the following contains cruft that could be cleaned up.
2463 if( !strncmp( numtype, "INT", 3 ) ) {
2464 if( value->type == JSON_NUMBER )
2465 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2466 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2468 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2471 } else if( !strcmp( numtype, "NUMERIC" )) {
2472 if( value->type == JSON_NUMBER )
2473 buffer_fadd( val_buf, jsonObjectGetString( value ));
2475 buffer_fadd( val_buf, jsonObjectGetString( value ));
2479 // Presumably this was really intended to be a string, so quote it
2480 char* str = jsonObjectToSimpleString( value );
2481 if( dbi_conn_quote_string( dbhandle, &str )) {
2482 OSRF_BUFFER_ADD( val_buf, str );
2485 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2487 buffer_free( val_buf );
2492 return buffer_release( val_buf );
2495 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2496 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2497 growing_buffer* sql_buf = buffer_init( 32 );
2503 osrfHashGet( field, "name" )
2507 buffer_add( sql_buf, "IN (" );
2508 } else if( !strcasecmp( op,"not in" )) {
2509 buffer_add( sql_buf, "NOT IN (" );
2511 buffer_add( sql_buf, "IN (" );
2514 if( node->type == JSON_HASH ) {
2515 // subquery predicate
2516 char* subpred = buildQuery( ctx, node, SUBSELECT );
2518 buffer_free( sql_buf );
2522 buffer_add( sql_buf, subpred );
2525 } else if( node->type == JSON_ARRAY ) {
2526 // literal value list
2527 int in_item_index = 0;
2528 int in_item_first = 1;
2529 const jsonObject* in_item;
2530 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2535 buffer_add( sql_buf, ", " );
2538 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2539 osrfLogError( OSRF_LOG_MARK,
2540 "%s: Expected string or number within IN list; found %s",
2541 modulename, json_type( in_item->type ) );
2542 buffer_free( sql_buf );
2546 // Append the literal value -- quoted if not a number
2547 if( JSON_NUMBER == in_item->type ) {
2548 char* val = jsonNumberToDBString( field, in_item );
2549 OSRF_BUFFER_ADD( sql_buf, val );
2552 } else if( !strcmp( get_primitive( field ), "number" )) {
2553 char* val = jsonNumberToDBString( field, in_item );
2554 OSRF_BUFFER_ADD( sql_buf, val );
2558 char* key_string = jsonObjectToSimpleString( in_item );
2559 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2560 OSRF_BUFFER_ADD( sql_buf, key_string );
2563 osrfLogError( OSRF_LOG_MARK,
2564 "%s: Error quoting key string [%s]", modulename, key_string );
2566 buffer_free( sql_buf );
2572 if( in_item_first ) {
2573 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2574 buffer_free( sql_buf );
2578 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2579 modulename, json_type( node->type ));
2580 buffer_free( sql_buf );
2584 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2586 return buffer_release( sql_buf );
2589 // Receive a JSON_ARRAY representing a function call. The first
2590 // entry in the array is the function name. The rest are parameters.
2591 static char* searchValueTransform( const jsonObject* array ) {
2593 if( array->size < 1 ) {
2594 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2598 // Get the function name
2599 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2600 if( func_item->type != JSON_STRING ) {
2601 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2602 modulename, json_type( func_item->type ));
2606 growing_buffer* sql_buf = buffer_init( 32 );
2608 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2609 OSRF_BUFFER_ADD( sql_buf, "( " );
2611 // Get the parameters
2612 int func_item_index = 1; // We already grabbed the zeroth entry
2613 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2615 // Add a separator comma, if we need one
2616 if( func_item_index > 2 )
2617 buffer_add( sql_buf, ", " );
2619 // Add the current parameter
2620 if( func_item->type == JSON_NULL ) {
2621 buffer_add( sql_buf, "NULL" );
2623 if( func_item->type == JSON_BOOL ) {
2624 if( jsonBoolIsTrue(func_item) ) {
2625 buffer_add( sql_buf, "TRUE" );
2627 buffer_add( sql_buf, "FALSE" );
2630 char* val = jsonObjectToSimpleString( func_item );
2631 if( dbi_conn_quote_string( dbhandle, &val )) {
2632 OSRF_BUFFER_ADD( sql_buf, val );
2635 osrfLogError( OSRF_LOG_MARK,
2636 "%s: Error quoting key string [%s]", modulename, val );
2637 buffer_free( sql_buf );
2645 buffer_add( sql_buf, " )" );
2647 return buffer_release( sql_buf );
2650 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2651 const jsonObject* node, const char* op ) {
2653 if( ! is_good_operator( op ) ) {
2654 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2658 char* val = searchValueTransform( node );
2662 growing_buffer* sql_buf = buffer_init( 32 );
2667 osrfHashGet( field, "name" ),
2674 return buffer_release( sql_buf );
2677 // class_alias is a class name or other table alias
2678 // field is a field definition as stored in the IDL
2679 // node comes from the method parameter, and may represent an entry in the SELECT list
2680 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2681 const jsonObject* node ) {
2682 growing_buffer* sql_buf = buffer_init( 32 );
2684 const char* field_transform = jsonObjectGetString(
2685 jsonObjectGetKeyConst( node, "transform" ) );
2686 const char* transform_subcolumn = jsonObjectGetString(
2687 jsonObjectGetKeyConst( node, "result_field" ) );
2689 if( transform_subcolumn ) {
2690 if( ! is_identifier( transform_subcolumn ) ) {
2691 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2692 modulename, transform_subcolumn );
2693 buffer_free( sql_buf );
2696 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2699 if( field_transform ) {
2701 if( ! is_identifier( field_transform ) ) {
2702 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2703 modulename, field_transform );
2704 buffer_free( sql_buf );
2708 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2709 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2710 field_transform, class_alias, osrfHashGet( field, "name" ));
2712 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2713 field_transform, class_alias, osrfHashGet( field, "name" ));
2716 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2719 if( array->type != JSON_ARRAY ) {
2720 osrfLogError( OSRF_LOG_MARK,
2721 "%s: Expected JSON_ARRAY for function params; found %s",
2722 modulename, json_type( array->type ) );
2723 buffer_free( sql_buf );
2726 int func_item_index = 0;
2727 jsonObject* func_item;
2728 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2730 char* val = jsonObjectToSimpleString( func_item );
2733 buffer_add( sql_buf, ",NULL" );
2734 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2735 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2736 OSRF_BUFFER_ADD( sql_buf, val );
2738 osrfLogError( OSRF_LOG_MARK,
2739 "%s: Error quoting key string [%s]", modulename, val );
2741 buffer_free( sql_buf );
2748 buffer_add( sql_buf, " )" );
2751 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2754 if( transform_subcolumn )
2755 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2757 return buffer_release( sql_buf );
2760 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2761 const jsonObject* node, const char* op ) {
2763 if( ! is_good_operator( op ) ) {
2764 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2768 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2769 if( ! field_transform )
2772 int extra_parens = 0; // boolean
2774 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2776 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2778 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2780 free( field_transform );
2784 } else if( value_obj->type == JSON_ARRAY ) {
2785 value = searchValueTransform( value_obj );
2787 osrfLogError( OSRF_LOG_MARK,
2788 "%s: Error building value transform for field transform", modulename );
2789 free( field_transform );
2792 } else if( value_obj->type == JSON_HASH ) {
2793 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2795 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2797 free( field_transform );
2801 } else if( value_obj->type == JSON_NUMBER ) {
2802 value = jsonNumberToDBString( field, value_obj );
2803 } else if( value_obj->type == JSON_NULL ) {
2804 osrfLogError( OSRF_LOG_MARK,
2805 "%s: Error building predicate for field transform: null value", modulename );
2806 free( field_transform );
2808 } else if( value_obj->type == JSON_BOOL ) {
2809 osrfLogError( OSRF_LOG_MARK,
2810 "%s: Error building predicate for field transform: boolean value", modulename );
2811 free( field_transform );
2814 if( !strcmp( get_primitive( field ), "number") ) {
2815 value = jsonNumberToDBString( field, value_obj );
2817 value = jsonObjectToSimpleString( value_obj );
2818 if( !dbi_conn_quote_string( dbhandle, &value )) {
2819 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2820 modulename, value );
2822 free( field_transform );
2828 const char* left_parens = "";
2829 const char* right_parens = "";
2831 if( extra_parens ) {
2836 const char* right_percent = "";
2837 const char* real_op = op;
2839 if( !strcasecmp( op, "startwith") ) {
2841 right_percent = "|| '%'";
2844 growing_buffer* sql_buf = buffer_init( 32 );
2848 "%s%s %s %s %s%s %s%s",
2860 free( field_transform );
2862 return buffer_release( sql_buf );
2865 static char* searchSimplePredicate( const char* op, const char* class_alias,
2866 osrfHash* field, const jsonObject* node ) {
2868 if( ! is_good_operator( op ) ) {
2869 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2875 // Get the value to which we are comparing the specified column
2876 if( node->type != JSON_NULL ) {
2877 if( node->type == JSON_NUMBER ) {
2878 val = jsonNumberToDBString( field, node );
2879 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2880 val = jsonNumberToDBString( field, node );
2882 val = jsonObjectToSimpleString( node );
2887 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2888 // Value is not numeric; enclose it in quotes
2889 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2890 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2897 // Compare to a null value
2898 val = strdup( "NULL" );
2899 if( strcmp( op, "=" ))
2905 growing_buffer* sql_buf = buffer_init( 32 );
2906 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2907 char* pred = buffer_release( sql_buf );
2914 static char* searchBETWEENPredicate( const char* class_alias,
2915 osrfHash* field, const jsonObject* node ) {
2917 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2918 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2920 if( NULL == y_node ) {
2921 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2924 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2925 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2932 if( !strcmp( get_primitive( field ), "number") ) {
2933 x_string = jsonNumberToDBString( field, x_node );
2934 y_string = jsonNumberToDBString( field, y_node );
2937 x_string = jsonObjectToSimpleString( x_node );
2938 y_string = jsonObjectToSimpleString( y_node );
2939 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2940 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2941 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2942 modulename, x_string, y_string );
2949 growing_buffer* sql_buf = buffer_init( 32 );
2950 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2951 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2955 return buffer_release( sql_buf );
2958 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2959 jsonObject* node, osrfMethodContext* ctx ) {
2962 if( node->type == JSON_ARRAY ) { // equality IN search
2963 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2964 } else if( node->type == JSON_HASH ) { // other search
2965 jsonIterator* pred_itr = jsonNewIterator( node );
2966 if( !jsonIteratorHasNext( pred_itr ) ) {
2967 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2968 modulename, osrfHashGet(field, "name" ));
2970 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2972 // Verify that there are no additional predicates
2973 if( jsonIteratorHasNext( pred_itr ) ) {
2974 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2975 modulename, osrfHashGet(field, "name" ));
2976 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2977 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2978 else if( !(strcasecmp( pred_itr->key,"in" ))
2979 || !(strcasecmp( pred_itr->key,"not in" )) )
2980 pred = searchINPredicate(
2981 class_info->alias, field, pred_node, pred_itr->key, ctx );
2982 else if( pred_node->type == JSON_ARRAY )
2983 pred = searchFunctionPredicate(
2984 class_info->alias, field, pred_node, pred_itr->key );
2985 else if( pred_node->type == JSON_HASH )
2986 pred = searchFieldTransformPredicate(
2987 class_info, field, pred_node, pred_itr->key );
2989 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2991 jsonIteratorFree( pred_itr );
2993 } else if( node->type == JSON_NULL ) { // IS NULL search
2994 growing_buffer* _p = buffer_init( 64 );
2997 "\"%s\".%s IS NULL",
2999 osrfHashGet( field, "name" )
3001 pred = buffer_release( _p );
3002 } else { // equality search
3003 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3022 field : call_number,
3038 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3040 const jsonObject* working_hash;
3041 jsonObject* freeable_hash = NULL;
3043 if( join_hash->type == JSON_HASH ) {
3044 working_hash = join_hash;
3045 } else if( join_hash->type == JSON_STRING ) {
3046 // turn it into a JSON_HASH by creating a wrapper
3047 // around a copy of the original
3048 const char* _tmp = jsonObjectGetString( join_hash );
3049 freeable_hash = jsonNewObjectType( JSON_HASH );
3050 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3051 working_hash = freeable_hash;
3055 "%s: JOIN failed; expected JSON object type not found",
3061 growing_buffer* join_buf = buffer_init( 128 );
3062 const char* leftclass = left_info->class_name;
3064 jsonObject* snode = NULL;
3065 jsonIterator* search_itr = jsonNewIterator( working_hash );
3067 while ( (snode = jsonIteratorNext( search_itr )) ) {
3068 const char* right_alias = search_itr->key;
3070 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3072 class = right_alias;
3074 const ClassInfo* right_info = add_joined_class( right_alias, class );
3078 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3082 jsonIteratorFree( search_itr );
3083 buffer_free( join_buf );
3085 jsonObjectFree( freeable_hash );
3088 osrfHash* links = right_info->links;
3089 const char* table = right_info->source_def;
3091 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3092 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3094 if( field && !fkey ) {
3095 // Look up the corresponding join column in the IDL.
3096 // The link must be defined in the child table,
3097 // and point to the right parent table.
3098 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3099 const char* reltype = NULL;
3100 const char* other_class = NULL;
3101 reltype = osrfHashGet( idl_link, "reltype" );
3102 if( reltype && strcmp( reltype, "has_many" ) )
3103 other_class = osrfHashGet( idl_link, "class" );
3104 if( other_class && !strcmp( other_class, leftclass ) )
3105 fkey = osrfHashGet( idl_link, "key" );
3109 "%s: JOIN failed. No link defined from %s.%s to %s",
3115 buffer_free( join_buf );
3117 jsonObjectFree( freeable_hash );
3118 jsonIteratorFree( search_itr );
3122 } else if( !field && fkey ) {
3123 // Look up the corresponding join column in the IDL.
3124 // The link must be defined in the child table,
3125 // and point to the right parent table.
3126 osrfHash* left_links = left_info->links;
3127 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3128 const char* reltype = NULL;
3129 const char* other_class = NULL;
3130 reltype = osrfHashGet( idl_link, "reltype" );
3131 if( reltype && strcmp( reltype, "has_many" ) )
3132 other_class = osrfHashGet( idl_link, "class" );
3133 if( other_class && !strcmp( other_class, class ) )
3134 field = osrfHashGet( idl_link, "key" );
3138 "%s: JOIN failed. No link defined from %s.%s to %s",
3144 buffer_free( join_buf );
3146 jsonObjectFree( freeable_hash );
3147 jsonIteratorFree( search_itr );
3151 } else if( !field && !fkey ) {
3152 osrfHash* left_links = left_info->links;
3154 // For each link defined for the left class:
3155 // see if the link references the joined class
3156 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3157 osrfHash* curr_link = NULL;
3158 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3159 const char* other_class = osrfHashGet( curr_link, "class" );
3160 if( other_class && !strcmp( other_class, class ) ) {
3162 // In the IDL, the parent class doesn't always know then names of the child
3163 // columns that are pointing to it, so don't use that end of the link
3164 const char* reltype = osrfHashGet( curr_link, "reltype" );
3165 if( reltype && strcmp( reltype, "has_many" ) ) {
3166 // Found a link between the classes
3167 fkey = osrfHashIteratorKey( itr );
3168 field = osrfHashGet( curr_link, "key" );
3173 osrfHashIteratorFree( itr );
3175 if( !field || !fkey ) {
3176 // Do another such search, with the classes reversed
3178 // For each link defined for the joined class:
3179 // see if the link references the left class
3180 osrfHashIterator* itr = osrfNewHashIterator( links );
3181 osrfHash* curr_link = NULL;
3182 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3183 const char* other_class = osrfHashGet( curr_link, "class" );
3184 if( other_class && !strcmp( other_class, leftclass ) ) {
3186 // In the IDL, the parent class doesn't know then names of the child
3187 // columns that are pointing to it, so don't use that end of the link
3188 const char* reltype = osrfHashGet( curr_link, "reltype" );
3189 if( reltype && strcmp( reltype, "has_many" ) ) {
3190 // Found a link between the classes
3191 field = osrfHashIteratorKey( itr );
3192 fkey = osrfHashGet( curr_link, "key" );
3197 osrfHashIteratorFree( itr );
3200 if( !field || !fkey ) {
3203 "%s: JOIN failed. No link defined between %s and %s",
3208 buffer_free( join_buf );
3210 jsonObjectFree( freeable_hash );
3211 jsonIteratorFree( search_itr );
3216 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3218 if( !strcasecmp( type,"left" )) {
3219 buffer_add( join_buf, " LEFT JOIN" );
3220 } else if( !strcasecmp( type,"right" )) {
3221 buffer_add( join_buf, " RIGHT JOIN" );
3222 } else if( !strcasecmp( type,"full" )) {
3223 buffer_add( join_buf, " FULL JOIN" );
3225 buffer_add( join_buf, " INNER JOIN" );
3228 buffer_add( join_buf, " INNER JOIN" );
3231 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3232 table, right_alias, right_alias, field, left_info->alias, fkey );
3234 // Add any other join conditions as specified by "filter"
3235 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3237 const char* filter_op = jsonObjectGetString(
3238 jsonObjectGetKeyConst( snode, "filter_op" ) );
3239 if( filter_op && !strcasecmp( "or",filter_op )) {
3240 buffer_add( join_buf, " OR " );
3242 buffer_add( join_buf, " AND " );
3245 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3247 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3248 OSRF_BUFFER_ADD( join_buf, jpred );
3253 "%s: JOIN failed. Invalid conditional expression.",
3256 jsonIteratorFree( search_itr );
3257 buffer_free( join_buf );
3259 jsonObjectFree( freeable_hash );
3264 buffer_add( join_buf, " ) " );
3266 // Recursively add a nested join, if one is present
3267 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3269 char* jpred = searchJOIN( join_filter, right_info );
3271 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3272 OSRF_BUFFER_ADD( join_buf, jpred );
3275 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3276 jsonIteratorFree( search_itr );
3277 buffer_free( join_buf );
3279 jsonObjectFree( freeable_hash );
3286 jsonObjectFree( freeable_hash );
3287 jsonIteratorFree( search_itr );
3289 return buffer_release( join_buf );
3294 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3295 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3296 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3298 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3300 search_hash is the JSON expression of the conditions.
3301 meta is the class definition from the IDL, for the relevant table.
3302 opjoin_type indicates whether multiple conditions, if present, should be
3303 connected by AND or OR.
3304 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3305 to pass it to other functions -- and all they do with it is to use the session
3306 and request members to send error messages back to the client.
3310 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3311 int opjoin_type, osrfMethodContext* ctx ) {
3315 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3316 "opjoin_type = %d, ctx addr = %p",
3319 class_info->class_def,
3324 growing_buffer* sql_buf = buffer_init( 128 );
3326 jsonObject* node = NULL;
3329 if( search_hash->type == JSON_ARRAY ) {
3330 if( 0 == search_hash->size ) {
3333 "%s: Invalid predicate structure: empty JSON array",
3336 buffer_free( sql_buf );
3340 unsigned long i = 0;
3341 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3345 if( opjoin_type == OR_OP_JOIN )
3346 buffer_add( sql_buf, " OR " );
3348 buffer_add( sql_buf, " AND " );
3351 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3353 buffer_free( sql_buf );
3357 buffer_fadd( sql_buf, "( %s )", subpred );
3361 } else if( search_hash->type == JSON_HASH ) {
3362 osrfLogDebug( OSRF_LOG_MARK,
3363 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3364 jsonIterator* search_itr = jsonNewIterator( search_hash );
3365 if( !jsonIteratorHasNext( search_itr ) ) {
3368 "%s: Invalid predicate structure: empty JSON object",
3371 jsonIteratorFree( search_itr );
3372 buffer_free( sql_buf );
3376 while( (node = jsonIteratorNext( search_itr )) ) {
3381 if( opjoin_type == OR_OP_JOIN )
3382 buffer_add( sql_buf, " OR " );
3384 buffer_add( sql_buf, " AND " );
3387 if( '+' == search_itr->key[ 0 ] ) {
3389 // This plus sign prefixes a class name or other table alias;
3390 // make sure the table alias is in scope
3391 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3392 if( ! alias_info ) {
3395 "%s: Invalid table alias \"%s\" in WHERE clause",
3399 jsonIteratorFree( search_itr );
3400 buffer_free( sql_buf );
3404 if( node->type == JSON_STRING ) {
3405 // It's the name of a column; make sure it belongs to the class
3406 const char* fieldname = jsonObjectGetString( node );
3407 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3410 "%s: Invalid column name \"%s\" in WHERE clause "
3411 "for table alias \"%s\"",
3416 jsonIteratorFree( search_itr );
3417 buffer_free( sql_buf );
3421 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3423 // It's something more complicated
3424 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3426 jsonIteratorFree( search_itr );
3427 buffer_free( sql_buf );
3431 buffer_fadd( sql_buf, "( %s )", subpred );
3434 } else if( '-' == search_itr->key[ 0 ] ) {
3435 if( !strcasecmp( "-or", search_itr->key )) {
3436 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3438 jsonIteratorFree( search_itr );
3439 buffer_free( sql_buf );
3443 buffer_fadd( sql_buf, "( %s )", subpred );
3445 } else if( !strcasecmp( "-and", search_itr->key )) {
3446 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3448 jsonIteratorFree( search_itr );
3449 buffer_free( sql_buf );
3453 buffer_fadd( sql_buf, "( %s )", subpred );
3455 } else if( !strcasecmp("-not",search_itr->key) ) {
3456 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3458 jsonIteratorFree( search_itr );
3459 buffer_free( sql_buf );
3463 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3465 } else if( !strcasecmp( "-exists", search_itr->key )) {
3466 char* subpred = buildQuery( ctx, node, SUBSELECT );
3468 jsonIteratorFree( search_itr );
3469 buffer_free( sql_buf );
3473 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3475 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3476 char* subpred = buildQuery( ctx, node, SUBSELECT );
3478 jsonIteratorFree( search_itr );
3479 buffer_free( sql_buf );
3483 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3485 } else { // Invalid "minus" operator
3488 "%s: Invalid operator \"%s\" in WHERE clause",
3492 jsonIteratorFree( search_itr );
3493 buffer_free( sql_buf );
3499 const char* class = class_info->class_name;
3500 osrfHash* fields = class_info->fields;
3501 osrfHash* field = osrfHashGet( fields, search_itr->key );
3504 const char* table = class_info->source_def;
3507 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3510 table ? table : "?",
3513 jsonIteratorFree( search_itr );
3514 buffer_free( sql_buf );
3518 char* subpred = searchPredicate( class_info, field, node, ctx );
3520 buffer_free( sql_buf );
3521 jsonIteratorFree( search_itr );
3525 buffer_add( sql_buf, subpred );
3529 jsonIteratorFree( search_itr );
3532 // ERROR ... only hash and array allowed at this level
3533 char* predicate_string = jsonObjectToJSON( search_hash );
3536 "%s: Invalid predicate structure: %s",
3540 buffer_free( sql_buf );
3541 free( predicate_string );
3545 return buffer_release( sql_buf );
3548 /* Build a JSON_ARRAY of field names for a given table alias
3550 static jsonObject* defaultSelectList( const char* table_alias ) {
3555 ClassInfo* class_info = search_all_alias( table_alias );
3556 if( ! class_info ) {
3559 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3566 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3567 osrfHash* field_def = NULL;
3568 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3569 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3570 const char* field_name = osrfHashIteratorKey( field_itr );
3571 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3572 jsonObjectPush( array, jsonNewObject( field_name ) );
3575 osrfHashIteratorFree( field_itr );
3580 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3581 // The jsonObject must be a JSON_HASH with an single entry for "union",
3582 // "intersect", or "except". The data associated with this key must be an
3583 // array of hashes, each hash being a query.
3584 // Also allowed but currently ignored: entries for "order_by" and "alias".
3585 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3587 if( ! combo || combo->type != JSON_HASH )
3588 return NULL; // should be impossible; validated by caller
3590 const jsonObject* query_array = NULL; // array of subordinate queries
3591 const char* op = NULL; // name of operator, e.g. UNION
3592 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3593 int op_count = 0; // for detecting conflicting operators
3594 int excepting = 0; // boolean
3595 int all = 0; // boolean
3596 jsonObject* order_obj = NULL;
3598 // Identify the elements in the hash
3599 jsonIterator* query_itr = jsonNewIterator( combo );
3600 jsonObject* curr_obj = NULL;
3601 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3602 if( ! strcmp( "union", query_itr->key ) ) {
3605 query_array = curr_obj;
3606 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3609 query_array = curr_obj;
3610 } else if( ! strcmp( "except", query_itr->key ) ) {
3614 query_array = curr_obj;
3615 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3618 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3621 order_obj = curr_obj;
3622 } else if( ! strcmp( "alias", query_itr->key ) ) {
3623 if( curr_obj->type != JSON_STRING ) {
3624 jsonIteratorFree( query_itr );
3627 alias = jsonObjectGetString( curr_obj );
3628 } else if( ! strcmp( "all", query_itr->key ) ) {
3629 if( obj_is_true( curr_obj ) )
3633 osrfAppSessionStatus(
3635 OSRF_STATUS_INTERNALSERVERERROR,
3636 "osrfMethodException",
3638 "Malformed query; unexpected entry in query object"
3642 "%s: Unexpected entry for \"%s\" in%squery",
3647 jsonIteratorFree( query_itr );
3651 jsonIteratorFree( query_itr );
3653 // More sanity checks
3654 if( ! query_array ) {
3656 osrfAppSessionStatus(
3658 OSRF_STATUS_INTERNALSERVERERROR,
3659 "osrfMethodException",
3661 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3665 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3668 return NULL; // should be impossible...
3669 } else if( op_count > 1 ) {
3671 osrfAppSessionStatus(
3673 OSRF_STATUS_INTERNALSERVERERROR,
3674 "osrfMethodException",
3676 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3680 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3684 } if( query_array->type != JSON_ARRAY ) {
3686 osrfAppSessionStatus(
3688 OSRF_STATUS_INTERNALSERVERERROR,
3689 "osrfMethodException",
3691 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3695 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3698 json_type( query_array->type )
3701 } if( query_array->size < 2 ) {
3703 osrfAppSessionStatus(
3705 OSRF_STATUS_INTERNALSERVERERROR,
3706 "osrfMethodException",
3708 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3712 "%s:%srequires multiple queries as operands",
3717 } else if( excepting && query_array->size > 2 ) {
3719 osrfAppSessionStatus(
3721 OSRF_STATUS_INTERNALSERVERERROR,
3722 "osrfMethodException",
3724 "EXCEPT operator has too many queries as operands"
3728 "%s:EXCEPT operator has too many queries as operands",
3732 } else if( order_obj && ! alias ) {
3734 osrfAppSessionStatus(
3736 OSRF_STATUS_INTERNALSERVERERROR,
3737 "osrfMethodException",
3739 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3743 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3749 // So far so good. Now build the SQL.
3750 growing_buffer* sql = buffer_init( 256 );
3752 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3753 // Add a layer of parentheses
3754 if( flags & SUBCOMBO )
3755 OSRF_BUFFER_ADD( sql, "( " );
3757 // Traverse the query array. Each entry should be a hash.
3758 int first = 1; // boolean
3760 jsonObject* query = NULL;
3761 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3762 if( query->type != JSON_HASH ) {
3764 osrfAppSessionStatus(
3766 OSRF_STATUS_INTERNALSERVERERROR,
3767 "osrfMethodException",
3769 "Malformed query under UNION, INTERSECT or EXCEPT"
3773 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3776 json_type( query->type )
3785 OSRF_BUFFER_ADD( sql, op );
3787 OSRF_BUFFER_ADD( sql, "ALL " );
3790 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3794 "%s: Error building query under%s",
3802 OSRF_BUFFER_ADD( sql, query_str );
3805 if( flags & SUBCOMBO )
3806 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3808 if( !(flags & SUBSELECT) )
3809 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3811 return buffer_release( sql );
3814 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3815 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3816 // or "except" to indicate the type of query.
3817 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3821 osrfAppSessionStatus(
3823 OSRF_STATUS_INTERNALSERVERERROR,
3824 "osrfMethodException",
3826 "Malformed query; no query object"
3828 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3830 } else if( query->type != JSON_HASH ) {
3832 osrfAppSessionStatus(
3834 OSRF_STATUS_INTERNALSERVERERROR,
3835 "osrfMethodException",
3837 "Malformed query object"
3841 "%s: Query object is %s instead of JSON_HASH",
3843 json_type( query->type )
3848 // Determine what kind of query it purports to be, and dispatch accordingly.
3849 if( jsonObjectGetKeyConst( query, "union" ) ||
3850 jsonObjectGetKeyConst( query, "intersect" ) ||
3851 jsonObjectGetKeyConst( query, "except" )) {
3852 return doCombo( ctx, query, flags );
3854 // It is presumably a SELECT query
3856 // Push a node onto the stack for the current query. Every level of
3857 // subquery gets its own QueryFrame on the Stack.
3860 // Build an SQL SELECT statement
3863 jsonObjectGetKey( query, "select" ),
3864 jsonObjectGetKeyConst( query, "from" ),
3865 jsonObjectGetKeyConst( query, "where" ),
3866 jsonObjectGetKeyConst( query, "having" ),
3867 jsonObjectGetKeyConst( query, "order_by" ),
3868 jsonObjectGetKeyConst( query, "limit" ),
3869 jsonObjectGetKeyConst( query, "offset" ),
3878 /* method context */ osrfMethodContext* ctx,
3880 /* SELECT */ jsonObject* selhash,
3881 /* FROM */ const jsonObject* join_hash,
3882 /* WHERE */ const jsonObject* search_hash,
3883 /* HAVING */ const jsonObject* having_hash,
3884 /* ORDER BY */ const jsonObject* order_hash,
3885 /* LIMIT */ const jsonObject* limit,
3886 /* OFFSET */ const jsonObject* offset,
3887 /* flags */ int flags
3889 const char* locale = osrf_message_get_last_locale();
3891 // general tmp objects
3892 const jsonObject* tmp_const;
3893 jsonObject* selclass = NULL;
3894 jsonObject* snode = NULL;
3895 jsonObject* onode = NULL;
3897 char* string = NULL;
3898 int from_function = 0;
3903 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3905 // punt if there's no FROM clause
3906 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3909 "%s: FROM clause is missing or empty",
3913 osrfAppSessionStatus(
3915 OSRF_STATUS_INTERNALSERVERERROR,
3916 "osrfMethodException",
3918 "FROM clause is missing or empty in JSON query"
3923 // the core search class
3924 const char* core_class = NULL;
3926 // get the core class -- the only key of the top level FROM clause, or a string
3927 if( join_hash->type == JSON_HASH ) {
3928 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3929 snode = jsonIteratorNext( tmp_itr );
3931 // Populate the current QueryFrame with information
3932 // about the core class
3933 if( add_query_core( NULL, tmp_itr->key ) ) {
3935 osrfAppSessionStatus(
3937 OSRF_STATUS_INTERNALSERVERERROR,
3938 "osrfMethodException",
3940 "Unable to look up core class"
3944 core_class = curr_query->core.class_name;
3947 jsonObject* extra = jsonIteratorNext( tmp_itr );
3949 jsonIteratorFree( tmp_itr );
3952 // There shouldn't be more than one entry in join_hash
3956 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3960 osrfAppSessionStatus(
3962 OSRF_STATUS_INTERNALSERVERERROR,
3963 "osrfMethodException",
3965 "Malformed FROM clause in JSON query"
3967 return NULL; // Malformed join_hash; extra entry
3969 } else if( join_hash->type == JSON_ARRAY ) {
3970 // We're selecting from a function, not from a table
3972 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3975 } else if( join_hash->type == JSON_STRING ) {
3976 // Populate the current QueryFrame with information
3977 // about the core class
3978 core_class = jsonObjectGetString( join_hash );
3980 if( add_query_core( NULL, core_class ) ) {
3982 osrfAppSessionStatus(
3984 OSRF_STATUS_INTERNALSERVERERROR,
3985 "osrfMethodException",
3987 "Unable to look up core class"
3995 "%s: FROM clause is unexpected JSON type: %s",
3997 json_type( join_hash->type )
4000 osrfAppSessionStatus(
4002 OSRF_STATUS_INTERNALSERVERERROR,
4003 "osrfMethodException",
4005 "Ill-formed FROM clause in JSON query"
4010 // Build the join clause, if any, while filling out the list
4011 // of joined classes in the current QueryFrame.
4012 char* join_clause = NULL;
4013 if( join_hash && ! from_function ) {
4015 join_clause = searchJOIN( join_hash, &curr_query->core );
4016 if( ! join_clause ) {
4018 osrfAppSessionStatus(
4020 OSRF_STATUS_INTERNALSERVERERROR,
4021 "osrfMethodException",
4023 "Unable to construct JOIN clause(s)"
4029 // For in case we don't get a select list
4030 jsonObject* defaultselhash = NULL;
4032 // if there is no select list, build a default select list ...
4033 if( !selhash && !from_function ) {
4034 jsonObject* default_list = defaultSelectList( core_class );
4035 if( ! default_list ) {
4037 osrfAppSessionStatus(
4039 OSRF_STATUS_INTERNALSERVERERROR,
4040 "osrfMethodException",
4042 "Unable to build default SELECT clause in JSON query"
4044 free( join_clause );
4049 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4050 jsonObjectSetKey( selhash, core_class, default_list );
4053 // The SELECT clause can be encoded only by a hash
4054 if( !from_function && selhash->type != JSON_HASH ) {
4057 "%s: Expected JSON_HASH for SELECT clause; found %s",
4059 json_type( selhash->type )
4063 osrfAppSessionStatus(
4065 OSRF_STATUS_INTERNALSERVERERROR,
4066 "osrfMethodException",
4068 "Malformed SELECT clause in JSON query"
4070 free( join_clause );
4074 // If you see a null or wild card specifier for the core class, or an
4075 // empty array, replace it with a default SELECT list
4076 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4078 int default_needed = 0; // boolean
4079 if( JSON_STRING == tmp_const->type
4080 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4082 else if( JSON_NULL == tmp_const->type )
4085 if( default_needed ) {
4086 // Build a default SELECT list
4087 jsonObject* default_list = defaultSelectList( core_class );
4088 if( ! default_list ) {
4090 osrfAppSessionStatus(
4092 OSRF_STATUS_INTERNALSERVERERROR,
4093 "osrfMethodException",
4095 "Can't build default SELECT clause in JSON query"
4097 free( join_clause );
4102 jsonObjectSetKey( selhash, core_class, default_list );
4106 // temp buffers for the SELECT list and GROUP BY clause
4107 growing_buffer* select_buf = buffer_init( 128 );
4108 growing_buffer* group_buf = buffer_init( 128 );
4110 int aggregate_found = 0; // boolean
4112 // Build a select list
4113 if( from_function ) // From a function we select everything
4114 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4117 // Build the SELECT list as SQL
4121 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4122 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4124 const char* cname = selclass_itr->key;
4126 // Make sure the target relation is in the FROM clause.
4128 // At this point join_hash is a step down from the join_hash we
4129 // received as a parameter. If the original was a JSON_STRING,
4130 // then json_hash is now NULL. If the original was a JSON_HASH,
4131 // then json_hash is now the first (and only) entry in it,
4132 // denoting the core class. We've already excluded the
4133 // possibility that the original was a JSON_ARRAY, because in
4134 // that case from_function would be non-NULL, and we wouldn't
4137 // If the current table alias isn't in scope, bail out
4138 ClassInfo* class_info = search_alias( cname );
4139 if( ! class_info ) {
4142 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4147 osrfAppSessionStatus(
4149 OSRF_STATUS_INTERNALSERVERERROR,
4150 "osrfMethodException",
4152 "Selected class not in FROM clause in JSON query"
4154 jsonIteratorFree( selclass_itr );
4155 buffer_free( select_buf );
4156 buffer_free( group_buf );
4157 if( defaultselhash )
4158 jsonObjectFree( defaultselhash );
4159 free( join_clause );
4163 if( selclass->type != JSON_ARRAY ) {
4166 "%s: Malformed SELECT list for class \"%s\"; not an array",
4171 osrfAppSessionStatus(
4173 OSRF_STATUS_INTERNALSERVERERROR,
4174 "osrfMethodException",
4176 "Selected class not in FROM clause in JSON query"
4179 jsonIteratorFree( selclass_itr );
4180 buffer_free( select_buf );
4181 buffer_free( group_buf );
4182 if( defaultselhash )
4183 jsonObjectFree( defaultselhash );
4184 free( join_clause );
4188 // Look up some attributes of the current class
4189 osrfHash* idlClass = class_info->class_def;
4190 osrfHash* class_field_set = class_info->fields;
4191 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4192 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4194 if( 0 == selclass->size ) {
4197 "%s: No columns selected from \"%s\"",
4203 // stitch together the column list for the current table alias...
4204 unsigned long field_idx = 0;
4205 jsonObject* selfield = NULL;
4206 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4208 // If we need a separator comma, add one
4212 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4215 // if the field specification is a string, add it to the list
4216 if( selfield->type == JSON_STRING ) {
4218 // Look up the field in the IDL
4219 const char* col_name = jsonObjectGetString( selfield );
4220 osrfHash* field_def;
4222 if (!osrfStringArrayContains(
4224 osrfHashGet( class_field_set, col_name ),
4225 "suppress_controller"),
4228 field_def = osrfHashGet( class_field_set, col_name );
4231 // No such field in current class
4234 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4240 osrfAppSessionStatus(
4242 OSRF_STATUS_INTERNALSERVERERROR,
4243 "osrfMethodException",
4245 "Selected column not defined in JSON query"
4247 jsonIteratorFree( selclass_itr );
4248 buffer_free( select_buf );
4249 buffer_free( group_buf );
4250 if( defaultselhash )
4251 jsonObjectFree( defaultselhash );
4252 free( join_clause );
4254 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4255 // Virtual field not allowed
4258 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4264 osrfAppSessionStatus(
4266 OSRF_STATUS_INTERNALSERVERERROR,
4267 "osrfMethodException",
4269 "Selected column may not be virtual in JSON query"
4271 jsonIteratorFree( selclass_itr );
4272 buffer_free( select_buf );
4273 buffer_free( group_buf );
4274 if( defaultselhash )
4275 jsonObjectFree( defaultselhash );
4276 free( join_clause );
4282 if( flags & DISABLE_I18N )
4285 i18n = osrfHashGet( field_def, "i18n" );
4287 if( str_is_true( i18n ) ) {
4288 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4289 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4290 class_tname, cname, col_name, class_pkey,
4291 cname, class_pkey, locale, col_name );
4293 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4294 cname, col_name, col_name );
4297 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4298 cname, col_name, col_name );
4301 // ... but it could be an object, in which case we check for a Field Transform
4302 } else if( selfield->type == JSON_HASH ) {
4304 const char* col_name = jsonObjectGetString(
4305 jsonObjectGetKeyConst( selfield, "column" ) );
4307 // Get the field definition from the IDL
4308 osrfHash* field_def;
4309 if (!osrfStringArrayContains(
4311 osrfHashGet( class_field_set, col_name ),
4312 "suppress_controller"),
4315 field_def = osrfHashGet( class_field_set, col_name );
4319 // No such field in current class
4322 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4328 osrfAppSessionStatus(
4330 OSRF_STATUS_INTERNALSERVERERROR,
4331 "osrfMethodException",
4333 "Selected column is not defined in JSON query"
4335 jsonIteratorFree( selclass_itr );
4336 buffer_free( select_buf );
4337 buffer_free( group_buf );
4338 if( defaultselhash )
4339 jsonObjectFree( defaultselhash );
4340 free( join_clause );
4342 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4343 // No such field in current class
4346 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4352 osrfAppSessionStatus(
4354 OSRF_STATUS_INTERNALSERVERERROR,
4355 "osrfMethodException",
4357 "Selected column is virtual in JSON query"
4359 jsonIteratorFree( selclass_itr );
4360 buffer_free( select_buf );
4361 buffer_free( group_buf );
4362 if( defaultselhash )
4363 jsonObjectFree( defaultselhash );
4364 free( join_clause );
4368 // Decide what to use as a column alias
4370 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4371 _alias = jsonObjectGetString( tmp_const );
4372 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4373 _alias = jsonObjectGetString( tmp_const );
4374 } else { // Use field name as the alias
4378 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4379 char* transform_str = searchFieldTransform(
4380 class_info->alias, field_def, selfield );
4381 if( transform_str ) {
4382 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4383 free( transform_str );
4386 osrfAppSessionStatus(
4388 OSRF_STATUS_INTERNALSERVERERROR,
4389 "osrfMethodException",
4391 "Unable to generate transform function in JSON query"
4393 jsonIteratorFree( selclass_itr );
4394 buffer_free( select_buf );
4395 buffer_free( group_buf );
4396 if( defaultselhash )
4397 jsonObjectFree( defaultselhash );
4398 free( join_clause );
4405 if( flags & DISABLE_I18N )
4408 i18n = osrfHashGet( field_def, "i18n" );
4410 if( str_is_true( i18n ) ) {
4411 buffer_fadd( select_buf,
4412 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4413 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4414 class_tname, cname, col_name, class_pkey, cname,
4415 class_pkey, locale, _alias );
4417 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4418 cname, col_name, _alias );
4421 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4422 cname, col_name, _alias );
4429 "%s: Selected item is unexpected JSON type: %s",
4431 json_type( selfield->type )
4434 osrfAppSessionStatus(
4436 OSRF_STATUS_INTERNALSERVERERROR,
4437 "osrfMethodException",
4439 "Ill-formed SELECT item in JSON query"
4441 jsonIteratorFree( selclass_itr );
4442 buffer_free( select_buf );
4443 buffer_free( group_buf );
4444 if( defaultselhash )
4445 jsonObjectFree( defaultselhash );
4446 free( join_clause );
4450 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4451 if( obj_is_true( agg_obj ) )
4452 aggregate_found = 1;
4454 // Append a comma (except for the first one)
4455 // and add the column to a GROUP BY clause
4459 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4461 buffer_fadd( group_buf, " %d", sel_pos );
4465 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4467 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( elfield, "aggregate");
4468 if ( ! obj_is_true( aggregate_obj ) ) {
4472 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4475 buffer_fadd(group_buf, " %d", sel_pos);
4478 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4482 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4485 _column = searchFieldTransform(class_info->alias, field, selfield);
4486 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4487 OSRF_BUFFER_ADD(group_buf, _column);
4488 _column = searchFieldTransform(class_info->alias, field, selfield);
4495 } // end while -- iterating across SELECT columns
4497 } // end while -- iterating across classes
4499 jsonIteratorFree( selclass_itr );
4502 char* col_list = buffer_release( select_buf );
4504 // Make sure the SELECT list isn't empty. This can happen, for example,
4505 // if we try to build a default SELECT clause from a non-core table.
4508 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4510 osrfAppSessionStatus(
4512 OSRF_STATUS_INTERNALSERVERERROR,
4513 "osrfMethodException",
4515 "SELECT list is empty"
4518 buffer_free( group_buf );
4519 if( defaultselhash )
4520 jsonObjectFree( defaultselhash );
4521 free( join_clause );
4527 table = searchValueTransform( join_hash );
4529 table = strdup( curr_query->core.source_def );
4533 osrfAppSessionStatus(
4535 OSRF_STATUS_INTERNALSERVERERROR,
4536 "osrfMethodException",
4538 "Unable to identify table for core class"
4541 buffer_free( group_buf );
4542 if( defaultselhash )
4543 jsonObjectFree( defaultselhash );
4544 free( join_clause );
4548 // Put it all together
4549 growing_buffer* sql_buf = buffer_init( 128 );
4550 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4554 // Append the join clause, if any
4556 buffer_add(sql_buf, join_clause );
4557 free( join_clause );
4560 char* order_by_list = NULL;
4561 char* having_buf = NULL;
4563 if( !from_function ) {
4565 // Build a WHERE clause, if there is one
4567 buffer_add( sql_buf, " WHERE " );
4569 // and it's on the WHERE clause
4570 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4573 osrfAppSessionStatus(
4575 OSRF_STATUS_INTERNALSERVERERROR,
4576 "osrfMethodException",
4578 "Severe query error in WHERE predicate -- see error log for more details"
4581 buffer_free( group_buf );
4582 buffer_free( sql_buf );
4583 if( defaultselhash )
4584 jsonObjectFree( defaultselhash );
4588 buffer_add( sql_buf, pred );
4592 // Build a HAVING clause, if there is one
4595 // and it's on the the WHERE clause
4596 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4598 if( ! having_buf ) {
4600 osrfAppSessionStatus(
4602 OSRF_STATUS_INTERNALSERVERERROR,
4603 "osrfMethodException",
4605 "Severe query error in HAVING predicate -- see error log for more details"
4608 buffer_free( group_buf );
4609 buffer_free( sql_buf );
4610 if( defaultselhash )
4611 jsonObjectFree( defaultselhash );
4616 // Build an ORDER BY clause, if there is one
4617 if( NULL == order_hash )
4618 ; // No ORDER BY? do nothing
4619 else if( JSON_ARRAY == order_hash->type ) {
4620 order_by_list = buildOrderByFromArray( ctx, order_hash );
4621 if( !order_by_list ) {
4623 buffer_free( group_buf );
4624 buffer_free( sql_buf );
4625 if( defaultselhash )
4626 jsonObjectFree( defaultselhash );
4629 } else if( JSON_HASH == order_hash->type ) {
4630 // This hash is keyed on class alias. Each class has either
4631 // an array of field names or a hash keyed on field name.
4632 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4633 jsonIterator* class_itr = jsonNewIterator( order_hash );
4634 while( (snode = jsonIteratorNext( class_itr )) ) {
4636 ClassInfo* order_class_info = search_alias( class_itr->key );
4637 if( ! order_class_info ) {
4638 osrfLogError( OSRF_LOG_MARK,
4639 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4640 modulename, class_itr->key );
4642 osrfAppSessionStatus(
4644 OSRF_STATUS_INTERNALSERVERERROR,
4645 "osrfMethodException",
4647 "Invalid class referenced in ORDER BY clause -- "
4648 "see error log for more details"
4650 jsonIteratorFree( class_itr );
4651 buffer_free( order_buf );
4653 buffer_free( group_buf );
4654 buffer_free( sql_buf );
4655 if( defaultselhash )
4656 jsonObjectFree( defaultselhash );
4660 osrfHash* field_list_def = order_class_info->fields;
4662 if( snode->type == JSON_HASH ) {
4664 // Hash is keyed on field names from the current class. For each field
4665 // there is another layer of hash to define the sorting details, if any,
4666 // or a string to indicate direction of sorting.
4667 jsonIterator* order_itr = jsonNewIterator( snode );
4668 while( (onode = jsonIteratorNext( order_itr )) ) {
4670 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4672 osrfLogError( OSRF_LOG_MARK,
4673 "%s: Invalid field \"%s\" in ORDER BY clause",
4674 modulename, order_itr->key );
4676 osrfAppSessionStatus(
4678 OSRF_STATUS_INTERNALSERVERERROR,
4679 "osrfMethodException",
4681 "Invalid field in ORDER BY clause -- "
4682 "see error log for more details"
4684 jsonIteratorFree( order_itr );
4685 jsonIteratorFree( class_itr );
4686 buffer_free( order_buf );
4688 buffer_free( group_buf );
4689 buffer_free( sql_buf );
4690 if( defaultselhash )
4691 jsonObjectFree( defaultselhash );
4693 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4694 osrfLogError( OSRF_LOG_MARK,
4695 "%s: Virtual field \"%s\" in ORDER BY clause",
4696 modulename, order_itr->key );
4698 osrfAppSessionStatus(
4700 OSRF_STATUS_INTERNALSERVERERROR,
4701 "osrfMethodException",
4703 "Virtual field in ORDER BY clause -- "
4704 "see error log for more details"
4706 jsonIteratorFree( order_itr );
4707 jsonIteratorFree( class_itr );
4708 buffer_free( order_buf );
4710 buffer_free( group_buf );
4711 buffer_free( sql_buf );
4712 if( defaultselhash )
4713 jsonObjectFree( defaultselhash );
4717 const char* direction = NULL;
4718 if( onode->type == JSON_HASH ) {
4719 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4720 string = searchFieldTransform(
4722 osrfHashGet( field_list_def, order_itr->key ),
4726 if( ctx ) osrfAppSessionStatus(
4728 OSRF_STATUS_INTERNALSERVERERROR,
4729 "osrfMethodException",
4731 "Severe query error in ORDER BY clause -- "
4732 "see error log for more details"
4734 jsonIteratorFree( order_itr );
4735 jsonIteratorFree( class_itr );
4737 buffer_free( group_buf );
4738 buffer_free( order_buf);
4739 buffer_free( sql_buf );
4740 if( defaultselhash )
4741 jsonObjectFree( defaultselhash );
4745 growing_buffer* field_buf = buffer_init( 16 );
4746 buffer_fadd( field_buf, "\"%s\".%s",
4747 class_itr->key, order_itr->key );
4748 string = buffer_release( field_buf );
4751 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4752 const char* dir = jsonObjectGetString( tmp_const );
4753 if(!strncasecmp( dir, "d", 1 )) {
4754 direction = " DESC";
4760 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4761 osrfLogError( OSRF_LOG_MARK,
4762 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4763 modulename, json_type( onode->type ) );
4765 osrfAppSessionStatus(
4767 OSRF_STATUS_INTERNALSERVERERROR,
4768 "osrfMethodException",
4770 "Malformed ORDER BY clause -- see error log for more details"
4772 jsonIteratorFree( order_itr );
4773 jsonIteratorFree( class_itr );
4775 buffer_free( group_buf );
4776 buffer_free( order_buf );
4777 buffer_free( sql_buf );
4778 if( defaultselhash )
4779 jsonObjectFree( defaultselhash );
4783 string = strdup( order_itr->key );
4784 const char* dir = jsonObjectGetString( onode );
4785 if( !strncasecmp( dir, "d", 1 )) {
4786 direction = " DESC";
4793 OSRF_BUFFER_ADD( order_buf, ", " );
4795 order_buf = buffer_init( 128 );
4797 OSRF_BUFFER_ADD( order_buf, string );
4801 OSRF_BUFFER_ADD( order_buf, direction );
4805 jsonIteratorFree( order_itr );
4807 } else if( snode->type == JSON_ARRAY ) {
4809 // Array is a list of fields from the current class
4810 unsigned long order_idx = 0;
4811 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4813 const char* _f = jsonObjectGetString( onode );
4815 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4817 osrfLogError( OSRF_LOG_MARK,
4818 "%s: Invalid field \"%s\" in ORDER BY clause",
4821 osrfAppSessionStatus(
4823 OSRF_STATUS_INTERNALSERVERERROR,
4824 "osrfMethodException",
4826 "Invalid field in ORDER BY clause -- "
4827 "see error log for more details"
4829 jsonIteratorFree( class_itr );
4830 buffer_free( order_buf );
4832 buffer_free( group_buf );
4833 buffer_free( sql_buf );
4834 if( defaultselhash )
4835 jsonObjectFree( defaultselhash );
4837 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4838 osrfLogError( OSRF_LOG_MARK,
4839 "%s: Virtual field \"%s\" in ORDER BY clause",
4842 osrfAppSessionStatus(
4844 OSRF_STATUS_INTERNALSERVERERROR,
4845 "osrfMethodException",
4847 "Virtual field in ORDER BY clause -- "
4848 "see error log for more details"
4850 jsonIteratorFree( class_itr );
4851 buffer_free( order_buf );
4853 buffer_free( group_buf );
4854 buffer_free( sql_buf );
4855 if( defaultselhash )
4856 jsonObjectFree( defaultselhash );
4861 OSRF_BUFFER_ADD( order_buf, ", " );
4863 order_buf = buffer_init( 128 );
4865 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4869 // IT'S THE OOOOOOOOOOOLD STYLE!
4871 osrfLogError( OSRF_LOG_MARK,
4872 "%s: Possible SQL injection attempt; direct order by is not allowed",
4875 osrfAppSessionStatus(
4877 OSRF_STATUS_INTERNALSERVERERROR,
4878 "osrfMethodException",
4880 "Severe query error -- see error log for more details"
4885 buffer_free( group_buf );
4886 buffer_free( order_buf );
4887 buffer_free( sql_buf );
4888 if( defaultselhash )
4889 jsonObjectFree( defaultselhash );
4890 jsonIteratorFree( class_itr );
4894 jsonIteratorFree( class_itr );
4896 order_by_list = buffer_release( order_buf );
4898 osrfLogError( OSRF_LOG_MARK,
4899 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4900 modulename, json_type( order_hash->type ) );
4902 osrfAppSessionStatus(
4904 OSRF_STATUS_INTERNALSERVERERROR,
4905 "osrfMethodException",
4907 "Malformed ORDER BY clause -- see error log for more details"
4910 buffer_free( group_buf );
4911 buffer_free( sql_buf );
4912 if( defaultselhash )
4913 jsonObjectFree( defaultselhash );
4918 string = buffer_release( group_buf );
4920 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4921 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4922 OSRF_BUFFER_ADD( sql_buf, string );
4927 if( having_buf && *having_buf ) {
4928 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4929 OSRF_BUFFER_ADD( sql_buf, having_buf );
4933 if( order_by_list ) {
4935 if( *order_by_list ) {
4936 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4937 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4940 free( order_by_list );
4944 const char* str = jsonObjectGetString( limit );
4945 if (str) { // limit could be JSON_NULL, etc.
4946 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4951 const char* str = jsonObjectGetString( offset );
4953 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4957 if( !(flags & SUBSELECT) )
4958 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4960 if( defaultselhash )
4961 jsonObjectFree( defaultselhash );
4963 return buffer_release( sql_buf );
4965 } // end of SELECT()
4968 @brief Build a list of ORDER BY expressions.
4969 @param ctx Pointer to the method context.
4970 @param order_array Pointer to a JSON_ARRAY of field specifications.
4971 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
4972 Each expression may be either a column reference or a function call whose first parameter
4973 is a column reference.
4975 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
4976 It may optionally include entries for "direction" and/or "transform".
4978 The calling code is responsible for freeing the returned string.
4980 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
4981 if( ! order_array ) {
4982 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
4985 osrfAppSessionStatus(
4987 OSRF_STATUS_INTERNALSERVERERROR,
4988 "osrfMethodException",
4990 "Logic error: ORDER BY clause expected, not found; "
4991 "see error log for more details"
4994 } else if( order_array->type != JSON_ARRAY ) {
4995 osrfLogError( OSRF_LOG_MARK,
4996 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
4998 osrfAppSessionStatus(
5000 OSRF_STATUS_INTERNALSERVERERROR,
5001 "osrfMethodException",
5003 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
5007 growing_buffer* order_buf = buffer_init( 128 );
5008 int first = 1; // boolean
5010 jsonObject* order_spec;
5011 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
5013 if( JSON_HASH != order_spec->type ) {
5014 osrfLogError( OSRF_LOG_MARK,
5015 "%s: Malformed field specification in ORDER BY clause; "
5016 "expected JSON_HASH, found %s",
5017 modulename, json_type( order_spec->type ) );
5019 osrfAppSessionStatus(
5021 OSRF_STATUS_INTERNALSERVERERROR,
5022 "osrfMethodException",
5024 "Malformed ORDER BY clause -- see error log for more details"
5026 buffer_free( order_buf );
5030 const char* class_alias =
5031 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5033 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5035 jsonObject* compare_to = jsonObjectGetKeyConst( order_spec, "compare" );
5037 if( !field || !class_alias ) {
5038 osrfLogError( OSRF_LOG_MARK,
5039 "%s: Missing class or field name in field specification of ORDER BY clause",
5042 osrfAppSessionStatus(
5044 OSRF_STATUS_INTERNALSERVERERROR,
5045 "osrfMethodException",
5047 "Malformed ORDER BY clause -- see error log for more details"
5049 buffer_free( order_buf );
5053 const ClassInfo* order_class_info = search_alias( class_alias );
5054 if( ! order_class_info ) {
5055 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5056 "not in FROM clause, skipping it", modulename, class_alias );
5060 // Add a separating comma, except at the beginning
5064 OSRF_BUFFER_ADD( order_buf, ", " );
5066 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5068 osrfLogError( OSRF_LOG_MARK,
5069 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5070 modulename, class_alias, field );
5072 osrfAppSessionStatus(
5074 OSRF_STATUS_INTERNALSERVERERROR,
5075 "osrfMethodException",
5077 "Invalid field referenced in ORDER BY clause -- "
5078 "see error log for more details"
5082 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5083 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5084 modulename, field );
5086 osrfAppSessionStatus(
5088 OSRF_STATUS_INTERNALSERVERERROR,
5089 "osrfMethodException",
5091 "Virtual field in ORDER BY clause -- see error log for more details"
5093 buffer_free( order_buf );
5097 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5098 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5099 if( ! transform_str ) {
5101 osrfAppSessionStatus(
5103 OSRF_STATUS_INTERNALSERVERERROR,
5104 "osrfMethodException",
5106 "Severe query error in ORDER BY clause -- "
5107 "see error log for more details"
5109 buffer_free( order_buf );
5113 OSRF_BUFFER_ADD( order_buf, transform_str );
5114 free( transform_str );
5115 } else if( compare_to ) {
5116 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5117 if( ! compare_str ) {
5119 osrfAppSessionStatus(
5121 OSRF_STATUS_INTERNALSERVERERROR,
5122 "osrfMethodException",
5124 "Severe query error in ORDER BY clause -- "
5125 "see error log for more details"
5127 buffer_free( order_buf );
5131 buffer_fadd( order_buf, "(%s)", compare_str );
5132 free( compare_str );
5135 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5137 const char* direction =
5138 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5140 if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5141 OSRF_BUFFER_ADD( order_buf, " DESC" );
5143 OSRF_BUFFER_ADD( order_buf, " ASC" );
5147 return buffer_release( order_buf );
5151 @brief Build a SELECT statement.
5152 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5153 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5154 @param meta Pointer to the class metadata for the core class.
5155 @param ctx Pointer to the method context.
5156 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5158 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5159 "order_by", "limit", and "offset".
5161 The SELECT statements built here are distinct from those built for the json_query method.
5163 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5164 osrfHash* meta, osrfMethodContext* ctx ) {
5166 const char* locale = osrf_message_get_last_locale();
5168 osrfHash* fields = osrfHashGet( meta, "fields" );
5169 const char* core_class = osrfHashGet( meta, "classname" );
5171 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5173 jsonObject* selhash = NULL;
5174 jsonObject* defaultselhash = NULL;
5176 growing_buffer* sql_buf = buffer_init( 128 );
5177 growing_buffer* select_buf = buffer_init( 128 );
5179 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5180 defaultselhash = jsonNewObjectType( JSON_HASH );
5181 selhash = defaultselhash;
5184 // If there's no SELECT list for the core class, build one
5185 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5186 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5188 // Add every non-virtual field to the field list
5189 osrfHash* field_def = NULL;
5190 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5191 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5192 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5193 const char* field = osrfHashIteratorKey( field_itr );
5194 jsonObjectPush( field_list, jsonNewObject( field ) );
5197 osrfHashIteratorFree( field_itr );
5198 jsonObjectSetKey( selhash, core_class, field_list );
5201 // Build a list of columns for the SELECT clause
5203 const jsonObject* snode = NULL;
5204 jsonIterator* class_itr = jsonNewIterator( selhash );
5205 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5207 // If the class isn't in the IDL, ignore it
5208 const char* cname = class_itr->key;
5209 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5213 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5214 if( strcmp( core_class, class_itr->key )) {
5218 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5219 if( !found->size ) {
5220 jsonObjectFree( found );
5224 jsonObjectFree( found );
5227 const jsonObject* node = NULL;
5228 jsonIterator* select_itr = jsonNewIterator( snode );
5229 while( (node = jsonIteratorNext( select_itr )) ) {
5230 const char* item_str = jsonObjectGetString( node );
5231 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5232 char* fname = osrfHashGet( field, "name" );
5237 if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5243 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5248 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5249 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5252 i18n = osrfHashGet( field, "i18n" );
5254 if( str_is_true( i18n ) ) {
5255 char* pkey = osrfHashGet( idlClass, "primarykey" );
5256 char* tname = osrfHashGet( idlClass, "tablename" );
5258 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5259 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5260 tname, cname, fname, pkey, cname, pkey, locale, fname );
5262 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5265 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5269 jsonIteratorFree( select_itr );
5272 jsonIteratorFree( class_itr );
5274 char* col_list = buffer_release( select_buf );
5275 char* table = oilsGetRelation( meta );
5277 table = strdup( "(null)" );
5279 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5283 // Clear the query stack (as a fail-safe precaution against possible
5284 // leftover garbage); then push the first query frame onto the stack.
5285 clear_query_stack();
5287 if( add_query_core( NULL, core_class ) ) {
5289 osrfAppSessionStatus(
5291 OSRF_STATUS_INTERNALSERVERERROR,
5292 "osrfMethodException",
5294 "Unable to build query frame for core class"
5296 buffer_free( sql_buf );
5297 if( defaultselhash )
5298 jsonObjectFree( defaultselhash );
5302 // Add the JOIN clauses, if any
5304 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5305 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5306 OSRF_BUFFER_ADD( sql_buf, join_clause );
5307 free( join_clause );
5310 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5311 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5313 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5315 // Add the conditions in the WHERE clause
5316 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5318 osrfAppSessionStatus(
5320 OSRF_STATUS_INTERNALSERVERERROR,
5321 "osrfMethodException",
5323 "Severe query error -- see error log for more details"
5325 buffer_free( sql_buf );
5326 if( defaultselhash )
5327 jsonObjectFree( defaultselhash );
5328 clear_query_stack();
5331 buffer_add( sql_buf, pred );
5335 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5336 if( rest_of_query ) {
5337 const jsonObject* order_by = NULL;
5338 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5340 char* order_by_list = NULL;
5342 if( JSON_ARRAY == order_by->type ) {
5343 order_by_list = buildOrderByFromArray( ctx, order_by );
5344 if( !order_by_list ) {
5345 buffer_free( sql_buf );
5346 if( defaultselhash )
5347 jsonObjectFree( defaultselhash );
5348 clear_query_stack();
5351 } else if( JSON_HASH == order_by->type ) {
5352 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5353 // and build a list of ORDER BY expressions.
5354 growing_buffer* order_buf = buffer_init( 128 );
5356 jsonIterator* class_itr = jsonNewIterator( order_by );
5357 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5359 ClassInfo* order_class_info = search_alias( class_itr->key );
5360 if( ! order_class_info )
5361 continue; // class not referenced by FROM clause? Ignore it.
5363 if( JSON_HASH == snode->type ) {
5365 // If the data for the current class is a JSON_HASH, then it is
5366 // keyed on field name.
5368 const jsonObject* onode = NULL;
5369 jsonIterator* order_itr = jsonNewIterator( snode );
5370 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5372 osrfHash* field_def = osrfHashGet(
5373 order_class_info->fields, order_itr->key );
5375 continue; // Field not defined in IDL? Ignore it.
5376 if( str_is_true( osrfHashGet( field_def, "virtual")))
5377 continue; // Field is virtual? Ignore it.
5379 char* field_str = NULL;
5380 char* direction = NULL;
5381 if( onode->type == JSON_HASH ) {
5382 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5383 field_str = searchFieldTransform(
5384 class_itr->key, field_def, onode );
5386 osrfAppSessionStatus(
5388 OSRF_STATUS_INTERNALSERVERERROR,
5389 "osrfMethodException",
5391 "Severe query error in ORDER BY clause -- "
5392 "see error log for more details"
5394 jsonIteratorFree( order_itr );
5395 jsonIteratorFree( class_itr );
5396 buffer_free( order_buf );
5397 buffer_free( sql_buf );
5398 if( defaultselhash )
5399 jsonObjectFree( defaultselhash );
5400 clear_query_stack();
5404 growing_buffer* field_buf = buffer_init( 16 );
5405 buffer_fadd( field_buf, "\"%s\".%s",
5406 class_itr->key, order_itr->key );
5407 field_str = buffer_release( field_buf );
5410 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5411 const char* dir = jsonObjectGetString( order_by );
5412 if(!strncasecmp( dir, "d", 1 )) {
5413 direction = " DESC";
5417 field_str = strdup( order_itr->key );
5418 const char* dir = jsonObjectGetString( onode );
5419 if( !strncasecmp( dir, "d", 1 )) {
5420 direction = " DESC";
5429 buffer_add( order_buf, ", " );
5432 buffer_add( order_buf, field_str );
5436 buffer_add( order_buf, direction );
5438 } // end while; looping over ORDER BY expressions
5440 jsonIteratorFree( order_itr );
5442 } else if( JSON_STRING == snode->type ) {
5443 // We expect a comma-separated list of sort fields.
5444 const char* str = jsonObjectGetString( snode );
5445 if( strchr( str, ';' )) {
5446 // No semicolons allowed. It is theoretically possible for a
5447 // legitimate semicolon to occur within quotes, but it's not likely
5448 // to occur in practice in the context of an ORDER BY list.
5449 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5450 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5452 osrfAppSessionStatus(
5454 OSRF_STATUS_INTERNALSERVERERROR,
5455 "osrfMethodException",
5457 "Possible attempt at SOL injection -- "
5458 "semicolon found in ORDER BY list"
5461 jsonIteratorFree( class_itr );
5462 buffer_free( order_buf );
5463 buffer_free( sql_buf );
5464 if( defaultselhash )
5465 jsonObjectFree( defaultselhash );
5466 clear_query_stack();
5469 buffer_add( order_buf, str );
5473 } // end while; looping over order_by classes
5475 jsonIteratorFree( class_itr );
5476 order_by_list = buffer_release( order_buf );
5479 osrfLogWarning( OSRF_LOG_MARK,
5480 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5481 "no ORDER BY generated" );
5484 if( order_by_list && *order_by_list ) {
5485 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5486 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5489 free( order_by_list );
5492 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5494 const char* str = jsonObjectGetString( limit );
5504 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5506 const char* str = jsonObjectGetString( offset );
5517 if( defaultselhash )
5518 jsonObjectFree( defaultselhash );
5519 clear_query_stack();
5521 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5522 return buffer_release( sql_buf );
5525 int doJSONSearch ( osrfMethodContext* ctx ) {
5526 if(osrfMethodVerifyContext( ctx )) {
5527 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5531 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5535 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5539 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5540 flags |= SELECT_DISTINCT;
5542 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5543 flags |= DISABLE_I18N;
5545 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5546 clear_query_stack(); // a possibly needless precaution
5547 char* sql = buildQuery( ctx, hash, flags );
5548 clear_query_stack();
5555 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5558 dbhandle = writehandle;
5560 dbi_result result = dbi_conn_query( dbhandle, sql );
5563 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5565 if( dbi_result_first_row( result )) {
5566 /* JSONify the result */
5567 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5570 jsonObject* return_val = oilsMakeJSONFromResult( result );
5571 osrfAppRespond( ctx, return_val );
5572 jsonObjectFree( return_val );
5573 } while( dbi_result_next_row( result ));
5576 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5579 osrfAppRespondComplete( ctx, NULL );
5581 /* clean up the query */
5582 dbi_result_free( result );
5587 int errnum = dbi_conn_error( dbhandle, &msg );
5588 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5589 modulename, sql, errnum, msg ? msg : "(No description available)" );
5590 osrfAppSessionStatus(
5592 OSRF_STATUS_INTERNALSERVERERROR,
5593 "osrfMethodException",
5595 "Severe query error -- see error log for more details"
5597 if( !oilsIsDBConnected( dbhandle ))
5598 osrfAppSessionPanic( ctx->session );
5605 // The last parameter, err, is used to report an error condition by updating an int owned by
5606 // the calling code.
5608 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5609 // It is the responsibility of the calling code to initialize *err before the
5610 // call, so that it will be able to make sense of the result.
5612 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5613 // redundant anyway.
5614 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5615 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5618 dbhandle = writehandle;
5620 char* core_class = osrfHashGet( class_meta, "classname" );
5621 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5623 char* pkey = osrfHashGet( class_meta, "primarykey" );
5625 if (!ctx->session->userData)
5626 (void) initSessionCache( ctx );
5628 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5629 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5630 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5632 int i_respond_directly = 0;
5633 int flesh_depth = 0;
5635 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5637 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5642 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5644 dbi_result result = dbi_conn_query( dbhandle, sql );
5645 if( NULL == result ) {
5647 int errnum = dbi_conn_error( dbhandle, &msg );
5648 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5649 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5650 msg ? msg : "(No description available)" );
5651 if( !oilsIsDBConnected( dbhandle ))
5652 osrfAppSessionPanic( ctx->session );
5653 osrfAppSessionStatus(
5655 OSRF_STATUS_INTERNALSERVERERROR,
5656 "osrfMethodException",
5658 "Severe query error -- see error log for more details"
5665 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5668 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5669 jsonObject* row_obj = NULL;
5671 // The following two steps are for verifyObjectPCRUD()'s benefit.
5672 // 1. get the flesh depth
5673 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5675 flesh_depth = (int) jsonObjectGetNumber( _tmp );
5676 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5677 flesh_depth = max_flesh_depth;
5680 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
5681 // over the whole life of this request. This means if we've already set
5682 // up a rs_size_req_%d, do nothing.
5683 // a. Incidentally, we can also use this opportunity to set i_respond_directly
5684 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
5685 if( !rs_size ) { // pointer null, so value not set in hash
5686 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
5687 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
5689 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
5690 unsigned long long result_count = dbi_result_get_numrows( result );
5691 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
5692 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
5695 if( dbi_result_first_row( result )) {
5697 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5698 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5699 // eliminate the duplicates.
5700 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5701 osrfHash* dedup = osrfNewHash();
5703 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5704 char* pkey_val = oilsFMGetString( row_obj, pkey );
5705 if( osrfHashGet( dedup, pkey_val ) ) {
5706 jsonObjectFree( row_obj );
5709 if( !enforce_pcrud || !need_to_verify ||
5710 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
5711 osrfHashSet( dedup, pkey_val, pkey_val );
5712 jsonObjectPush( res_list, row_obj );
5715 } while( dbi_result_next_row( result ));
5716 osrfHashFree( dedup );
5719 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5723 /* clean up the query */
5724 dbi_result_free( result );
5727 // If we're asked to flesh, and there's anything to flesh, then flesh it
5728 // (formerly we would skip fleshing if in pcrud mode, but now we support
5729 // fleshing even in PCRUD).
5730 if( res_list->size ) {
5731 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
5732 jsonObject* flesh_fields;
5733 jsonObject* flesh_blob = NULL;
5734 osrfStringArray* link_fields = NULL;
5735 osrfHash* links = NULL;
5739 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
5740 if( temp_blob && flesh_depth > 0 ) {
5742 flesh_blob = jsonObjectClone( temp_blob );
5743 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
5745 links = osrfHashGet( class_meta, "links" );
5747 // Make an osrfStringArray of the names of fields to be fleshed
5748 if( flesh_fields ) {
5749 if( flesh_fields->size == 1 ) {
5750 const char* _t = jsonObjectGetString(
5751 jsonObjectGetIndex( flesh_fields, 0 ) );
5752 if( !strcmp( _t, "*" ))
5753 link_fields = osrfHashKeys( links );
5756 if( !link_fields ) {
5758 link_fields = osrfNewStringArray( 1 );
5759 jsonIterator* _i = jsonNewIterator( flesh_fields );
5760 while ((_f = jsonIteratorNext( _i ))) {
5761 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5763 jsonIteratorFree( _i );
5766 want_flesh = link_fields ? 1 : 0;
5770 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5772 // Iterate over the JSON_ARRAY of rows
5774 unsigned long res_idx = 0;
5775 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5778 const char* link_field;
5780 // Iterate over the list of fleshable fields
5782 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5784 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5786 osrfHash* kid_link = osrfHashGet( links, link_field );
5788 continue; // Not a link field; skip it
5790 osrfHash* field = osrfHashGet( fields, link_field );
5792 continue; // Not a field at all; skip it (IDL is ill-formed)
5794 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5795 osrfHashGet( kid_link, "class" ));
5797 continue; // The class it links to doesn't exist; skip it
5799 const char* reltype = osrfHashGet( kid_link, "reltype" );
5801 continue; // No reltype; skip it (IDL is ill-formed)
5803 osrfHash* value_field = field;
5805 if( !strcmp( reltype, "has_many" )
5806 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5807 value_field = osrfHashGet(
5808 fields, osrfHashGet( class_meta, "primarykey" ) );
5811 int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
5812 // fleshing pcrud case: we require the controller in need_to_verify mode
5813 if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
5814 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
5818 (unsigned long) atoi( osrfHashGet(field, "array_position") ),
5820 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
5826 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5828 if( link_map->size > 0 ) {
5829 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5832 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5837 osrfHashGet( kid_link, "class" ),
5844 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5845 osrfHashGet( kid_link, "field" ),
5846 osrfHashGet( kid_link, "class" ),
5847 osrfHashGet( kid_link, "key" ),
5848 osrfHashGet( kid_link, "reltype" )
5851 const char* search_key = jsonObjectGetString(
5852 jsonObjectGetIndex( cur,
5853 atoi( osrfHashGet( value_field, "array_position" ) )
5858 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5862 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5864 // construct WHERE clause
5865 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5868 osrfHashGet( kid_link, "key" ),
5869 jsonNewObject( search_key )
5872 // construct the rest of the query, mostly
5873 // by copying pieces of the previous level of query
5874 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5875 jsonObjectSetKey( rest_of_query, "flesh",
5876 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5880 jsonObjectSetKey( rest_of_query, "flesh_fields",
5881 jsonObjectClone( flesh_blob ));
5883 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5884 jsonObjectSetKey( rest_of_query, "order_by",
5885 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5889 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5890 jsonObjectSetKey( rest_of_query, "select",
5891 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5895 // do the query, recursively, to expand the fleshable field
5896 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5897 where_clause, rest_of_query, err );
5899 jsonObjectFree( where_clause );
5900 jsonObjectFree( rest_of_query );
5903 osrfStringArrayFree( link_fields );
5904 jsonObjectFree( res_list );
5905 jsonObjectFree( flesh_blob );
5909 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5910 osrfHashGet( kid_link, "class" ), kids->size );
5912 // Traverse the result set
5913 jsonObject* X = NULL;
5914 if( link_map->size > 0 && kids->size > 0 ) {
5916 kids = jsonNewObjectType( JSON_ARRAY );
5918 jsonObject* _k_node;
5919 unsigned long res_idx = 0;
5920 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5926 (unsigned long) atoi(
5932 osrfHashGet( kid_link, "class" )
5936 osrfStringArrayGetString( link_map, 0 )
5944 } // end while loop traversing X
5947 if (kids->size > 0) {
5949 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5950 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
5952 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5953 osrfHashGet( kid_link, "field" ));
5956 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5957 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5962 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5964 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5965 osrfHashGet( kid_link, "field" ) );
5968 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5969 jsonObjectClone( kids )
5974 jsonObjectFree( kids );
5978 jsonObjectFree( kids );
5980 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5981 osrfHashGet( kid_link, "field" ) );
5982 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5984 } // end while loop traversing list of fleshable fields
5987 if( i_respond_directly ) {
5988 if ( *methodtype == 'i' ) {
5989 osrfAppRespond( ctx,
5990 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
5992 osrfAppRespond( ctx, cur );
5995 } // end while loop traversing res_list
5996 jsonObjectFree( flesh_blob );
5997 osrfStringArrayFree( link_fields );
6000 if( i_respond_directly ) {
6001 jsonObjectFree( res_list );
6002 return jsonNewObjectType( JSON_ARRAY );
6009 int doUpdate( osrfMethodContext* ctx ) {
6010 if( osrfMethodVerifyContext( ctx )) {
6011 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6016 timeout_needs_resetting = 1;
6018 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6020 jsonObject* target = NULL;
6022 target = jsonObjectGetIndex( ctx->params, 1 );
6024 target = jsonObjectGetIndex( ctx->params, 0 );
6026 if(!verifyObjectClass( ctx, target )) {
6027 osrfAppRespondComplete( ctx, NULL );
6031 if( getXactId( ctx ) == NULL ) {
6032 osrfAppSessionStatus(
6034 OSRF_STATUS_BADREQUEST,
6035 "osrfMethodException",
6037 "No active transaction -- required for UPDATE"
6039 osrfAppRespondComplete( ctx, NULL );
6043 // The following test is harmless but redundant. If a class is
6044 // readonly, we don't register an update method for it.
6045 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6046 osrfAppSessionStatus(
6048 OSRF_STATUS_BADREQUEST,
6049 "osrfMethodException",
6051 "Cannot UPDATE readonly class"
6053 osrfAppRespondComplete( ctx, NULL );
6057 const char* trans_id = getXactId( ctx );
6059 // Set the last_xact_id
6060 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6062 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6063 trans_id, target->classname, index );
6064 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6067 char* pkey = osrfHashGet( meta, "primarykey" );
6068 osrfHash* fields = osrfHashGet( meta, "fields" );
6070 char* id = oilsFMGetString( target, pkey );
6074 "%s updating %s object with %s = %s",
6076 osrfHashGet( meta, "fieldmapper" ),
6081 dbhandle = writehandle;
6082 growing_buffer* sql = buffer_init( 128 );
6083 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6086 osrfHash* field_def = NULL;
6087 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6088 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6090 // Skip virtual fields, and the primary key
6091 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6094 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6098 const char* field_name = osrfHashIteratorKey( field_itr );
6099 if( ! strcmp( field_name, pkey ) )
6102 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6104 int value_is_numeric = 0; // boolean
6106 if( field_object && field_object->classname ) {
6107 value = oilsFMGetString(
6109 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6111 } else if( field_object && JSON_BOOL == field_object->type ) {
6112 if( jsonBoolIsTrue( field_object ) )
6113 value = strdup( "t" );
6115 value = strdup( "f" );
6117 value = jsonObjectToSimpleString( field_object );
6118 if( field_object && JSON_NUMBER == field_object->type )
6119 value_is_numeric = 1;
6122 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6123 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6125 if( !field_object || field_object->type == JSON_NULL ) {
6126 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6127 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6131 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6132 buffer_fadd( sql, " %s = NULL", field_name );
6135 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6139 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6141 const char* numtype = get_datatype( field_def );
6142 if( !strncmp( numtype, "INT", 3 ) ) {
6143 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6144 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6145 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6147 // Must really be intended as a string, so quote it
6148 if( dbi_conn_quote_string( dbhandle, &value )) {
6149 buffer_fadd( sql, " %s = %s", field_name, value );
6151 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6152 modulename, value );
6153 osrfAppSessionStatus(
6155 OSRF_STATUS_INTERNALSERVERERROR,
6156 "osrfMethodException",
6158 "Error quoting string -- please see the error log for more details"
6162 osrfHashIteratorFree( field_itr );
6164 osrfAppRespondComplete( ctx, NULL );
6169 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6172 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6176 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6177 buffer_fadd( sql, " %s = %s", field_name, value );
6179 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6180 osrfAppSessionStatus(
6182 OSRF_STATUS_INTERNALSERVERERROR,
6183 "osrfMethodException",
6185 "Error quoting string -- please see the error log for more details"
6189 osrfHashIteratorFree( field_itr );
6191 osrfAppRespondComplete( ctx, NULL );
6200 osrfHashIteratorFree( field_itr );
6202 jsonObject* obj = jsonNewObject( id );
6204 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6205 dbi_conn_quote_string( dbhandle, &id );
6207 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6209 char* query = buffer_release( sql );
6210 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6212 dbi_result result = dbi_conn_query( dbhandle, query );
6217 jsonObjectFree( obj );
6218 obj = jsonNewObject( NULL );
6220 int errnum = dbi_conn_error( dbhandle, &msg );
6223 "%s ERROR updating %s object with %s = %s: %d %s",
6225 osrfHashGet( meta, "fieldmapper" ),
6229 msg ? msg : "(No description available)"
6231 osrfAppSessionStatus(
6233 OSRF_STATUS_INTERNALSERVERERROR,
6234 "osrfMethodException",
6236 "Error in updating a row -- please see the error log for more details"
6238 if( !oilsIsDBConnected( dbhandle ))
6239 osrfAppSessionPanic( ctx->session );
6242 dbi_result_free( result );
6245 osrfAppRespondComplete( ctx, obj );
6246 jsonObjectFree( obj );
6250 int doDelete( osrfMethodContext* ctx ) {
6251 if( osrfMethodVerifyContext( ctx )) {
6252 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6257 timeout_needs_resetting = 1;
6259 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6261 if( getXactId( ctx ) == NULL ) {
6262 osrfAppSessionStatus(
6264 OSRF_STATUS_BADREQUEST,
6265 "osrfMethodException",
6267 "No active transaction -- required for DELETE"
6269 osrfAppRespondComplete( ctx, NULL );
6273 // The following test is harmless but redundant. If a class is
6274 // readonly, we don't register a delete method for it.
6275 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6276 osrfAppSessionStatus(
6278 OSRF_STATUS_BADREQUEST,
6279 "osrfMethodException",
6281 "Cannot DELETE readonly class"
6283 osrfAppRespondComplete( ctx, NULL );
6287 dbhandle = writehandle;
6289 char* pkey = osrfHashGet( meta, "primarykey" );
6296 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6297 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6298 osrfAppRespondComplete( ctx, NULL );
6302 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6304 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6305 osrfAppRespondComplete( ctx, NULL );
6308 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6313 "%s deleting %s object with %s = %s",
6315 osrfHashGet( meta, "fieldmapper" ),
6320 jsonObject* obj = jsonNewObject( id );
6322 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6323 dbi_conn_quote_string( writehandle, &id );
6325 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6326 osrfHashGet( meta, "tablename" ), pkey, id );
6331 jsonObjectFree( obj );
6332 obj = jsonNewObject( NULL );
6334 int errnum = dbi_conn_error( writehandle, &msg );
6337 "%s ERROR deleting %s object with %s = %s: %d %s",
6339 osrfHashGet( meta, "fieldmapper" ),
6343 msg ? msg : "(No description available)"
6345 osrfAppSessionStatus(
6347 OSRF_STATUS_INTERNALSERVERERROR,
6348 "osrfMethodException",
6350 "Error in deleting a row -- please see the error log for more details"
6352 if( !oilsIsDBConnected( writehandle ))
6353 osrfAppSessionPanic( ctx->session );
6355 dbi_result_free( result );
6359 osrfAppRespondComplete( ctx, obj );
6360 jsonObjectFree( obj );
6365 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6366 @param result An iterator for a result set; we only look at the current row.
6367 @param @meta Pointer to the class metadata for the core class.
6368 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6370 If a column is not defined in the IDL, or if it has no array_position defined for it in
6371 the IDL, or if it is defined as virtual, ignore it.
6373 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6374 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6375 array_position in the IDL.
6377 A field defined in the IDL but not represented in the returned row will leave a hole
6378 in the JSON_ARRAY. In effect it will be treated as a null value.
6380 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6381 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6382 classname corresponding to the @a meta argument.
6384 The calling code is responsible for freeing the the resulting jsonObject by calling
6387 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6388 if( !( result && meta )) return NULL;
6390 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6391 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6392 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6394 osrfHash* fields = osrfHashGet( meta, "fields" );
6396 int columnIndex = 1;
6397 const char* columnName;
6399 /* cycle through the columns in the row returned from the database */
6400 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6402 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6404 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6406 /* determine the field type and storage attributes */
6407 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6408 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6410 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6411 // or if it has no sequence number there, or if it's virtual, skip it.
6412 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6415 if( str_is_true( osrfHashGet( _f, "virtual" )))
6416 continue; // skip this column: IDL says it's virtual
6418 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6419 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6420 continue; // since we assign sequence numbers dynamically as we load the IDL.
6422 fmIndex = atoi( pos );
6423 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6425 continue; // This field is not defined in the IDL
6428 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6429 // sequence number from the IDL (which is likely to be different from the sequence
6430 // of columns in the SELECT clause).
6431 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6432 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6437 case DBI_TYPE_INTEGER :
6439 if( attr & DBI_INTEGER_SIZE8 )
6440 jsonObjectSetIndex( object, fmIndex,
6441 jsonNewNumberObject(
6442 dbi_result_get_longlong_idx( result, columnIndex )));
6444 jsonObjectSetIndex( object, fmIndex,
6445 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6449 case DBI_TYPE_DECIMAL :
6450 jsonObjectSetIndex( object, fmIndex,
6451 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6454 case DBI_TYPE_STRING :
6459 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6464 case DBI_TYPE_DATETIME : {
6466 char dt_string[ 256 ] = "";
6469 // Fetch the date column as a time_t
6470 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6472 // Translate the time_t to a human-readable string
6473 if( !( attr & DBI_DATETIME_DATE )) {
6474 gmtime_r( &_tmp_dt, &gmdt );
6475 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6476 } else if( !( attr & DBI_DATETIME_TIME )) {
6477 localtime_r( &_tmp_dt, &gmdt );
6478 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6480 localtime_r( &_tmp_dt, &gmdt );
6481 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6484 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6488 case DBI_TYPE_BINARY :
6489 osrfLogError( OSRF_LOG_MARK,
6490 "Can't do binary at column %s : index %d", columnName, columnIndex );
6499 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6500 if( !result ) return NULL;
6502 jsonObject* object = jsonNewObject( NULL );
6505 char dt_string[ 256 ];
6509 int columnIndex = 1;
6511 unsigned short type;
6512 const char* columnName;
6514 /* cycle through the column list */
6515 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6517 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6519 fmIndex = -1; // reset the position
6521 /* determine the field type and storage attributes */
6522 type = dbi_result_get_field_type_idx( result, columnIndex );
6523 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6525 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6526 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6531 case DBI_TYPE_INTEGER :
6533 if( attr & DBI_INTEGER_SIZE8 )
6534 jsonObjectSetKey( object, columnName,
6535 jsonNewNumberObject( dbi_result_get_longlong_idx(
6536 result, columnIndex )) );
6538 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6539 dbi_result_get_int_idx( result, columnIndex )) );
6542 case DBI_TYPE_DECIMAL :
6543 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6544 dbi_result_get_double_idx( result, columnIndex )) );
6547 case DBI_TYPE_STRING :
6548 jsonObjectSetKey( object, columnName,
6549 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6552 case DBI_TYPE_DATETIME :
6554 memset( dt_string, '\0', sizeof( dt_string ));
6555 memset( &gmdt, '\0', sizeof( gmdt ));
6557 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6559 if( !( attr & DBI_DATETIME_DATE )) {
6560 gmtime_r( &_tmp_dt, &gmdt );
6561 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6562 } else if( !( attr & DBI_DATETIME_TIME )) {
6563 localtime_r( &_tmp_dt, &gmdt );
6564 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6566 localtime_r( &_tmp_dt, &gmdt );
6567 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6570 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6573 case DBI_TYPE_BINARY :
6574 osrfLogError( OSRF_LOG_MARK,
6575 "Can't do binary at column %s : index %d", columnName, columnIndex );
6579 } // end while loop traversing result
6584 // Interpret a string as true or false
6585 int str_is_true( const char* str ) {
6586 if( NULL == str || strcasecmp( str, "true" ) )
6592 // Interpret a jsonObject as true or false
6593 static int obj_is_true( const jsonObject* obj ) {
6596 else switch( obj->type )
6604 if( strcasecmp( obj->value.s, "true" ) )
6608 case JSON_NUMBER : // Support 1/0 for perl's sake
6609 if( jsonObjectGetNumber( obj ) == 1.0 )
6618 // Translate a numeric code into a text string identifying a type of
6619 // jsonObject. To be used for building error messages.
6620 static const char* json_type( int code ) {
6626 return "JSON_ARRAY";
6628 return "JSON_STRING";
6630 return "JSON_NUMBER";
6636 return "(unrecognized)";
6640 // Extract the "primitive" attribute from an IDL field definition.
6641 // If we haven't initialized the app, then we must be running in
6642 // some kind of testbed. In that case, default to "string".
6643 static const char* get_primitive( osrfHash* field ) {
6644 const char* s = osrfHashGet( field, "primitive" );
6646 if( child_initialized )
6649 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6651 osrfHashGet( field, "name" )
6659 // Extract the "datatype" attribute from an IDL field definition.
6660 // If we haven't initialized the app, then we must be running in
6661 // some kind of testbed. In that case, default to to NUMERIC,
6662 // since we look at the datatype only for numbers.
6663 static const char* get_datatype( osrfHash* field ) {
6664 const char* s = osrfHashGet( field, "datatype" );
6666 if( child_initialized )
6669 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6671 osrfHashGet( field, "name" )
6680 @brief Determine whether a string is potentially a valid SQL identifier.
6681 @param s The identifier to be tested.
6682 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6684 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6685 need to follow all the rules exactly, such as requiring that the first character not
6688 We allow leading and trailing white space. In between, we do not allow punctuation
6689 (except for underscores and dollar signs), control characters, or embedded white space.
6691 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6692 for the foreseeable future such quoted identifiers are not likely to be an issue.
6694 int is_identifier( const char* s) {
6698 // Skip leading white space
6699 while( isspace( (unsigned char) *s ) )
6703 return 0; // Nothing but white space? Not okay.
6705 // Check each character until we reach white space or
6706 // end-of-string. Letters, digits, underscores, and
6707 // dollar signs are okay. With the exception of periods
6708 // (as in schema.identifier), control characters and other
6709 // punctuation characters are not okay. Anything else
6710 // is okay -- it could for example be part of a multibyte
6711 // UTF8 character such as a letter with diacritical marks,
6712 // and those are allowed.
6714 if( isalnum( (unsigned char) *s )
6718 ; // Fine; keep going
6719 else if( ispunct( (unsigned char) *s )
6720 || iscntrl( (unsigned char) *s ) )
6723 } while( *s && ! isspace( (unsigned char) *s ) );
6725 // If we found any white space in the above loop,
6726 // the rest had better be all white space.
6728 while( isspace( (unsigned char) *s ) )
6732 return 0; // White space was embedded within non-white space
6738 @brief Determine whether to accept a character string as a comparison operator.
6739 @param op The candidate comparison operator.
6740 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6742 We don't validate the operator for real. We just make sure that it doesn't contain
6743 any semicolons or white space (with special exceptions for a few specific operators).
6744 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6745 space but it's still not a valid operator, then the database will complain.
6747 Another approach would be to compare the string against a short list of approved operators.
6748 We don't do that because we want to allow custom operators like ">100*", which at this
6749 writing would be difficult or impossible to express otherwise in a JSON query.
6751 int is_good_operator( const char* op ) {
6752 if( !op ) return 0; // Sanity check
6756 if( isspace( (unsigned char) *s ) ) {
6757 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6758 // and IS NOT DISTINCT FROM.
6759 if( !strcasecmp( op, "similar to" ) )
6761 else if( !strcasecmp( op, "is distinct from" ) )
6763 else if( !strcasecmp( op, "is not distinct from" ) )
6768 else if( ';' == *s )
6776 @name Query Frame Management
6778 The following machinery supports a stack of query frames for use by SELECT().
6780 A query frame caches information about one level of a SELECT query. When we enter
6781 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6783 The query frame stores information about the core class, and about any joined classes
6786 The main purpose is to map table aliases to classes and tables, so that a query can
6787 join to the same table more than once. A secondary goal is to reduce the number of
6788 lookups in the IDL by caching the results.
6792 #define STATIC_CLASS_INFO_COUNT 3
6794 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6797 @brief Allocate a ClassInfo as raw memory.
6798 @return Pointer to the newly allocated ClassInfo.
6800 Except for the in_use flag, which is used only by the allocation and deallocation
6801 logic, we don't initialize the ClassInfo here.
6803 static ClassInfo* allocate_class_info( void ) {
6804 // In order to reduce the number of mallocs and frees, we return a static
6805 // instance of ClassInfo, if we can find one that we're not already using.
6806 // We rely on the fact that the compiler will implicitly initialize the
6807 // static instances so that in_use == 0.
6810 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6811 if( ! static_class_info[ i ].in_use ) {
6812 static_class_info[ i ].in_use = 1;
6813 return static_class_info + i;
6817 // The static ones are all in use. Malloc one.
6819 return safe_malloc( sizeof( ClassInfo ) );
6823 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6824 @param info Pointer to the ClassInfo to be cleared.
6826 static void clear_class_info( ClassInfo* info ) {
6831 // Free any malloc'd strings
6833 if( info->alias != info->alias_store )
6834 free( info->alias );
6836 if( info->class_name != info->class_name_store )
6837 free( info->class_name );
6839 free( info->source_def );
6841 info->alias = info->class_name = info->source_def = NULL;
6846 @brief Free a ClassInfo and everything it owns.
6847 @param info Pointer to the ClassInfo to be freed.
6849 static void free_class_info( ClassInfo* info ) {
6854 clear_class_info( info );
6856 // If it's one of the static instances, just mark it as not in use
6859 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6860 if( info == static_class_info + i ) {
6861 static_class_info[ i ].in_use = 0;
6866 // Otherwise it must have been malloc'd, so free it
6872 @brief Populate an already-allocated ClassInfo.
6873 @param info Pointer to the ClassInfo to be populated.
6874 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6876 @param class Name of the class.
6877 @return Zero if successful, or 1 if not.
6879 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6880 the relevant portions of the IDL for the specified class.
6882 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6885 osrfLogError( OSRF_LOG_MARK,
6886 "%s ERROR: No ClassInfo available to populate", modulename );
6887 info->alias = info->class_name = info->source_def = NULL;
6888 info->class_def = info->fields = info->links = NULL;
6893 osrfLogError( OSRF_LOG_MARK,
6894 "%s ERROR: No class name provided for lookup", modulename );
6895 info->alias = info->class_name = info->source_def = NULL;
6896 info->class_def = info->fields = info->links = NULL;
6900 // Alias defaults to class name if not supplied
6901 if( ! alias || ! alias[ 0 ] )
6904 // Look up class info in the IDL
6905 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6907 osrfLogError( OSRF_LOG_MARK,
6908 "%s ERROR: Class %s not defined in IDL", modulename, class );
6909 info->alias = info->class_name = info->source_def = NULL;
6910 info->class_def = info->fields = info->links = NULL;
6912 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6913 osrfLogError( OSRF_LOG_MARK,
6914 "%s ERROR: Class %s is defined as virtual", modulename, class );
6915 info->alias = info->class_name = info->source_def = NULL;
6916 info->class_def = info->fields = info->links = NULL;
6920 osrfHash* links = osrfHashGet( class_def, "links" );
6922 osrfLogError( OSRF_LOG_MARK,
6923 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6924 info->alias = info->class_name = info->source_def = NULL;
6925 info->class_def = info->fields = info->links = NULL;
6929 osrfHash* fields = osrfHashGet( class_def, "fields" );
6931 osrfLogError( OSRF_LOG_MARK,
6932 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6933 info->alias = info->class_name = info->source_def = NULL;
6934 info->class_def = info->fields = info->links = NULL;
6938 char* source_def = oilsGetRelation( class_def );
6942 // We got everything we need, so populate the ClassInfo
6943 if( strlen( alias ) > ALIAS_STORE_SIZE )
6944 info->alias = strdup( alias );
6946 strcpy( info->alias_store, alias );
6947 info->alias = info->alias_store;
6950 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6951 info->class_name = strdup( class );
6953 strcpy( info->class_name_store, class );
6954 info->class_name = info->class_name_store;
6957 info->source_def = source_def;
6959 info->class_def = class_def;
6960 info->links = links;
6961 info->fields = fields;
6966 #define STATIC_FRAME_COUNT 3
6968 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6971 @brief Allocate a QueryFrame as raw memory.
6972 @return Pointer to the newly allocated QueryFrame.
6974 Except for the in_use flag, which is used only by the allocation and deallocation
6975 logic, we don't initialize the QueryFrame here.
6977 static QueryFrame* allocate_frame( void ) {
6978 // In order to reduce the number of mallocs and frees, we return a static
6979 // instance of QueryFrame, if we can find one that we're not already using.
6980 // We rely on the fact that the compiler will implicitly initialize the
6981 // static instances so that in_use == 0.
6984 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6985 if( ! static_frame[ i ].in_use ) {
6986 static_frame[ i ].in_use = 1;
6987 return static_frame + i;
6991 // The static ones are all in use. Malloc one.
6993 return safe_malloc( sizeof( QueryFrame ) );
6997 @brief Free a QueryFrame, and all the memory it owns.
6998 @param frame Pointer to the QueryFrame to be freed.
7000 static void free_query_frame( QueryFrame* frame ) {
7005 clear_class_info( &frame->core );
7007 // Free the join list
7009 ClassInfo* info = frame->join_list;
7012 free_class_info( info );
7016 frame->join_list = NULL;
7019 // If the frame is a static instance, just mark it as unused
7021 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7022 if( frame == static_frame + i ) {
7023 static_frame[ i ].in_use = 0;
7028 // Otherwise it must have been malloc'd, so free it
7034 @brief Search a given QueryFrame for a specified alias.
7035 @param frame Pointer to the QueryFrame to be searched.
7036 @param target The alias for which to search.
7037 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7039 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7040 if( ! frame || ! target ) {
7044 ClassInfo* found_class = NULL;
7046 if( !strcmp( target, frame->core.alias ) )
7047 return &(frame->core);
7049 ClassInfo* curr_class = frame->join_list;
7050 while( curr_class ) {
7051 if( strcmp( target, curr_class->alias ) )
7052 curr_class = curr_class->next;
7054 found_class = curr_class;
7064 @brief Push a new (blank) QueryFrame onto the stack.
7066 static void push_query_frame( void ) {
7067 QueryFrame* frame = allocate_frame();
7068 frame->join_list = NULL;
7069 frame->next = curr_query;
7071 // Initialize the ClassInfo for the core class
7072 ClassInfo* core = &frame->core;
7073 core->alias = core->class_name = core->source_def = NULL;
7074 core->class_def = core->fields = core->links = NULL;
7080 @brief Pop a QueryFrame off the stack and destroy it.
7082 static void pop_query_frame( void ) {
7087 QueryFrame* popped = curr_query;
7088 curr_query = popped->next;
7090 free_query_frame( popped );
7094 @brief Populate the ClassInfo for the core class.
7095 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7096 class name as an alias.
7097 @param class_name Name of the core class.
7098 @return Zero if successful, or 1 if not.
7100 Populate the ClassInfo of the core class with copies of the alias and class name, and
7101 with pointers to the relevant portions of the IDL for the core class.
7103 static int add_query_core( const char* alias, const char* class_name ) {
7106 if( ! curr_query ) {
7107 osrfLogError( OSRF_LOG_MARK,
7108 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7110 } else if( curr_query->core.alias ) {
7111 osrfLogError( OSRF_LOG_MARK,
7112 "%s ERROR: Core class %s already populated as %s",
7113 modulename, curr_query->core.class_name, curr_query->core.alias );
7117 build_class_info( &curr_query->core, alias, class_name );
7118 if( curr_query->core.alias )
7121 osrfLogError( OSRF_LOG_MARK,
7122 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7128 @brief Search the current QueryFrame for a specified alias.
7129 @param target The alias for which to search.
7130 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7132 static inline ClassInfo* search_alias( const char* target ) {
7133 return search_alias_in_frame( curr_query, target );
7137 @brief Search all levels of query for a specified alias, starting with the current query.
7138 @param target The alias for which to search.
7139 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7141 static ClassInfo* search_all_alias( const char* target ) {
7142 ClassInfo* found_class = NULL;
7143 QueryFrame* curr_frame = curr_query;
7145 while( curr_frame ) {
7146 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7149 curr_frame = curr_frame->next;
7156 @brief Add a class to the list of classes joined to the current query.
7157 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7158 the class name as an alias.
7159 @param classname The name of the class to be added.
7160 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7162 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7164 if( ! classname || ! *classname ) { // sanity check
7165 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7172 const ClassInfo* conflict = search_alias( alias );
7174 osrfLogError( OSRF_LOG_MARK,
7175 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7176 modulename, alias, conflict->class_name );
7180 ClassInfo* info = allocate_class_info();
7182 if( build_class_info( info, alias, classname ) ) {
7183 free_class_info( info );
7187 // Add the new ClassInfo to the join list of the current QueryFrame
7188 info->next = curr_query->join_list;
7189 curr_query->join_list = info;
7195 @brief Destroy all nodes on the query stack.
7197 static void clear_query_stack( void ) {
7203 @brief Implement the set_audit_info method.
7204 @param ctx Pointer to the method context.
7205 @return Zero if successful, or -1 if not.
7207 Issue a SAVEPOINT to the database server.
7212 - workstation id (int)
7214 If user id is not provided the authkey will be used.
7215 For PCRUD the authkey is always used, even if a user is provided.
7217 int setAuditInfo( osrfMethodContext* ctx ) {
7218 if(osrfMethodVerifyContext( ctx )) {
7219 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
7223 // Get the user id from the parameters
7224 const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7226 if( enforce_pcrud || !user_id ) {
7227 timeout_needs_resetting = 1;
7228 const jsonObject* user = verifyUserPCRUD( ctx );
7231 osrfAppRespondComplete( ctx, NULL );
7235 // Not PCRUD and have a user_id?
7236 int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7237 osrfAppRespondComplete( ctx, NULL );
7242 @brief Save a audit info
7243 @param ctx Pointer to the method context.
7244 @param user_id User ID to write as a string
7245 @param ws_id Workstation ID to write as a string
7247 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7248 if( ctx && ctx->session ) {
7249 osrfAppSession* session = ctx->session;
7251 osrfHash* cache = session->userData;
7253 // If the session doesn't already have a hash, create one. Make sure
7254 // that the application session frees the hash when it terminates.
7255 if( NULL == cache ) {
7256 session->userData = cache = osrfNewHash();
7257 osrfHashSetCallback( cache, &sessionDataFree );
7258 ctx->session->userDataFree = &userDataFree;
7261 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7263 osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7265 int errnum = dbi_conn_error( writehandle, &msg );
7268 "%s: Error setting auditor information: %d %s",
7271 msg ? msg : "(No description available)"
7273 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7274 "osrfMethodException", ctx->request, "Error setting auditor info" );
7275 if( !oilsIsDBConnected( writehandle ))
7276 osrfAppSessionPanic( ctx->session );
7279 dbi_result_free( result );
7286 @brief Remove all but safe character from savepoint name
7287 @param sp User-supplied savepoint name
7288 @return sanitized savepoint name, or NULL
7290 The caller is expected to free the returned string. Note that
7291 this function exists only because we can't use PQescapeLiteral
7292 without either forking libdbi or abandoning it.
7294 static char* _sanitize_savepoint_name( const char* sp ) {
7296 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_";
7298 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7299 // and the default value of NAMEDATALEN is 64; that should be long enough
7300 // for our purposes, and it's unlikely that anyone is going to recompile
7301 // PostgreSQL to have a smaller value, so cap the identifier name
7302 // accordingly to avoid the remote chance that someone manages to pass in a
7303 // 12GB savepoint name
7304 const int MAX_LITERAL_NAMELEN = 63;
7307 if (len > MAX_LITERAL_NAMELEN) {
7308 len = MAX_LITERAL_NAMELEN;
7311 char* safeSpName = safe_malloc( len + 1 );
7315 for (j = 0; j < len; j++) {
7316 found = strchr(safe_chars, sp[j]);
7318 safeSpName[ i++ ] = found[0];
7321 safeSpName[ i ] = '\0';