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) );
893 char *safeSpName = _sanitize_savepoint_name( spName );
895 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", safeSpName );
899 int errnum = dbi_conn_error( writehandle, &msg );
902 "%s: Error creating savepoint %s in transaction %s: %d %s",
907 msg ? msg : "(No description available)"
909 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
910 "osrfMethodException", ctx->request, "Error creating savepoint" );
911 if( !oilsIsDBConnected( writehandle ))
912 osrfAppSessionPanic( ctx->session );
915 dbi_result_free( result );
916 jsonObject* ret = jsonNewObject( spName );
917 osrfAppRespondComplete( ctx, ret );
918 jsonObjectFree( ret );
924 @brief Implement the savepoint.release method.
925 @param ctx Pointer to the method context.
926 @return Zero if successful, or -1 if not.
928 Issue a RELEASE SAVEPOINT to the database server.
931 - authkey (PCRUD only)
934 Return to client: Savepoint name
936 int releaseSavepoint( osrfMethodContext* ctx ) {
937 if(osrfMethodVerifyContext( ctx )) {
938 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
943 if( enforce_pcrud ) {
945 timeout_needs_resetting = 1;
946 const jsonObject* user = verifyUserPCRUD( ctx );
951 // Verify that a transaction is pending
952 const char* trans_id = getXactId( ctx );
953 if( NULL == trans_id ) {
954 osrfAppSessionStatus(
956 OSRF_STATUS_INTERNALSERVERERROR,
957 "osrfMethodException",
959 "No active transaction -- required for savepoints"
964 // Get the savepoint name from the method params
965 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
966 char *safeSpName = _sanitize_savepoint_name( spName );
968 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", safeSpName );
972 int errnum = dbi_conn_error( writehandle, &msg );
975 "%s: Error releasing savepoint %s in transaction %s: %d %s",
980 msg ? msg : "(No description available)"
982 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
983 "osrfMethodException", ctx->request, "Error releasing savepoint" );
984 if( !oilsIsDBConnected( writehandle ))
985 osrfAppSessionPanic( ctx->session );
988 dbi_result_free( result );
989 jsonObject* ret = jsonNewObject( spName );
990 osrfAppRespondComplete( ctx, ret );
991 jsonObjectFree( ret );
997 @brief Implement the savepoint.rollback method.
998 @param ctx Pointer to the method context.
999 @return Zero if successful, or -1 if not.
1001 Issue a ROLLBACK TO SAVEPOINT to the database server.
1004 - authkey (PCRUD only)
1007 Return to client: Savepoint name
1009 int rollbackSavepoint( osrfMethodContext* ctx ) {
1010 if(osrfMethodVerifyContext( ctx )) {
1011 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1016 if( enforce_pcrud ) {
1018 timeout_needs_resetting = 1;
1019 const jsonObject* user = verifyUserPCRUD( ctx );
1024 // Verify that a transaction is pending
1025 const char* trans_id = getXactId( ctx );
1026 if( NULL == trans_id ) {
1027 osrfAppSessionStatus(
1029 OSRF_STATUS_INTERNALSERVERERROR,
1030 "osrfMethodException",
1032 "No active transaction -- required for savepoints"
1037 // Get the savepoint name from the method params
1038 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1039 char *safeSpName = _sanitize_savepoint_name( spName );
1041 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", safeSpName );
1045 int errnum = dbi_conn_error( writehandle, &msg );
1048 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
1053 msg ? msg : "(No description available)"
1055 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1056 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1057 if( !oilsIsDBConnected( writehandle ))
1058 osrfAppSessionPanic( ctx->session );
1061 dbi_result_free( result );
1062 jsonObject* ret = jsonNewObject( spName );
1063 osrfAppRespondComplete( ctx, ret );
1064 jsonObjectFree( ret );
1070 @brief Implement the transaction.commit method.
1071 @param ctx Pointer to the method context.
1072 @return Zero if successful, or -1 if not.
1074 Issue a COMMIT to the database server.
1077 - authkey (PCRUD only)
1079 Return to client: Transaction ID.
1081 int commitTransaction( osrfMethodContext* ctx ) {
1082 if(osrfMethodVerifyContext( ctx )) {
1083 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1087 if( enforce_pcrud ) {
1088 timeout_needs_resetting = 1;
1089 const jsonObject* user = verifyUserPCRUD( ctx );
1094 // Verify that a transaction is pending
1095 const char* trans_id = getXactId( ctx );
1096 if( NULL == trans_id ) {
1097 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1098 "osrfMethodException", ctx->request, "No active transaction to commit" );
1102 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1105 int errnum = dbi_conn_error( writehandle, &msg );
1106 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1107 modulename, errnum, msg ? msg : "(No description available)" );
1108 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1109 "osrfMethodException", ctx->request, "Error committing transaction" );
1110 if( !oilsIsDBConnected( writehandle ))
1111 osrfAppSessionPanic( ctx->session );
1114 dbi_result_free( result );
1115 jsonObject* ret = jsonNewObject( trans_id );
1116 osrfAppRespondComplete( ctx, ret );
1117 jsonObjectFree( ret );
1124 @brief Implement the transaction.rollback method.
1125 @param ctx Pointer to the method context.
1126 @return Zero if successful, or -1 if not.
1128 Issue a ROLLBACK to the database server.
1131 - authkey (PCRUD only)
1133 Return to client: Transaction ID
1135 int rollbackTransaction( osrfMethodContext* ctx ) {
1136 if( osrfMethodVerifyContext( ctx )) {
1137 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1141 if( enforce_pcrud ) {
1142 timeout_needs_resetting = 1;
1143 const jsonObject* user = verifyUserPCRUD( ctx );
1148 // Verify that a transaction is pending
1149 const char* trans_id = getXactId( ctx );
1150 if( NULL == trans_id ) {
1151 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1152 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1156 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1159 int errnum = dbi_conn_error( writehandle, &msg );
1160 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1161 modulename, errnum, msg ? msg : "(No description available)" );
1162 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1163 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1164 if( !oilsIsDBConnected( writehandle ))
1165 osrfAppSessionPanic( ctx->session );
1168 dbi_result_free( result );
1169 jsonObject* ret = jsonNewObject( trans_id );
1170 osrfAppRespondComplete( ctx, ret );
1171 jsonObjectFree( ret );
1178 @brief Implement the "search" method.
1179 @param ctx Pointer to the method context.
1180 @return Zero if successful, or -1 if not.
1183 - authkey (PCRUD only)
1184 - WHERE clause, as jsonObject
1185 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1187 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1188 Optionally flesh linked fields.
1190 int doSearch( osrfMethodContext* ctx ) {
1191 if( osrfMethodVerifyContext( ctx )) {
1192 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1197 timeout_needs_resetting = 1;
1199 jsonObject* where_clause;
1200 jsonObject* rest_of_query;
1202 if( enforce_pcrud ) {
1203 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1204 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1206 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1207 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1210 if( !where_clause ) {
1211 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1215 // Get the class metadata
1216 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1217 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1221 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1223 osrfAppRespondComplete( ctx, NULL );
1227 // doFieldmapperSearch() now takes care of our responding for us
1228 // // Return each row to the client
1229 // jsonObject* cur = 0;
1230 // unsigned long res_idx = 0;
1232 // while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1233 // // We used to discard based on perms here, but now that's
1234 // // inside doFieldmapperSearch()
1235 // osrfAppRespond( ctx, cur );
1238 jsonObjectFree( obj );
1240 osrfAppRespondComplete( ctx, NULL );
1245 @brief Implement the "id_list" method.
1246 @param ctx Pointer to the method context.
1247 @param err Pointer through which to return an error code.
1248 @return Zero if successful, or -1 if not.
1251 - authkey (PCRUD only)
1252 - WHERE clause, as jsonObject
1253 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1255 Return to client: The primary key values for all rows of the relevant class that
1256 satisfy a specified WHERE clause.
1258 This method relies on the assumption that every class has a primary key consisting of
1261 int doIdList( osrfMethodContext* ctx ) {
1262 if( osrfMethodVerifyContext( ctx )) {
1263 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1268 timeout_needs_resetting = 1;
1270 jsonObject* where_clause;
1271 jsonObject* rest_of_query;
1273 // We use the where clause without change. But we need to massage the rest of the
1274 // query, so we work with a copy of it instead of modifying the original.
1276 if( enforce_pcrud ) {
1277 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1278 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1280 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1281 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1284 if( !where_clause ) {
1285 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1289 // Eliminate certain SQL clauses, if present.
1290 if( rest_of_query ) {
1291 jsonObjectRemoveKey( rest_of_query, "select" );
1292 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1293 jsonObjectRemoveKey( rest_of_query, "flesh" );
1294 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1296 rest_of_query = jsonNewObjectType( JSON_HASH );
1299 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1301 // Get the class metadata
1302 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1303 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1305 // Build a SELECT list containing just the primary key,
1306 // i.e. like { "classname":["keyname"] }
1307 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1309 // Load array with name of primary key
1310 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1311 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1312 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1314 jsonObjectSetKey( rest_of_query, "select", select_clause );
1319 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1321 jsonObjectFree( rest_of_query );
1323 osrfAppRespondComplete( ctx, NULL );
1327 // Return each primary key value to the client
1329 unsigned long res_idx = 0;
1330 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1331 // We used to discard based on perms here, but now that's
1332 // inside doFieldmapperSearch()
1333 osrfAppRespond( ctx,
1334 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1337 jsonObjectFree( obj );
1338 osrfAppRespondComplete( ctx, NULL );
1343 @brief Verify that we have a valid class reference.
1344 @param ctx Pointer to the method context.
1345 @param param Pointer to the method parameters.
1346 @return 1 if the class reference is valid, or zero if it isn't.
1348 The class of the method params must match the class to which the method id devoted.
1349 For PCRUD there are additional restrictions.
1351 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1353 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1354 osrfHash* class = osrfHashGet( method_meta, "class" );
1356 // Compare the method's class to the parameters' class
1357 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1359 // Oops -- they don't match. Complain.
1360 growing_buffer* msg = buffer_init( 128 );
1363 "%s: %s method for type %s was passed a %s",
1365 osrfHashGet( method_meta, "methodtype" ),
1366 osrfHashGet( class, "classname" ),
1367 param->classname ? param->classname : "(null)"
1370 char* m = buffer_release( msg );
1371 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1379 return verifyObjectPCRUD( ctx, class, param, 1 );
1385 @brief (PCRUD only) Verify that the user is properly logged in.
1386 @param ctx Pointer to the method context.
1387 @return If the user is logged in, a pointer to the user object from the authentication
1388 server; otherwise NULL.
1390 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1392 // Get the authkey (the first method parameter)
1393 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1395 // See if we have the same authkey, and a user object,
1396 // locally cached from a previous call
1397 const char* cached_authkey = getAuthkey( ctx );
1398 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1399 const jsonObject* cached_user = getUserLogin( ctx );
1404 // We have no matching authentication data in the cache. Authenticate from scratch.
1405 jsonObject* auth_object = jsonNewObject( auth );
1407 // Fetch the user object from the authentication server
1408 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1410 jsonObjectFree( auth_object );
1412 if( !user->classname || strcmp(user->classname, "au" )) {
1414 growing_buffer* msg = buffer_init( 128 );
1417 "%s: permacrud received a bad auth token: %s",
1422 char* m = buffer_release( msg );
1423 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1427 jsonObjectFree( user );
1429 } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
1430 // Failed to set audit information - But note that write_audit_info already set error information.
1431 jsonObjectFree( user );
1435 setUserLogin( ctx, user );
1436 setAuthkey( ctx, auth );
1438 // Allow ourselves up to a second before we have to reset the login timeout.
1439 // It would be nice to use some fraction of the timeout interval enforced by the
1440 // authentication server, but that value is not readily available at this point.
1441 // Instead, we use a conservative default interval.
1442 time_next_reset = time( NULL ) + 1;
1448 @brief For PCRUD: Determine whether the current user may access the current row.
1449 @param ctx Pointer to the method context.
1450 @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1451 @param obj Pointer to the row being potentially accessed.
1452 @return 1 if access is permitted, or 0 if it isn't.
1454 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1456 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1458 dbhandle = writehandle;
1460 // Figure out what class and method are involved
1461 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1462 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1465 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1466 if (rs_size_from_hash) {
1467 rs_size = *rs_size_from_hash;
1468 osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1472 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1473 // contexts we will do another lookup of the current row, even if we already have a
1474 // previously fetched row image, because the row image in hand may not include the
1475 // foreign key(s) that we need.
1477 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1478 // but they aren't implemented yet.
1481 if( *method_type == 's' || *method_type == 'i' ) {
1482 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1484 } else if( *method_type == 'u' || *method_type == 'd' ) {
1485 fetch = 1; // MUST go to the db for the object for update and delete
1488 // Get the appropriate permacrud entry from the IDL, depending on method type
1489 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1491 // No permacrud for this method type on this class
1493 growing_buffer* msg = buffer_init( 128 );
1496 "%s: %s on class %s has no permacrud IDL entry",
1498 osrfHashGet( method_metadata, "methodtype" ),
1499 osrfHashGet( class, "classname" )
1502 char* m = buffer_release( msg );
1503 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1504 "osrfMethodException", ctx->request, m );
1511 // Get the user id, and make sure the user is logged in
1512 const jsonObject* user = verifyUserPCRUD( ctx );
1514 return 0; // Not logged in? No access.
1516 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1518 // Get a list of permissions from the permacrud entry.
1519 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1520 if( permission->size == 0 ) {
1523 "No permissions required for this action (class %s), passing through",
1524 osrfHashGet(class, "classname")
1529 // Build a list of org units that own the row. This is fairly convoluted because there
1530 // are several different ways that an org unit may own the row, as defined by the
1533 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1534 // identifying an owning org_unit..
1535 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1537 // Foreign context adds a layer of indirection. The row points to some other row that
1538 // an org unit may own. The "jump" attribute, if present, adds another layer of
1540 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1542 // The following string array stores the list of org units. (We don't have a thingie
1543 // for storing lists of integers, so we fake it with a list of strings.)
1544 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1547 const char* pkey_value = NULL;
1548 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1549 // If the global_required attribute is present and true, then the only owning
1550 // org unit is the root org unit, i.e. the one with no parent.
1551 osrfLogDebug( OSRF_LOG_MARK,
1552 "global-level permissions required, fetching top of the org tree" );
1554 // no need to check perms for org tree root retrieval
1555 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1556 // check for perm at top of org tree
1557 const char* org_tree_root_id = org_tree_root( ctx );
1558 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1560 if( org_tree_root_id ) {
1561 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1562 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1564 osrfStringArrayFree( context_org_array );
1569 // If the global_required attribute is absent or false, then we look for
1570 // local and/or foreign context. In order to find the relevant foreign
1571 // keys, we must either read the relevant row from the database, or look at
1572 // the image of the row that we already have in memory.
1574 // Even if we have an image of the row in memory, that image may not include the
1575 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1576 // of the row to make sure that we have what we need.
1578 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1579 "fetching context org ids" );
1580 const char* pkey = osrfHashGet( class, "primarykey" );
1581 jsonObject *param = NULL;
1584 // There is no primary key, so we can't do a fresh lookup. Use the row
1585 // image that we already have. If it doesn't have everything we need, too bad.
1587 param = jsonObjectClone( obj );
1588 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1589 } else if( obj->classname ) {
1590 pkey_value = oilsFMGetStringConst( obj, pkey );
1592 param = jsonObjectClone( obj );
1593 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1596 pkey_value = jsonObjectGetString( obj );
1598 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1599 "of %s and retrieving from the database", pkey_value );
1603 // Fetch the row so that we can look at the foreign key(s)
1604 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1605 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1606 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1607 jsonObjectFree( _tmp_params );
1608 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1610 param = jsonObjectExtractIndex( _list, 0 );
1611 jsonObjectFree( _list );
1615 // The row doesn't exist. Complain, and deny access.
1616 osrfLogDebug( OSRF_LOG_MARK,
1617 "Object not found in the database with primary key %s of %s",
1620 growing_buffer* msg = buffer_init( 128 );
1623 "%s: no object found with primary key %s of %s",
1629 char* m = buffer_release( msg );
1630 osrfAppSessionStatus(
1632 OSRF_STATUS_INTERNALSERVERERROR,
1633 "osrfMethodException",
1642 if( local_context && local_context->size > 0 ) {
1643 // The IDL provides a list of column names for the foreign keys denoting
1644 // local context, i.e. columns identifying owing org units directly. Look up
1645 // the value of each one, and if it isn't null, add it to the list of org units.
1646 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1647 local_context->size );
1649 const char* lcontext = NULL;
1650 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1651 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1652 if( fkey_value ) { // if not null
1653 osrfStringArrayAdd( context_org_array, fkey_value );
1656 "adding class-local field %s (value: %s) to the context org list",
1658 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1664 if( foreign_context ) {
1665 unsigned long class_count = osrfHashGetCount( foreign_context );
1666 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1668 if( class_count > 0 ) {
1670 // The IDL provides a list of foreign key columns pointing to rows that
1671 // an org unit may own. Follow each link, identify the owning org unit,
1672 // and add it to the list.
1673 osrfHash* fcontext = NULL;
1674 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1675 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1676 // For each class to which a foreign key points:
1677 const char* class_name = osrfHashIteratorKey( class_itr );
1678 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1682 "%d foreign context fields(s) specified for class %s",
1683 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1687 // Get the name of the key field in the foreign table
1688 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1690 // Get the value of the foreign key pointing to the foreign table
1691 char* foreign_pkey_value =
1692 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1693 if( !foreign_pkey_value )
1694 continue; // Foreign key value is null; skip it
1696 // Look up the row to which the foreign key points
1697 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1699 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1700 jsonObject* _list = doFieldmapperSearch(
1701 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1702 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1704 jsonObject* _fparam = NULL;
1705 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1706 _fparam = jsonObjectExtractIndex( _list, 0 );
1708 jsonObjectFree( _tmp_params );
1709 jsonObjectFree( _list );
1711 // At this point _fparam either points to the row identified by the
1712 // foreign key, or it's NULL (no such row found).
1714 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1716 const char* bad_class = NULL; // For noting failed lookups
1718 bad_class = class_name; // Referenced row not found
1719 else if( jump_list ) {
1720 // Follow a chain of rows, linked by foreign keys, to find an owner
1721 const char* flink = NULL;
1723 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1724 // For each entry in the jump list. Each entry (i.e. flink) is
1725 // the name of a foreign key column in the current row.
1727 // From the IDL, get the linkage information for the next jump
1728 osrfHash* foreign_link_hash =
1729 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1731 // Get the class metadata for the class
1732 // to which the foreign key points
1733 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1734 osrfHashGet( foreign_link_hash, "class" ));
1736 // Get the name of the referenced key of that class
1737 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1739 // Get the value of the foreign key pointing to that class
1740 free( foreign_pkey_value );
1741 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1742 if( !foreign_pkey_value )
1743 break; // Foreign key is null; quit looking
1745 // Build a WHERE clause for the lookup
1746 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1749 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1750 _tmp_params, NULL, &err );
1752 // Get the resulting row
1753 jsonObjectFree( _fparam );
1754 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1755 _fparam = jsonObjectExtractIndex( _list, 0 );
1757 // Referenced row not found
1759 bad_class = osrfHashGet( foreign_link_hash, "class" );
1762 jsonObjectFree( _tmp_params );
1763 jsonObjectFree( _list );
1769 // We had a foreign key pointing to such-and-such a row, but then
1770 // we couldn't fetch that row. The data in the database are in an
1771 // inconsistent state; the database itself may even be corrupted.
1772 growing_buffer* msg = buffer_init( 128 );
1775 "%s: no object of class %s found with primary key %s of %s",
1779 foreign_pkey_value ? foreign_pkey_value : "(null)"
1782 char* m = buffer_release( msg );
1783 osrfAppSessionStatus(
1785 OSRF_STATUS_INTERNALSERVERERROR,
1786 "osrfMethodException",
1792 osrfHashIteratorFree( class_itr );
1793 free( foreign_pkey_value );
1794 jsonObjectFree( param );
1799 free( foreign_pkey_value );
1802 // Examine each context column of the foreign row,
1803 // and add its value to the list of org units.
1805 const char* foreign_field = NULL;
1806 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1807 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1808 osrfStringArrayAdd( context_org_array,
1809 oilsFMGetStringConst( _fparam, foreign_field ));
1810 osrfLogDebug( OSRF_LOG_MARK,
1811 "adding foreign class %s field %s (value: %s) "
1812 "to the context org list",
1815 osrfStringArrayGetString(
1816 context_org_array, context_org_array->size - 1 )
1820 jsonObjectFree( _fparam );
1824 osrfHashIteratorFree( class_itr );
1828 jsonObjectFree( param );
1831 const char* context_org = NULL;
1832 const char* perm = NULL;
1835 // For every combination of permission and context org unit: call a stored procedure
1836 // to determine if the user has this permission in the context of this org unit.
1837 // If the answer is yes at any point, then we're done, and the user has permission.
1838 // In other words permissions are additive.
1840 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1843 osrfStringArray* pcache = NULL;
1844 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
1845 pcache = getPermLocationCache(ctx, perm);
1848 pcache = osrfNewStringArray(0);
1850 result = dbi_conn_queryf(
1852 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
1860 "Received a result for permission [%s] for user %d",
1865 if( dbi_result_first_row( result )) {
1867 jsonObject* return_val = oilsMakeJSONFromResult( result );
1868 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
1869 jsonObjectFree( return_val );
1870 } while( dbi_result_next_row( result ));
1872 setPermLocationCache(ctx, perm, pcache);
1875 dbi_result_free( result );
1881 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1883 if (rs_size > perm_at_threshold) {
1884 if (osrfStringArrayContains( pcache, context_org )) {
1893 "Checking object permission [%s] for user %d "
1894 "on object %s (class %s) at org %d",
1898 osrfHashGet( class, "classname" ),
1902 result = dbi_conn_queryf(
1904 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1907 osrfHashGet( class, "classname" ),
1915 "Received a result for object permission [%s] "
1916 "for user %d on object %s (class %s) at org %d",
1920 osrfHashGet( class, "classname" ),
1924 if( dbi_result_first_row( result )) {
1925 jsonObject* return_val = oilsMakeJSONFromResult( result );
1926 const char* has_perm = jsonObjectGetString(
1927 jsonObjectGetKeyConst( return_val, "has_perm" ));
1931 "Status of object permission [%s] for user %d "
1932 "on object %s (class %s) at org %d is %s",
1936 osrfHashGet(class, "classname"),
1941 if( *has_perm == 't' )
1943 jsonObjectFree( return_val );
1946 dbi_result_free( result );
1951 int errnum = dbi_conn_error( writehandle, &msg );
1952 osrfLogWarning( OSRF_LOG_MARK,
1953 "Unable to call check object permissions: %d, %s",
1954 errnum, msg ? msg : "(No description available)" );
1955 if( !oilsIsDBConnected( writehandle ))
1956 osrfAppSessionPanic( ctx->session );
1960 if (rs_size > perm_at_threshold) break;
1962 osrfLogDebug( OSRF_LOG_MARK,
1963 "Checking non-object permission [%s] for user %d at org %d",
1964 perm, userid, atoi(context_org) );
1965 result = dbi_conn_queryf(
1967 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1974 osrfLogDebug( OSRF_LOG_MARK,
1975 "Received a result for permission [%s] for user %d at org %d",
1976 perm, userid, atoi( context_org ));
1977 if( dbi_result_first_row( result )) {
1978 jsonObject* return_val = oilsMakeJSONFromResult( result );
1979 const char* has_perm = jsonObjectGetString(
1980 jsonObjectGetKeyConst( return_val, "has_perm" ));
1981 osrfLogDebug( OSRF_LOG_MARK,
1982 "Status of permission [%s] for user %d at org %d is [%s]",
1983 perm, userid, atoi( context_org ), has_perm );
1984 if( *has_perm == 't' )
1986 jsonObjectFree( return_val );
1989 dbi_result_free( result );
1994 int errnum = dbi_conn_error( writehandle, &msg );
1995 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
1996 errnum, msg ? msg : "(No description available)" );
1997 if( !oilsIsDBConnected( writehandle ))
1998 osrfAppSessionPanic( ctx->session );
2007 osrfStringArrayFree( context_org_array );
2013 @brief Look up the root of the org_unit tree.
2014 @param ctx Pointer to the method context.
2015 @return The id of the root org unit, as a character string.
2017 Query actor.org_unit where parent_ou is null, and return the id as a string.
2019 This function assumes that there is only one root org unit, i.e. that we
2020 have a single tree, not a forest.
2022 The calling code is responsible for freeing the returned string.
2024 static const char* org_tree_root( osrfMethodContext* ctx ) {
2026 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
2027 static time_t last_lookup_time = 0;
2028 time_t current_time = time( NULL );
2030 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2031 // We successfully looked this up less than an hour ago.
2032 // It's not likely to have changed since then.
2033 return strdup( cached_root_id );
2035 last_lookup_time = current_time;
2038 jsonObject* where_clause = single_hash( "parent_ou", NULL );
2039 jsonObject* result = doFieldmapperSearch(
2040 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2041 jsonObjectFree( where_clause );
2043 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2046 jsonObjectFree( result );
2048 growing_buffer* msg = buffer_init( 128 );
2049 OSRF_BUFFER_ADD( msg, modulename );
2050 OSRF_BUFFER_ADD( msg,
2051 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2053 char* m = buffer_release( msg );
2054 osrfAppSessionStatus( ctx->session,
2055 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2058 cached_root_id[ 0 ] = '\0';
2062 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2063 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2065 strcpy( cached_root_id, root_org_unit_id );
2066 jsonObjectFree( result );
2067 return cached_root_id;
2071 @brief Create a JSON_HASH with a single key/value pair.
2072 @param key The key of the key/value pair.
2073 @param value the value of the key/value pair.
2074 @return Pointer to a newly created jsonObject of type JSON_HASH.
2076 The value of the key/value is either a string or (if @a value is NULL) a null.
2078 static jsonObject* single_hash( const char* key, const char* value ) {
2080 if( ! key ) key = "";
2082 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2083 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2088 int doCreate( osrfMethodContext* ctx ) {
2089 if(osrfMethodVerifyContext( ctx )) {
2090 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2095 timeout_needs_resetting = 1;
2097 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2098 jsonObject* target = NULL;
2099 jsonObject* options = NULL;
2101 if( enforce_pcrud ) {
2102 target = jsonObjectGetIndex( ctx->params, 1 );
2103 options = jsonObjectGetIndex( ctx->params, 2 );
2105 target = jsonObjectGetIndex( ctx->params, 0 );
2106 options = jsonObjectGetIndex( ctx->params, 1 );
2109 if( !verifyObjectClass( ctx, target )) {
2110 osrfAppRespondComplete( ctx, NULL );
2114 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2116 const char* trans_id = getXactId( ctx );
2118 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2120 osrfAppSessionStatus(
2122 OSRF_STATUS_BADREQUEST,
2123 "osrfMethodException",
2125 "No active transaction -- required for CREATE"
2127 osrfAppRespondComplete( ctx, NULL );
2131 // The following test is harmless but redundant. If a class is
2132 // readonly, we don't register a create method for it.
2133 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2134 osrfAppSessionStatus(
2136 OSRF_STATUS_BADREQUEST,
2137 "osrfMethodException",
2139 "Cannot INSERT readonly class"
2141 osrfAppRespondComplete( ctx, NULL );
2145 // Set the last_xact_id
2146 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2148 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2149 trans_id, target->classname, index);
2150 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2153 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2155 dbhandle = writehandle;
2157 osrfHash* fields = osrfHashGet( meta, "fields" );
2158 char* pkey = osrfHashGet( meta, "primarykey" );
2159 char* seq = osrfHashGet( meta, "sequence" );
2161 growing_buffer* table_buf = buffer_init( 128 );
2162 growing_buffer* col_buf = buffer_init( 128 );
2163 growing_buffer* val_buf = buffer_init( 128 );
2165 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2166 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2167 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2168 buffer_add( val_buf,"VALUES (" );
2172 osrfHash* field = NULL;
2173 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2174 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2176 const char* field_name = osrfHashIteratorKey( field_itr );
2178 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2181 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2184 if( field_object && field_object->classname ) {
2185 value = oilsFMGetString(
2187 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2189 } else if( field_object && JSON_BOOL == field_object->type ) {
2190 if( jsonBoolIsTrue( field_object ) )
2191 value = strdup( "t" );
2193 value = strdup( "f" );
2195 value = jsonObjectToSimpleString( field_object );
2201 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2202 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2205 buffer_add( col_buf, field_name );
2207 if( !field_object || field_object->type == JSON_NULL ) {
2208 buffer_add( val_buf, "DEFAULT" );
2210 } else if( !strcmp( get_primitive( field ), "number" )) {
2211 const char* numtype = get_datatype( field );
2212 if( !strcmp( numtype, "INT8" )) {
2213 buffer_fadd( val_buf, "%lld", atoll( value ));
2215 } else if( !strcmp( numtype, "INT" )) {
2216 buffer_fadd( val_buf, "%d", atoi( value ));
2218 } else if( !strcmp( numtype, "NUMERIC" )) {
2219 buffer_fadd( val_buf, "%f", atof( value ));
2222 if( dbi_conn_quote_string( writehandle, &value )) {
2223 OSRF_BUFFER_ADD( val_buf, value );
2226 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2227 osrfAppSessionStatus(
2229 OSRF_STATUS_INTERNALSERVERERROR,
2230 "osrfMethodException",
2232 "Error quoting string -- please see the error log for more details"
2235 buffer_free( table_buf );
2236 buffer_free( col_buf );
2237 buffer_free( val_buf );
2238 osrfAppRespondComplete( ctx, NULL );
2246 osrfHashIteratorFree( field_itr );
2248 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2249 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2251 char* table_str = buffer_release( table_buf );
2252 char* col_str = buffer_release( col_buf );
2253 char* val_str = buffer_release( val_buf );
2254 growing_buffer* sql = buffer_init( 128 );
2255 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2260 char* query = buffer_release( sql );
2262 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2264 jsonObject* obj = NULL;
2267 dbi_result result = dbi_conn_query( writehandle, query );
2269 obj = jsonNewObject( NULL );
2271 int errnum = dbi_conn_error( writehandle, &msg );
2274 "%s ERROR inserting %s object using query [%s]: %d %s",
2276 osrfHashGet(meta, "fieldmapper"),
2279 msg ? msg : "(No description available)"
2281 osrfAppSessionStatus(
2283 OSRF_STATUS_INTERNALSERVERERROR,
2284 "osrfMethodException",
2286 "INSERT error -- please see the error log for more details"
2288 if( !oilsIsDBConnected( writehandle ))
2289 osrfAppSessionPanic( ctx->session );
2292 dbi_result_free( result );
2294 char* id = oilsFMGetString( target, pkey );
2296 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2297 growing_buffer* _id = buffer_init( 10 );
2298 buffer_fadd( _id, "%lld", new_id );
2299 id = buffer_release( _id );
2302 // Find quietness specification, if present
2303 const char* quiet_str = NULL;
2305 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2307 quiet_str = jsonObjectGetString( quiet_obj );
2310 if( str_is_true( quiet_str )) { // if quietness is specified
2311 obj = jsonNewObject( id );
2315 // Fetch the row that we just inserted, so that we can return it to the client
2316 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2317 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2320 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2324 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2326 jsonObjectFree( list );
2327 jsonObjectFree( where_clause );
2334 osrfAppRespondComplete( ctx, obj );
2335 jsonObjectFree( obj );
2340 @brief Implement the retrieve method.
2341 @param ctx Pointer to the method context.
2342 @param err Pointer through which to return an error code.
2343 @return If successful, a pointer to the result to be returned to the client;
2346 From the method's class, fetch a row with a specified value in the primary key. This
2347 method relies on the database design convention that a primary key consists of a single
2351 - authkey (PCRUD only)
2352 - value of the primary key for the desired row, for building the WHERE clause
2353 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2355 Return to client: One row from the query.
2357 int doRetrieve( osrfMethodContext* ctx ) {
2358 if(osrfMethodVerifyContext( ctx )) {
2359 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2364 timeout_needs_resetting = 1;
2369 if( enforce_pcrud ) {
2374 // Get the class metadata
2375 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2377 // Get the value of the primary key, from a method parameter
2378 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2382 "%s retrieving %s object with primary key value of %s",
2384 osrfHashGet( class_def, "fieldmapper" ),
2385 jsonObjectGetString( id_obj )
2388 // Build a WHERE clause based on the key value
2389 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2392 osrfHashGet( class_def, "primarykey" ), // name of key column
2393 jsonObjectClone( id_obj ) // value of key column
2396 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2400 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2402 jsonObjectFree( where_clause );
2404 osrfAppRespondComplete( ctx, NULL );
2408 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2409 jsonObjectFree( list );
2411 if( enforce_pcrud ) {
2412 // no result, skip this entirely
2413 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2414 jsonObjectFree( obj );
2416 growing_buffer* msg = buffer_init( 128 );
2417 OSRF_BUFFER_ADD( msg, modulename );
2418 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2420 char* m = buffer_release( msg );
2421 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2425 osrfAppRespondComplete( ctx, NULL );
2430 // doFieldmapperSearch() now does the responding for us
2431 //osrfAppRespondComplete( ctx, obj );
2432 osrfAppRespondComplete( ctx, NULL );
2434 jsonObjectFree( obj );
2439 @brief Translate a numeric value to a string representation for the database.
2440 @param field Pointer to the IDL field definition.
2441 @param value Pointer to a jsonObject holding the value of a field.
2442 @return Pointer to a newly allocated string.
2444 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2445 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2446 or (what is worse) valid SQL that is wrong.
2448 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2450 The calling code is responsible for freeing the resulting string by calling free().
2452 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2453 growing_buffer* val_buf = buffer_init( 32 );
2454 const char* numtype = get_datatype( field );
2456 // For historical reasons the following contains cruft that could be cleaned up.
2457 if( !strncmp( numtype, "INT", 3 ) ) {
2458 if( value->type == JSON_NUMBER )
2459 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2460 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2462 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2465 } else if( !strcmp( numtype, "NUMERIC" )) {
2466 if( value->type == JSON_NUMBER )
2467 buffer_fadd( val_buf, jsonObjectGetString( value ));
2469 buffer_fadd( val_buf, jsonObjectGetString( value ));
2473 // Presumably this was really intended to be a string, so quote it
2474 char* str = jsonObjectToSimpleString( value );
2475 if( dbi_conn_quote_string( dbhandle, &str )) {
2476 OSRF_BUFFER_ADD( val_buf, str );
2479 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2481 buffer_free( val_buf );
2486 return buffer_release( val_buf );
2489 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2490 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2491 growing_buffer* sql_buf = buffer_init( 32 );
2497 osrfHashGet( field, "name" )
2501 buffer_add( sql_buf, "IN (" );
2502 } else if( !strcasecmp( op,"not in" )) {
2503 buffer_add( sql_buf, "NOT IN (" );
2505 buffer_add( sql_buf, "IN (" );
2508 if( node->type == JSON_HASH ) {
2509 // subquery predicate
2510 char* subpred = buildQuery( ctx, node, SUBSELECT );
2512 buffer_free( sql_buf );
2516 buffer_add( sql_buf, subpred );
2519 } else if( node->type == JSON_ARRAY ) {
2520 // literal value list
2521 int in_item_index = 0;
2522 int in_item_first = 1;
2523 const jsonObject* in_item;
2524 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2529 buffer_add( sql_buf, ", " );
2532 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2533 osrfLogError( OSRF_LOG_MARK,
2534 "%s: Expected string or number within IN list; found %s",
2535 modulename, json_type( in_item->type ) );
2536 buffer_free( sql_buf );
2540 // Append the literal value -- quoted if not a number
2541 if( JSON_NUMBER == in_item->type ) {
2542 char* val = jsonNumberToDBString( field, in_item );
2543 OSRF_BUFFER_ADD( sql_buf, val );
2546 } else if( !strcmp( get_primitive( field ), "number" )) {
2547 char* val = jsonNumberToDBString( field, in_item );
2548 OSRF_BUFFER_ADD( sql_buf, val );
2552 char* key_string = jsonObjectToSimpleString( in_item );
2553 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2554 OSRF_BUFFER_ADD( sql_buf, key_string );
2557 osrfLogError( OSRF_LOG_MARK,
2558 "%s: Error quoting key string [%s]", modulename, key_string );
2560 buffer_free( sql_buf );
2566 if( in_item_first ) {
2567 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2568 buffer_free( sql_buf );
2572 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2573 modulename, json_type( node->type ));
2574 buffer_free( sql_buf );
2578 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2580 return buffer_release( sql_buf );
2583 // Receive a JSON_ARRAY representing a function call. The first
2584 // entry in the array is the function name. The rest are parameters.
2585 static char* searchValueTransform( const jsonObject* array ) {
2587 if( array->size < 1 ) {
2588 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2592 // Get the function name
2593 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2594 if( func_item->type != JSON_STRING ) {
2595 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2596 modulename, json_type( func_item->type ));
2600 growing_buffer* sql_buf = buffer_init( 32 );
2602 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2603 OSRF_BUFFER_ADD( sql_buf, "( " );
2605 // Get the parameters
2606 int func_item_index = 1; // We already grabbed the zeroth entry
2607 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2609 // Add a separator comma, if we need one
2610 if( func_item_index > 2 )
2611 buffer_add( sql_buf, ", " );
2613 // Add the current parameter
2614 if( func_item->type == JSON_NULL ) {
2615 buffer_add( sql_buf, "NULL" );
2617 if( func_item->type == JSON_BOOL ) {
2618 if( jsonBoolIsTrue(func_item) ) {
2619 buffer_add( sql_buf, "TRUE" );
2621 buffer_add( sql_buf, "FALSE" );
2624 char* val = jsonObjectToSimpleString( func_item );
2625 if( dbi_conn_quote_string( dbhandle, &val )) {
2626 OSRF_BUFFER_ADD( sql_buf, val );
2629 osrfLogError( OSRF_LOG_MARK,
2630 "%s: Error quoting key string [%s]", modulename, val );
2631 buffer_free( sql_buf );
2639 buffer_add( sql_buf, " )" );
2641 return buffer_release( sql_buf );
2644 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2645 const jsonObject* node, const char* op ) {
2647 if( ! is_good_operator( op ) ) {
2648 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2652 char* val = searchValueTransform( node );
2656 growing_buffer* sql_buf = buffer_init( 32 );
2661 osrfHashGet( field, "name" ),
2668 return buffer_release( sql_buf );
2671 // class_alias is a class name or other table alias
2672 // field is a field definition as stored in the IDL
2673 // node comes from the method parameter, and may represent an entry in the SELECT list
2674 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2675 const jsonObject* node ) {
2676 growing_buffer* sql_buf = buffer_init( 32 );
2678 const char* field_transform = jsonObjectGetString(
2679 jsonObjectGetKeyConst( node, "transform" ) );
2680 const char* transform_subcolumn = jsonObjectGetString(
2681 jsonObjectGetKeyConst( node, "result_field" ) );
2683 if( transform_subcolumn ) {
2684 if( ! is_identifier( transform_subcolumn ) ) {
2685 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2686 modulename, transform_subcolumn );
2687 buffer_free( sql_buf );
2690 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2693 if( field_transform ) {
2695 if( ! is_identifier( field_transform ) ) {
2696 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2697 modulename, field_transform );
2698 buffer_free( sql_buf );
2702 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2703 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2704 field_transform, class_alias, osrfHashGet( field, "name" ));
2706 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2707 field_transform, class_alias, osrfHashGet( field, "name" ));
2710 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2713 if( array->type != JSON_ARRAY ) {
2714 osrfLogError( OSRF_LOG_MARK,
2715 "%s: Expected JSON_ARRAY for function params; found %s",
2716 modulename, json_type( array->type ) );
2717 buffer_free( sql_buf );
2720 int func_item_index = 0;
2721 jsonObject* func_item;
2722 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2724 char* val = jsonObjectToSimpleString( func_item );
2727 buffer_add( sql_buf, ",NULL" );
2728 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2729 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2730 OSRF_BUFFER_ADD( sql_buf, val );
2732 osrfLogError( OSRF_LOG_MARK,
2733 "%s: Error quoting key string [%s]", modulename, val );
2735 buffer_free( sql_buf );
2742 buffer_add( sql_buf, " )" );
2745 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2748 if( transform_subcolumn )
2749 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2751 return buffer_release( sql_buf );
2754 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2755 const jsonObject* node, const char* op ) {
2757 if( ! is_good_operator( op ) ) {
2758 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2762 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2763 if( ! field_transform )
2766 int extra_parens = 0; // boolean
2768 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2770 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2772 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2774 free( field_transform );
2778 } else if( value_obj->type == JSON_ARRAY ) {
2779 value = searchValueTransform( value_obj );
2781 osrfLogError( OSRF_LOG_MARK,
2782 "%s: Error building value transform for field transform", modulename );
2783 free( field_transform );
2786 } else if( value_obj->type == JSON_HASH ) {
2787 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2789 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2791 free( field_transform );
2795 } else if( value_obj->type == JSON_NUMBER ) {
2796 value = jsonNumberToDBString( field, value_obj );
2797 } else if( value_obj->type == JSON_NULL ) {
2798 osrfLogError( OSRF_LOG_MARK,
2799 "%s: Error building predicate for field transform: null value", modulename );
2800 free( field_transform );
2802 } else if( value_obj->type == JSON_BOOL ) {
2803 osrfLogError( OSRF_LOG_MARK,
2804 "%s: Error building predicate for field transform: boolean value", modulename );
2805 free( field_transform );
2808 if( !strcmp( get_primitive( field ), "number") ) {
2809 value = jsonNumberToDBString( field, value_obj );
2811 value = jsonObjectToSimpleString( value_obj );
2812 if( !dbi_conn_quote_string( dbhandle, &value )) {
2813 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2814 modulename, value );
2816 free( field_transform );
2822 const char* left_parens = "";
2823 const char* right_parens = "";
2825 if( extra_parens ) {
2830 const char* right_percent = "";
2831 const char* real_op = op;
2833 if( !strcasecmp( op, "startwith") ) {
2835 right_percent = "|| '%'";
2838 growing_buffer* sql_buf = buffer_init( 32 );
2842 "%s%s %s %s %s%s %s%s",
2854 free( field_transform );
2856 return buffer_release( sql_buf );
2859 static char* searchSimplePredicate( const char* op, const char* class_alias,
2860 osrfHash* field, const jsonObject* node ) {
2862 if( ! is_good_operator( op ) ) {
2863 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2869 // Get the value to which we are comparing the specified column
2870 if( node->type != JSON_NULL ) {
2871 if( node->type == JSON_NUMBER ) {
2872 val = jsonNumberToDBString( field, node );
2873 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2874 val = jsonNumberToDBString( field, node );
2876 val = jsonObjectToSimpleString( node );
2881 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2882 // Value is not numeric; enclose it in quotes
2883 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2884 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2891 // Compare to a null value
2892 val = strdup( "NULL" );
2893 if( strcmp( op, "=" ))
2899 growing_buffer* sql_buf = buffer_init( 32 );
2900 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2901 char* pred = buffer_release( sql_buf );
2908 static char* searchBETWEENPredicate( const char* class_alias,
2909 osrfHash* field, const jsonObject* node ) {
2911 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2912 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2914 if( NULL == y_node ) {
2915 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2918 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2919 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2926 if( !strcmp( get_primitive( field ), "number") ) {
2927 x_string = jsonNumberToDBString( field, x_node );
2928 y_string = jsonNumberToDBString( field, y_node );
2931 x_string = jsonObjectToSimpleString( x_node );
2932 y_string = jsonObjectToSimpleString( y_node );
2933 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2934 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2935 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2936 modulename, x_string, y_string );
2943 growing_buffer* sql_buf = buffer_init( 32 );
2944 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2945 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2949 return buffer_release( sql_buf );
2952 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2953 jsonObject* node, osrfMethodContext* ctx ) {
2956 if( node->type == JSON_ARRAY ) { // equality IN search
2957 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2958 } else if( node->type == JSON_HASH ) { // other search
2959 jsonIterator* pred_itr = jsonNewIterator( node );
2960 if( !jsonIteratorHasNext( pred_itr ) ) {
2961 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2962 modulename, osrfHashGet(field, "name" ));
2964 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2966 // Verify that there are no additional predicates
2967 if( jsonIteratorHasNext( pred_itr ) ) {
2968 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2969 modulename, osrfHashGet(field, "name" ));
2970 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2971 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2972 else if( !(strcasecmp( pred_itr->key,"in" ))
2973 || !(strcasecmp( pred_itr->key,"not in" )) )
2974 pred = searchINPredicate(
2975 class_info->alias, field, pred_node, pred_itr->key, ctx );
2976 else if( pred_node->type == JSON_ARRAY )
2977 pred = searchFunctionPredicate(
2978 class_info->alias, field, pred_node, pred_itr->key );
2979 else if( pred_node->type == JSON_HASH )
2980 pred = searchFieldTransformPredicate(
2981 class_info, field, pred_node, pred_itr->key );
2983 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2985 jsonIteratorFree( pred_itr );
2987 } else if( node->type == JSON_NULL ) { // IS NULL search
2988 growing_buffer* _p = buffer_init( 64 );
2991 "\"%s\".%s IS NULL",
2993 osrfHashGet( field, "name" )
2995 pred = buffer_release( _p );
2996 } else { // equality search
2997 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3016 field : call_number,
3032 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3034 const jsonObject* working_hash;
3035 jsonObject* freeable_hash = NULL;
3037 if( join_hash->type == JSON_HASH ) {
3038 working_hash = join_hash;
3039 } else if( join_hash->type == JSON_STRING ) {
3040 // turn it into a JSON_HASH by creating a wrapper
3041 // around a copy of the original
3042 const char* _tmp = jsonObjectGetString( join_hash );
3043 freeable_hash = jsonNewObjectType( JSON_HASH );
3044 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3045 working_hash = freeable_hash;
3049 "%s: JOIN failed; expected JSON object type not found",
3055 growing_buffer* join_buf = buffer_init( 128 );
3056 const char* leftclass = left_info->class_name;
3058 jsonObject* snode = NULL;
3059 jsonIterator* search_itr = jsonNewIterator( working_hash );
3061 while ( (snode = jsonIteratorNext( search_itr )) ) {
3062 const char* right_alias = search_itr->key;
3064 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3066 class = right_alias;
3068 const ClassInfo* right_info = add_joined_class( right_alias, class );
3072 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3076 jsonIteratorFree( search_itr );
3077 buffer_free( join_buf );
3079 jsonObjectFree( freeable_hash );
3082 osrfHash* links = right_info->links;
3083 const char* table = right_info->source_def;
3085 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3086 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3088 if( field && !fkey ) {
3089 // Look up the corresponding join column in the IDL.
3090 // The link must be defined in the child table,
3091 // and point to the right parent table.
3092 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3093 const char* reltype = NULL;
3094 const char* other_class = NULL;
3095 reltype = osrfHashGet( idl_link, "reltype" );
3096 if( reltype && strcmp( reltype, "has_many" ) )
3097 other_class = osrfHashGet( idl_link, "class" );
3098 if( other_class && !strcmp( other_class, leftclass ) )
3099 fkey = osrfHashGet( idl_link, "key" );
3103 "%s: JOIN failed. No link defined from %s.%s to %s",
3109 buffer_free( join_buf );
3111 jsonObjectFree( freeable_hash );
3112 jsonIteratorFree( search_itr );
3116 } else if( !field && fkey ) {
3117 // Look up the corresponding join column in the IDL.
3118 // The link must be defined in the child table,
3119 // and point to the right parent table.
3120 osrfHash* left_links = left_info->links;
3121 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3122 const char* reltype = NULL;
3123 const char* other_class = NULL;
3124 reltype = osrfHashGet( idl_link, "reltype" );
3125 if( reltype && strcmp( reltype, "has_many" ) )
3126 other_class = osrfHashGet( idl_link, "class" );
3127 if( other_class && !strcmp( other_class, class ) )
3128 field = osrfHashGet( idl_link, "key" );
3132 "%s: JOIN failed. No link defined from %s.%s to %s",
3138 buffer_free( join_buf );
3140 jsonObjectFree( freeable_hash );
3141 jsonIteratorFree( search_itr );
3145 } else if( !field && !fkey ) {
3146 osrfHash* left_links = left_info->links;
3148 // For each link defined for the left class:
3149 // see if the link references the joined class
3150 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3151 osrfHash* curr_link = NULL;
3152 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3153 const char* other_class = osrfHashGet( curr_link, "class" );
3154 if( other_class && !strcmp( other_class, class ) ) {
3156 // In the IDL, the parent class doesn't always know then names of the child
3157 // columns that are pointing to it, so don't use that end of the link
3158 const char* reltype = osrfHashGet( curr_link, "reltype" );
3159 if( reltype && strcmp( reltype, "has_many" ) ) {
3160 // Found a link between the classes
3161 fkey = osrfHashIteratorKey( itr );
3162 field = osrfHashGet( curr_link, "key" );
3167 osrfHashIteratorFree( itr );
3169 if( !field || !fkey ) {
3170 // Do another such search, with the classes reversed
3172 // For each link defined for the joined class:
3173 // see if the link references the left class
3174 osrfHashIterator* itr = osrfNewHashIterator( links );
3175 osrfHash* curr_link = NULL;
3176 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3177 const char* other_class = osrfHashGet( curr_link, "class" );
3178 if( other_class && !strcmp( other_class, leftclass ) ) {
3180 // In the IDL, the parent class doesn't know then names of the child
3181 // columns that are pointing to it, so don't use that end of the link
3182 const char* reltype = osrfHashGet( curr_link, "reltype" );
3183 if( reltype && strcmp( reltype, "has_many" ) ) {
3184 // Found a link between the classes
3185 field = osrfHashIteratorKey( itr );
3186 fkey = osrfHashGet( curr_link, "key" );
3191 osrfHashIteratorFree( itr );
3194 if( !field || !fkey ) {
3197 "%s: JOIN failed. No link defined between %s and %s",
3202 buffer_free( join_buf );
3204 jsonObjectFree( freeable_hash );
3205 jsonIteratorFree( search_itr );
3210 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3212 if( !strcasecmp( type,"left" )) {
3213 buffer_add( join_buf, " LEFT JOIN" );
3214 } else if( !strcasecmp( type,"right" )) {
3215 buffer_add( join_buf, " RIGHT JOIN" );
3216 } else if( !strcasecmp( type,"full" )) {
3217 buffer_add( join_buf, " FULL JOIN" );
3219 buffer_add( join_buf, " INNER JOIN" );
3222 buffer_add( join_buf, " INNER JOIN" );
3225 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3226 table, right_alias, right_alias, field, left_info->alias, fkey );
3228 // Add any other join conditions as specified by "filter"
3229 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3231 const char* filter_op = jsonObjectGetString(
3232 jsonObjectGetKeyConst( snode, "filter_op" ) );
3233 if( filter_op && !strcasecmp( "or",filter_op )) {
3234 buffer_add( join_buf, " OR " );
3236 buffer_add( join_buf, " AND " );
3239 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3241 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3242 OSRF_BUFFER_ADD( join_buf, jpred );
3247 "%s: JOIN failed. Invalid conditional expression.",
3250 jsonIteratorFree( search_itr );
3251 buffer_free( join_buf );
3253 jsonObjectFree( freeable_hash );
3258 buffer_add( join_buf, " ) " );
3260 // Recursively add a nested join, if one is present
3261 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3263 char* jpred = searchJOIN( join_filter, right_info );
3265 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3266 OSRF_BUFFER_ADD( join_buf, jpred );
3269 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3270 jsonIteratorFree( search_itr );
3271 buffer_free( join_buf );
3273 jsonObjectFree( freeable_hash );
3280 jsonObjectFree( freeable_hash );
3281 jsonIteratorFree( search_itr );
3283 return buffer_release( join_buf );
3288 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3289 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3290 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3292 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3294 search_hash is the JSON expression of the conditions.
3295 meta is the class definition from the IDL, for the relevant table.
3296 opjoin_type indicates whether multiple conditions, if present, should be
3297 connected by AND or OR.
3298 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3299 to pass it to other functions -- and all they do with it is to use the session
3300 and request members to send error messages back to the client.
3304 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3305 int opjoin_type, osrfMethodContext* ctx ) {
3309 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3310 "opjoin_type = %d, ctx addr = %p",
3313 class_info->class_def,
3318 growing_buffer* sql_buf = buffer_init( 128 );
3320 jsonObject* node = NULL;
3323 if( search_hash->type == JSON_ARRAY ) {
3324 if( 0 == search_hash->size ) {
3327 "%s: Invalid predicate structure: empty JSON array",
3330 buffer_free( sql_buf );
3334 unsigned long i = 0;
3335 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3339 if( opjoin_type == OR_OP_JOIN )
3340 buffer_add( sql_buf, " OR " );
3342 buffer_add( sql_buf, " AND " );
3345 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3347 buffer_free( sql_buf );
3351 buffer_fadd( sql_buf, "( %s )", subpred );
3355 } else if( search_hash->type == JSON_HASH ) {
3356 osrfLogDebug( OSRF_LOG_MARK,
3357 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3358 jsonIterator* search_itr = jsonNewIterator( search_hash );
3359 if( !jsonIteratorHasNext( search_itr ) ) {
3362 "%s: Invalid predicate structure: empty JSON object",
3365 jsonIteratorFree( search_itr );
3366 buffer_free( sql_buf );
3370 while( (node = jsonIteratorNext( search_itr )) ) {
3375 if( opjoin_type == OR_OP_JOIN )
3376 buffer_add( sql_buf, " OR " );
3378 buffer_add( sql_buf, " AND " );
3381 if( '+' == search_itr->key[ 0 ] ) {
3383 // This plus sign prefixes a class name or other table alias;
3384 // make sure the table alias is in scope
3385 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3386 if( ! alias_info ) {
3389 "%s: Invalid table alias \"%s\" in WHERE clause",
3393 jsonIteratorFree( search_itr );
3394 buffer_free( sql_buf );
3398 if( node->type == JSON_STRING ) {
3399 // It's the name of a column; make sure it belongs to the class
3400 const char* fieldname = jsonObjectGetString( node );
3401 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3404 "%s: Invalid column name \"%s\" in WHERE clause "
3405 "for table alias \"%s\"",
3410 jsonIteratorFree( search_itr );
3411 buffer_free( sql_buf );
3415 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3417 // It's something more complicated
3418 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3420 jsonIteratorFree( search_itr );
3421 buffer_free( sql_buf );
3425 buffer_fadd( sql_buf, "( %s )", subpred );
3428 } else if( '-' == search_itr->key[ 0 ] ) {
3429 if( !strcasecmp( "-or", search_itr->key )) {
3430 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3432 jsonIteratorFree( search_itr );
3433 buffer_free( sql_buf );
3437 buffer_fadd( sql_buf, "( %s )", subpred );
3439 } else if( !strcasecmp( "-and", search_itr->key )) {
3440 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3442 jsonIteratorFree( search_itr );
3443 buffer_free( sql_buf );
3447 buffer_fadd( sql_buf, "( %s )", subpred );
3449 } else if( !strcasecmp("-not",search_itr->key) ) {
3450 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3452 jsonIteratorFree( search_itr );
3453 buffer_free( sql_buf );
3457 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3459 } else if( !strcasecmp( "-exists", search_itr->key )) {
3460 char* subpred = buildQuery( ctx, node, SUBSELECT );
3462 jsonIteratorFree( search_itr );
3463 buffer_free( sql_buf );
3467 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3469 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3470 char* subpred = buildQuery( ctx, node, SUBSELECT );
3472 jsonIteratorFree( search_itr );
3473 buffer_free( sql_buf );
3477 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3479 } else { // Invalid "minus" operator
3482 "%s: Invalid operator \"%s\" in WHERE clause",
3486 jsonIteratorFree( search_itr );
3487 buffer_free( sql_buf );
3493 const char* class = class_info->class_name;
3494 osrfHash* fields = class_info->fields;
3495 osrfHash* field = osrfHashGet( fields, search_itr->key );
3498 const char* table = class_info->source_def;
3501 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3504 table ? table : "?",
3507 jsonIteratorFree( search_itr );
3508 buffer_free( sql_buf );
3512 char* subpred = searchPredicate( class_info, field, node, ctx );
3514 buffer_free( sql_buf );
3515 jsonIteratorFree( search_itr );
3519 buffer_add( sql_buf, subpred );
3523 jsonIteratorFree( search_itr );
3526 // ERROR ... only hash and array allowed at this level
3527 char* predicate_string = jsonObjectToJSON( search_hash );
3530 "%s: Invalid predicate structure: %s",
3534 buffer_free( sql_buf );
3535 free( predicate_string );
3539 return buffer_release( sql_buf );
3542 /* Build a JSON_ARRAY of field names for a given table alias
3544 static jsonObject* defaultSelectList( const char* table_alias ) {
3549 ClassInfo* class_info = search_all_alias( table_alias );
3550 if( ! class_info ) {
3553 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3560 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3561 osrfHash* field_def = NULL;
3562 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3563 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3564 const char* field_name = osrfHashIteratorKey( field_itr );
3565 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3566 jsonObjectPush( array, jsonNewObject( field_name ) );
3569 osrfHashIteratorFree( field_itr );
3574 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3575 // The jsonObject must be a JSON_HASH with an single entry for "union",
3576 // "intersect", or "except". The data associated with this key must be an
3577 // array of hashes, each hash being a query.
3578 // Also allowed but currently ignored: entries for "order_by" and "alias".
3579 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3581 if( ! combo || combo->type != JSON_HASH )
3582 return NULL; // should be impossible; validated by caller
3584 const jsonObject* query_array = NULL; // array of subordinate queries
3585 const char* op = NULL; // name of operator, e.g. UNION
3586 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3587 int op_count = 0; // for detecting conflicting operators
3588 int excepting = 0; // boolean
3589 int all = 0; // boolean
3590 jsonObject* order_obj = NULL;
3592 // Identify the elements in the hash
3593 jsonIterator* query_itr = jsonNewIterator( combo );
3594 jsonObject* curr_obj = NULL;
3595 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3596 if( ! strcmp( "union", query_itr->key ) ) {
3599 query_array = curr_obj;
3600 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3603 query_array = curr_obj;
3604 } else if( ! strcmp( "except", query_itr->key ) ) {
3608 query_array = curr_obj;
3609 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3612 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3615 order_obj = curr_obj;
3616 } else if( ! strcmp( "alias", query_itr->key ) ) {
3617 if( curr_obj->type != JSON_STRING ) {
3618 jsonIteratorFree( query_itr );
3621 alias = jsonObjectGetString( curr_obj );
3622 } else if( ! strcmp( "all", query_itr->key ) ) {
3623 if( obj_is_true( curr_obj ) )
3627 osrfAppSessionStatus(
3629 OSRF_STATUS_INTERNALSERVERERROR,
3630 "osrfMethodException",
3632 "Malformed query; unexpected entry in query object"
3636 "%s: Unexpected entry for \"%s\" in%squery",
3641 jsonIteratorFree( query_itr );
3645 jsonIteratorFree( query_itr );
3647 // More sanity checks
3648 if( ! query_array ) {
3650 osrfAppSessionStatus(
3652 OSRF_STATUS_INTERNALSERVERERROR,
3653 "osrfMethodException",
3655 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3659 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3662 return NULL; // should be impossible...
3663 } else if( op_count > 1 ) {
3665 osrfAppSessionStatus(
3667 OSRF_STATUS_INTERNALSERVERERROR,
3668 "osrfMethodException",
3670 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3674 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3678 } if( query_array->type != JSON_ARRAY ) {
3680 osrfAppSessionStatus(
3682 OSRF_STATUS_INTERNALSERVERERROR,
3683 "osrfMethodException",
3685 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3689 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3692 json_type( query_array->type )
3695 } if( query_array->size < 2 ) {
3697 osrfAppSessionStatus(
3699 OSRF_STATUS_INTERNALSERVERERROR,
3700 "osrfMethodException",
3702 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3706 "%s:%srequires multiple queries as operands",
3711 } else if( excepting && query_array->size > 2 ) {
3713 osrfAppSessionStatus(
3715 OSRF_STATUS_INTERNALSERVERERROR,
3716 "osrfMethodException",
3718 "EXCEPT operator has too many queries as operands"
3722 "%s:EXCEPT operator has too many queries as operands",
3726 } else if( order_obj && ! alias ) {
3728 osrfAppSessionStatus(
3730 OSRF_STATUS_INTERNALSERVERERROR,
3731 "osrfMethodException",
3733 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3737 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3743 // So far so good. Now build the SQL.
3744 growing_buffer* sql = buffer_init( 256 );
3746 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3747 // Add a layer of parentheses
3748 if( flags & SUBCOMBO )
3749 OSRF_BUFFER_ADD( sql, "( " );
3751 // Traverse the query array. Each entry should be a hash.
3752 int first = 1; // boolean
3754 jsonObject* query = NULL;
3755 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3756 if( query->type != JSON_HASH ) {
3758 osrfAppSessionStatus(
3760 OSRF_STATUS_INTERNALSERVERERROR,
3761 "osrfMethodException",
3763 "Malformed query under UNION, INTERSECT or EXCEPT"
3767 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3770 json_type( query->type )
3779 OSRF_BUFFER_ADD( sql, op );
3781 OSRF_BUFFER_ADD( sql, "ALL " );
3784 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3788 "%s: Error building query under%s",
3796 OSRF_BUFFER_ADD( sql, query_str );
3799 if( flags & SUBCOMBO )
3800 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3802 if( !(flags & SUBSELECT) )
3803 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3805 return buffer_release( sql );
3808 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3809 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3810 // or "except" to indicate the type of query.
3811 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3815 osrfAppSessionStatus(
3817 OSRF_STATUS_INTERNALSERVERERROR,
3818 "osrfMethodException",
3820 "Malformed query; no query object"
3822 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3824 } else if( query->type != JSON_HASH ) {
3826 osrfAppSessionStatus(
3828 OSRF_STATUS_INTERNALSERVERERROR,
3829 "osrfMethodException",
3831 "Malformed query object"
3835 "%s: Query object is %s instead of JSON_HASH",
3837 json_type( query->type )
3842 // Determine what kind of query it purports to be, and dispatch accordingly.
3843 if( jsonObjectGetKeyConst( query, "union" ) ||
3844 jsonObjectGetKeyConst( query, "intersect" ) ||
3845 jsonObjectGetKeyConst( query, "except" )) {
3846 return doCombo( ctx, query, flags );
3848 // It is presumably a SELECT query
3850 // Push a node onto the stack for the current query. Every level of
3851 // subquery gets its own QueryFrame on the Stack.
3854 // Build an SQL SELECT statement
3857 jsonObjectGetKey( query, "select" ),
3858 jsonObjectGetKeyConst( query, "from" ),
3859 jsonObjectGetKeyConst( query, "where" ),
3860 jsonObjectGetKeyConst( query, "having" ),
3861 jsonObjectGetKeyConst( query, "order_by" ),
3862 jsonObjectGetKeyConst( query, "limit" ),
3863 jsonObjectGetKeyConst( query, "offset" ),
3872 /* method context */ osrfMethodContext* ctx,
3874 /* SELECT */ jsonObject* selhash,
3875 /* FROM */ const jsonObject* join_hash,
3876 /* WHERE */ const jsonObject* search_hash,
3877 /* HAVING */ const jsonObject* having_hash,
3878 /* ORDER BY */ const jsonObject* order_hash,
3879 /* LIMIT */ const jsonObject* limit,
3880 /* OFFSET */ const jsonObject* offset,
3881 /* flags */ int flags
3883 const char* locale = osrf_message_get_last_locale();
3885 // general tmp objects
3886 const jsonObject* tmp_const;
3887 jsonObject* selclass = NULL;
3888 jsonObject* snode = NULL;
3889 jsonObject* onode = NULL;
3891 char* string = NULL;
3892 int from_function = 0;
3897 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3899 // punt if there's no FROM clause
3900 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3903 "%s: FROM clause is missing or empty",
3907 osrfAppSessionStatus(
3909 OSRF_STATUS_INTERNALSERVERERROR,
3910 "osrfMethodException",
3912 "FROM clause is missing or empty in JSON query"
3917 // the core search class
3918 const char* core_class = NULL;
3920 // get the core class -- the only key of the top level FROM clause, or a string
3921 if( join_hash->type == JSON_HASH ) {
3922 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3923 snode = jsonIteratorNext( tmp_itr );
3925 // Populate the current QueryFrame with information
3926 // about the core class
3927 if( add_query_core( NULL, tmp_itr->key ) ) {
3929 osrfAppSessionStatus(
3931 OSRF_STATUS_INTERNALSERVERERROR,
3932 "osrfMethodException",
3934 "Unable to look up core class"
3938 core_class = curr_query->core.class_name;
3941 jsonObject* extra = jsonIteratorNext( tmp_itr );
3943 jsonIteratorFree( tmp_itr );
3946 // There shouldn't be more than one entry in join_hash
3950 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3954 osrfAppSessionStatus(
3956 OSRF_STATUS_INTERNALSERVERERROR,
3957 "osrfMethodException",
3959 "Malformed FROM clause in JSON query"
3961 return NULL; // Malformed join_hash; extra entry
3963 } else if( join_hash->type == JSON_ARRAY ) {
3964 // We're selecting from a function, not from a table
3966 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3969 } else if( join_hash->type == JSON_STRING ) {
3970 // Populate the current QueryFrame with information
3971 // about the core class
3972 core_class = jsonObjectGetString( join_hash );
3974 if( add_query_core( NULL, core_class ) ) {
3976 osrfAppSessionStatus(
3978 OSRF_STATUS_INTERNALSERVERERROR,
3979 "osrfMethodException",
3981 "Unable to look up core class"
3989 "%s: FROM clause is unexpected JSON type: %s",
3991 json_type( join_hash->type )
3994 osrfAppSessionStatus(
3996 OSRF_STATUS_INTERNALSERVERERROR,
3997 "osrfMethodException",
3999 "Ill-formed FROM clause in JSON query"
4004 // Build the join clause, if any, while filling out the list
4005 // of joined classes in the current QueryFrame.
4006 char* join_clause = NULL;
4007 if( join_hash && ! from_function ) {
4009 join_clause = searchJOIN( join_hash, &curr_query->core );
4010 if( ! join_clause ) {
4012 osrfAppSessionStatus(
4014 OSRF_STATUS_INTERNALSERVERERROR,
4015 "osrfMethodException",
4017 "Unable to construct JOIN clause(s)"
4023 // For in case we don't get a select list
4024 jsonObject* defaultselhash = NULL;
4026 // if there is no select list, build a default select list ...
4027 if( !selhash && !from_function ) {
4028 jsonObject* default_list = defaultSelectList( core_class );
4029 if( ! default_list ) {
4031 osrfAppSessionStatus(
4033 OSRF_STATUS_INTERNALSERVERERROR,
4034 "osrfMethodException",
4036 "Unable to build default SELECT clause in JSON query"
4038 free( join_clause );
4043 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4044 jsonObjectSetKey( selhash, core_class, default_list );
4047 // The SELECT clause can be encoded only by a hash
4048 if( !from_function && selhash->type != JSON_HASH ) {
4051 "%s: Expected JSON_HASH for SELECT clause; found %s",
4053 json_type( selhash->type )
4057 osrfAppSessionStatus(
4059 OSRF_STATUS_INTERNALSERVERERROR,
4060 "osrfMethodException",
4062 "Malformed SELECT clause in JSON query"
4064 free( join_clause );
4068 // If you see a null or wild card specifier for the core class, or an
4069 // empty array, replace it with a default SELECT list
4070 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4072 int default_needed = 0; // boolean
4073 if( JSON_STRING == tmp_const->type
4074 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4076 else if( JSON_NULL == tmp_const->type )
4079 if( default_needed ) {
4080 // Build a default SELECT list
4081 jsonObject* default_list = defaultSelectList( core_class );
4082 if( ! default_list ) {
4084 osrfAppSessionStatus(
4086 OSRF_STATUS_INTERNALSERVERERROR,
4087 "osrfMethodException",
4089 "Can't build default SELECT clause in JSON query"
4091 free( join_clause );
4096 jsonObjectSetKey( selhash, core_class, default_list );
4100 // temp buffers for the SELECT list and GROUP BY clause
4101 growing_buffer* select_buf = buffer_init( 128 );
4102 growing_buffer* group_buf = buffer_init( 128 );
4104 int aggregate_found = 0; // boolean
4106 // Build a select list
4107 if( from_function ) // From a function we select everything
4108 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4111 // Build the SELECT list as SQL
4115 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4116 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4118 const char* cname = selclass_itr->key;
4120 // Make sure the target relation is in the FROM clause.
4122 // At this point join_hash is a step down from the join_hash we
4123 // received as a parameter. If the original was a JSON_STRING,
4124 // then json_hash is now NULL. If the original was a JSON_HASH,
4125 // then json_hash is now the first (and only) entry in it,
4126 // denoting the core class. We've already excluded the
4127 // possibility that the original was a JSON_ARRAY, because in
4128 // that case from_function would be non-NULL, and we wouldn't
4131 // If the current table alias isn't in scope, bail out
4132 ClassInfo* class_info = search_alias( cname );
4133 if( ! class_info ) {
4136 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4141 osrfAppSessionStatus(
4143 OSRF_STATUS_INTERNALSERVERERROR,
4144 "osrfMethodException",
4146 "Selected class not in FROM clause in JSON query"
4148 jsonIteratorFree( selclass_itr );
4149 buffer_free( select_buf );
4150 buffer_free( group_buf );
4151 if( defaultselhash )
4152 jsonObjectFree( defaultselhash );
4153 free( join_clause );
4157 if( selclass->type != JSON_ARRAY ) {
4160 "%s: Malformed SELECT list for class \"%s\"; not an array",
4165 osrfAppSessionStatus(
4167 OSRF_STATUS_INTERNALSERVERERROR,
4168 "osrfMethodException",
4170 "Selected class not in FROM clause in JSON query"
4173 jsonIteratorFree( selclass_itr );
4174 buffer_free( select_buf );
4175 buffer_free( group_buf );
4176 if( defaultselhash )
4177 jsonObjectFree( defaultselhash );
4178 free( join_clause );
4182 // Look up some attributes of the current class
4183 osrfHash* idlClass = class_info->class_def;
4184 osrfHash* class_field_set = class_info->fields;
4185 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4186 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4188 if( 0 == selclass->size ) {
4191 "%s: No columns selected from \"%s\"",
4197 // stitch together the column list for the current table alias...
4198 unsigned long field_idx = 0;
4199 jsonObject* selfield = NULL;
4200 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4202 // If we need a separator comma, add one
4206 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4209 // if the field specification is a string, add it to the list
4210 if( selfield->type == JSON_STRING ) {
4212 // Look up the field in the IDL
4213 const char* col_name = jsonObjectGetString( selfield );
4214 osrfHash* field_def;
4216 if (!osrfStringArrayContains(
4218 osrfHashGet( class_field_set, col_name ),
4219 "suppress_controller"),
4222 field_def = osrfHashGet( class_field_set, col_name );
4225 // No such field in current class
4228 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4234 osrfAppSessionStatus(
4236 OSRF_STATUS_INTERNALSERVERERROR,
4237 "osrfMethodException",
4239 "Selected column not defined in JSON query"
4241 jsonIteratorFree( selclass_itr );
4242 buffer_free( select_buf );
4243 buffer_free( group_buf );
4244 if( defaultselhash )
4245 jsonObjectFree( defaultselhash );
4246 free( join_clause );
4248 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4249 // Virtual field not allowed
4252 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4258 osrfAppSessionStatus(
4260 OSRF_STATUS_INTERNALSERVERERROR,
4261 "osrfMethodException",
4263 "Selected column may not be virtual in JSON query"
4265 jsonIteratorFree( selclass_itr );
4266 buffer_free( select_buf );
4267 buffer_free( group_buf );
4268 if( defaultselhash )
4269 jsonObjectFree( defaultselhash );
4270 free( join_clause );
4276 if( flags & DISABLE_I18N )
4279 i18n = osrfHashGet( field_def, "i18n" );
4281 if( str_is_true( i18n ) ) {
4282 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4283 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4284 class_tname, cname, col_name, class_pkey,
4285 cname, class_pkey, locale, col_name );
4287 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4288 cname, col_name, col_name );
4291 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4292 cname, col_name, col_name );
4295 // ... but it could be an object, in which case we check for a Field Transform
4296 } else if( selfield->type == JSON_HASH ) {
4298 const char* col_name = jsonObjectGetString(
4299 jsonObjectGetKeyConst( selfield, "column" ) );
4301 // Get the field definition from the IDL
4302 osrfHash* field_def;
4303 if (!osrfStringArrayContains(
4305 osrfHashGet( class_field_set, col_name ),
4306 "suppress_controller"),
4309 field_def = osrfHashGet( class_field_set, col_name );
4313 // No such field in current class
4316 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4322 osrfAppSessionStatus(
4324 OSRF_STATUS_INTERNALSERVERERROR,
4325 "osrfMethodException",
4327 "Selected column is not defined in JSON query"
4329 jsonIteratorFree( selclass_itr );
4330 buffer_free( select_buf );
4331 buffer_free( group_buf );
4332 if( defaultselhash )
4333 jsonObjectFree( defaultselhash );
4334 free( join_clause );
4336 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4337 // No such field in current class
4340 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4346 osrfAppSessionStatus(
4348 OSRF_STATUS_INTERNALSERVERERROR,
4349 "osrfMethodException",
4351 "Selected column is virtual in JSON query"
4353 jsonIteratorFree( selclass_itr );
4354 buffer_free( select_buf );
4355 buffer_free( group_buf );
4356 if( defaultselhash )
4357 jsonObjectFree( defaultselhash );
4358 free( join_clause );
4362 // Decide what to use as a column alias
4364 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4365 _alias = jsonObjectGetString( tmp_const );
4366 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4367 _alias = jsonObjectGetString( tmp_const );
4368 } else { // Use field name as the alias
4372 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4373 char* transform_str = searchFieldTransform(
4374 class_info->alias, field_def, selfield );
4375 if( transform_str ) {
4376 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4377 free( transform_str );
4380 osrfAppSessionStatus(
4382 OSRF_STATUS_INTERNALSERVERERROR,
4383 "osrfMethodException",
4385 "Unable to generate transform function in JSON query"
4387 jsonIteratorFree( selclass_itr );
4388 buffer_free( select_buf );
4389 buffer_free( group_buf );
4390 if( defaultselhash )
4391 jsonObjectFree( defaultselhash );
4392 free( join_clause );
4399 if( flags & DISABLE_I18N )
4402 i18n = osrfHashGet( field_def, "i18n" );
4404 if( str_is_true( i18n ) ) {
4405 buffer_fadd( select_buf,
4406 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4407 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4408 class_tname, cname, col_name, class_pkey, cname,
4409 class_pkey, locale, _alias );
4411 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4412 cname, col_name, _alias );
4415 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4416 cname, col_name, _alias );
4423 "%s: Selected item is unexpected JSON type: %s",
4425 json_type( selfield->type )
4428 osrfAppSessionStatus(
4430 OSRF_STATUS_INTERNALSERVERERROR,
4431 "osrfMethodException",
4433 "Ill-formed SELECT item in JSON query"
4435 jsonIteratorFree( selclass_itr );
4436 buffer_free( select_buf );
4437 buffer_free( group_buf );
4438 if( defaultselhash )
4439 jsonObjectFree( defaultselhash );
4440 free( join_clause );
4444 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4445 if( obj_is_true( agg_obj ) )
4446 aggregate_found = 1;
4448 // Append a comma (except for the first one)
4449 // and add the column to a GROUP BY clause
4453 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4455 buffer_fadd( group_buf, " %d", sel_pos );
4459 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4461 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( elfield, "aggregate");
4462 if ( ! obj_is_true( aggregate_obj ) ) {
4466 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4469 buffer_fadd(group_buf, " %d", sel_pos);
4472 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4476 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4479 _column = searchFieldTransform(class_info->alias, field, selfield);
4480 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4481 OSRF_BUFFER_ADD(group_buf, _column);
4482 _column = searchFieldTransform(class_info->alias, field, selfield);
4489 } // end while -- iterating across SELECT columns
4491 } // end while -- iterating across classes
4493 jsonIteratorFree( selclass_itr );
4496 char* col_list = buffer_release( select_buf );
4498 // Make sure the SELECT list isn't empty. This can happen, for example,
4499 // if we try to build a default SELECT clause from a non-core table.
4502 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4504 osrfAppSessionStatus(
4506 OSRF_STATUS_INTERNALSERVERERROR,
4507 "osrfMethodException",
4509 "SELECT list is empty"
4512 buffer_free( group_buf );
4513 if( defaultselhash )
4514 jsonObjectFree( defaultselhash );
4515 free( join_clause );
4521 table = searchValueTransform( join_hash );
4523 table = strdup( curr_query->core.source_def );
4527 osrfAppSessionStatus(
4529 OSRF_STATUS_INTERNALSERVERERROR,
4530 "osrfMethodException",
4532 "Unable to identify table for core class"
4535 buffer_free( group_buf );
4536 if( defaultselhash )
4537 jsonObjectFree( defaultselhash );
4538 free( join_clause );
4542 // Put it all together
4543 growing_buffer* sql_buf = buffer_init( 128 );
4544 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4548 // Append the join clause, if any
4550 buffer_add(sql_buf, join_clause );
4551 free( join_clause );
4554 char* order_by_list = NULL;
4555 char* having_buf = NULL;
4557 if( !from_function ) {
4559 // Build a WHERE clause, if there is one
4561 buffer_add( sql_buf, " WHERE " );
4563 // and it's on the WHERE clause
4564 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4567 osrfAppSessionStatus(
4569 OSRF_STATUS_INTERNALSERVERERROR,
4570 "osrfMethodException",
4572 "Severe query error in WHERE predicate -- see error log for more details"
4575 buffer_free( group_buf );
4576 buffer_free( sql_buf );
4577 if( defaultselhash )
4578 jsonObjectFree( defaultselhash );
4582 buffer_add( sql_buf, pred );
4586 // Build a HAVING clause, if there is one
4589 // and it's on the the WHERE clause
4590 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4592 if( ! having_buf ) {
4594 osrfAppSessionStatus(
4596 OSRF_STATUS_INTERNALSERVERERROR,
4597 "osrfMethodException",
4599 "Severe query error in HAVING predicate -- see error log for more details"
4602 buffer_free( group_buf );
4603 buffer_free( sql_buf );
4604 if( defaultselhash )
4605 jsonObjectFree( defaultselhash );
4610 // Build an ORDER BY clause, if there is one
4611 if( NULL == order_hash )
4612 ; // No ORDER BY? do nothing
4613 else if( JSON_ARRAY == order_hash->type ) {
4614 order_by_list = buildOrderByFromArray( ctx, order_hash );
4615 if( !order_by_list ) {
4617 buffer_free( group_buf );
4618 buffer_free( sql_buf );
4619 if( defaultselhash )
4620 jsonObjectFree( defaultselhash );
4623 } else if( JSON_HASH == order_hash->type ) {
4624 // This hash is keyed on class alias. Each class has either
4625 // an array of field names or a hash keyed on field name.
4626 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4627 jsonIterator* class_itr = jsonNewIterator( order_hash );
4628 while( (snode = jsonIteratorNext( class_itr )) ) {
4630 ClassInfo* order_class_info = search_alias( class_itr->key );
4631 if( ! order_class_info ) {
4632 osrfLogError( OSRF_LOG_MARK,
4633 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4634 modulename, class_itr->key );
4636 osrfAppSessionStatus(
4638 OSRF_STATUS_INTERNALSERVERERROR,
4639 "osrfMethodException",
4641 "Invalid class referenced in ORDER BY clause -- "
4642 "see error log for more details"
4644 jsonIteratorFree( class_itr );
4645 buffer_free( order_buf );
4647 buffer_free( group_buf );
4648 buffer_free( sql_buf );
4649 if( defaultselhash )
4650 jsonObjectFree( defaultselhash );
4654 osrfHash* field_list_def = order_class_info->fields;
4656 if( snode->type == JSON_HASH ) {
4658 // Hash is keyed on field names from the current class. For each field
4659 // there is another layer of hash to define the sorting details, if any,
4660 // or a string to indicate direction of sorting.
4661 jsonIterator* order_itr = jsonNewIterator( snode );
4662 while( (onode = jsonIteratorNext( order_itr )) ) {
4664 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4666 osrfLogError( OSRF_LOG_MARK,
4667 "%s: Invalid field \"%s\" in ORDER BY clause",
4668 modulename, order_itr->key );
4670 osrfAppSessionStatus(
4672 OSRF_STATUS_INTERNALSERVERERROR,
4673 "osrfMethodException",
4675 "Invalid field in ORDER BY clause -- "
4676 "see error log for more details"
4678 jsonIteratorFree( order_itr );
4679 jsonIteratorFree( class_itr );
4680 buffer_free( order_buf );
4682 buffer_free( group_buf );
4683 buffer_free( sql_buf );
4684 if( defaultselhash )
4685 jsonObjectFree( defaultselhash );
4687 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4688 osrfLogError( OSRF_LOG_MARK,
4689 "%s: Virtual field \"%s\" in ORDER BY clause",
4690 modulename, order_itr->key );
4692 osrfAppSessionStatus(
4694 OSRF_STATUS_INTERNALSERVERERROR,
4695 "osrfMethodException",
4697 "Virtual field in ORDER BY clause -- "
4698 "see error log for more details"
4700 jsonIteratorFree( order_itr );
4701 jsonIteratorFree( class_itr );
4702 buffer_free( order_buf );
4704 buffer_free( group_buf );
4705 buffer_free( sql_buf );
4706 if( defaultselhash )
4707 jsonObjectFree( defaultselhash );
4711 const char* direction = NULL;
4712 if( onode->type == JSON_HASH ) {
4713 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4714 string = searchFieldTransform(
4716 osrfHashGet( field_list_def, order_itr->key ),
4720 if( ctx ) osrfAppSessionStatus(
4722 OSRF_STATUS_INTERNALSERVERERROR,
4723 "osrfMethodException",
4725 "Severe query error in ORDER BY clause -- "
4726 "see error log for more details"
4728 jsonIteratorFree( order_itr );
4729 jsonIteratorFree( class_itr );
4731 buffer_free( group_buf );
4732 buffer_free( order_buf);
4733 buffer_free( sql_buf );
4734 if( defaultselhash )
4735 jsonObjectFree( defaultselhash );
4739 growing_buffer* field_buf = buffer_init( 16 );
4740 buffer_fadd( field_buf, "\"%s\".%s",
4741 class_itr->key, order_itr->key );
4742 string = buffer_release( field_buf );
4745 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4746 const char* dir = jsonObjectGetString( tmp_const );
4747 if(!strncasecmp( dir, "d", 1 )) {
4748 direction = " DESC";
4754 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4755 osrfLogError( OSRF_LOG_MARK,
4756 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4757 modulename, json_type( onode->type ) );
4759 osrfAppSessionStatus(
4761 OSRF_STATUS_INTERNALSERVERERROR,
4762 "osrfMethodException",
4764 "Malformed ORDER BY clause -- see error log for more details"
4766 jsonIteratorFree( order_itr );
4767 jsonIteratorFree( class_itr );
4769 buffer_free( group_buf );
4770 buffer_free( order_buf );
4771 buffer_free( sql_buf );
4772 if( defaultselhash )
4773 jsonObjectFree( defaultselhash );
4777 string = strdup( order_itr->key );
4778 const char* dir = jsonObjectGetString( onode );
4779 if( !strncasecmp( dir, "d", 1 )) {
4780 direction = " DESC";
4787 OSRF_BUFFER_ADD( order_buf, ", " );
4789 order_buf = buffer_init( 128 );
4791 OSRF_BUFFER_ADD( order_buf, string );
4795 OSRF_BUFFER_ADD( order_buf, direction );
4799 jsonIteratorFree( order_itr );
4801 } else if( snode->type == JSON_ARRAY ) {
4803 // Array is a list of fields from the current class
4804 unsigned long order_idx = 0;
4805 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4807 const char* _f = jsonObjectGetString( onode );
4809 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4811 osrfLogError( OSRF_LOG_MARK,
4812 "%s: Invalid field \"%s\" in ORDER BY clause",
4815 osrfAppSessionStatus(
4817 OSRF_STATUS_INTERNALSERVERERROR,
4818 "osrfMethodException",
4820 "Invalid field in ORDER BY clause -- "
4821 "see error log for more details"
4823 jsonIteratorFree( class_itr );
4824 buffer_free( order_buf );
4826 buffer_free( group_buf );
4827 buffer_free( sql_buf );
4828 if( defaultselhash )
4829 jsonObjectFree( defaultselhash );
4831 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4832 osrfLogError( OSRF_LOG_MARK,
4833 "%s: Virtual field \"%s\" in ORDER BY clause",
4836 osrfAppSessionStatus(
4838 OSRF_STATUS_INTERNALSERVERERROR,
4839 "osrfMethodException",
4841 "Virtual field in ORDER BY clause -- "
4842 "see error log for more details"
4844 jsonIteratorFree( class_itr );
4845 buffer_free( order_buf );
4847 buffer_free( group_buf );
4848 buffer_free( sql_buf );
4849 if( defaultselhash )
4850 jsonObjectFree( defaultselhash );
4855 OSRF_BUFFER_ADD( order_buf, ", " );
4857 order_buf = buffer_init( 128 );
4859 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4863 // IT'S THE OOOOOOOOOOOLD STYLE!
4865 osrfLogError( OSRF_LOG_MARK,
4866 "%s: Possible SQL injection attempt; direct order by is not allowed",
4869 osrfAppSessionStatus(
4871 OSRF_STATUS_INTERNALSERVERERROR,
4872 "osrfMethodException",
4874 "Severe query error -- see error log for more details"
4879 buffer_free( group_buf );
4880 buffer_free( order_buf );
4881 buffer_free( sql_buf );
4882 if( defaultselhash )
4883 jsonObjectFree( defaultselhash );
4884 jsonIteratorFree( class_itr );
4888 jsonIteratorFree( class_itr );
4890 order_by_list = buffer_release( order_buf );
4892 osrfLogError( OSRF_LOG_MARK,
4893 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4894 modulename, json_type( order_hash->type ) );
4896 osrfAppSessionStatus(
4898 OSRF_STATUS_INTERNALSERVERERROR,
4899 "osrfMethodException",
4901 "Malformed ORDER BY clause -- see error log for more details"
4904 buffer_free( group_buf );
4905 buffer_free( sql_buf );
4906 if( defaultselhash )
4907 jsonObjectFree( defaultselhash );
4912 string = buffer_release( group_buf );
4914 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4915 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4916 OSRF_BUFFER_ADD( sql_buf, string );
4921 if( having_buf && *having_buf ) {
4922 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4923 OSRF_BUFFER_ADD( sql_buf, having_buf );
4927 if( order_by_list ) {
4929 if( *order_by_list ) {
4930 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4931 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4934 free( order_by_list );
4938 const char* str = jsonObjectGetString( limit );
4939 if (str) { // limit could be JSON_NULL, etc.
4940 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4945 const char* str = jsonObjectGetString( offset );
4947 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4951 if( !(flags & SUBSELECT) )
4952 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4954 if( defaultselhash )
4955 jsonObjectFree( defaultselhash );
4957 return buffer_release( sql_buf );
4959 } // end of SELECT()
4962 @brief Build a list of ORDER BY expressions.
4963 @param ctx Pointer to the method context.
4964 @param order_array Pointer to a JSON_ARRAY of field specifications.
4965 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
4966 Each expression may be either a column reference or a function call whose first parameter
4967 is a column reference.
4969 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
4970 It may optionally include entries for "direction" and/or "transform".
4972 The calling code is responsible for freeing the returned string.
4974 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
4975 if( ! order_array ) {
4976 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
4979 osrfAppSessionStatus(
4981 OSRF_STATUS_INTERNALSERVERERROR,
4982 "osrfMethodException",
4984 "Logic error: ORDER BY clause expected, not found; "
4985 "see error log for more details"
4988 } else if( order_array->type != JSON_ARRAY ) {
4989 osrfLogError( OSRF_LOG_MARK,
4990 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
4992 osrfAppSessionStatus(
4994 OSRF_STATUS_INTERNALSERVERERROR,
4995 "osrfMethodException",
4997 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
5001 growing_buffer* order_buf = buffer_init( 128 );
5002 int first = 1; // boolean
5004 jsonObject* order_spec;
5005 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
5007 if( JSON_HASH != order_spec->type ) {
5008 osrfLogError( OSRF_LOG_MARK,
5009 "%s: Malformed field specification in ORDER BY clause; "
5010 "expected JSON_HASH, found %s",
5011 modulename, json_type( order_spec->type ) );
5013 osrfAppSessionStatus(
5015 OSRF_STATUS_INTERNALSERVERERROR,
5016 "osrfMethodException",
5018 "Malformed ORDER BY clause -- see error log for more details"
5020 buffer_free( order_buf );
5024 const char* class_alias =
5025 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5027 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5029 jsonObject* compare_to = jsonObjectGetKeyConst( order_spec, "compare" );
5031 if( !field || !class_alias ) {
5032 osrfLogError( OSRF_LOG_MARK,
5033 "%s: Missing class or field name in field specification of ORDER BY clause",
5036 osrfAppSessionStatus(
5038 OSRF_STATUS_INTERNALSERVERERROR,
5039 "osrfMethodException",
5041 "Malformed ORDER BY clause -- see error log for more details"
5043 buffer_free( order_buf );
5047 const ClassInfo* order_class_info = search_alias( class_alias );
5048 if( ! order_class_info ) {
5049 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5050 "not in FROM clause, skipping it", modulename, class_alias );
5054 // Add a separating comma, except at the beginning
5058 OSRF_BUFFER_ADD( order_buf, ", " );
5060 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5062 osrfLogError( OSRF_LOG_MARK,
5063 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5064 modulename, class_alias, field );
5066 osrfAppSessionStatus(
5068 OSRF_STATUS_INTERNALSERVERERROR,
5069 "osrfMethodException",
5071 "Invalid field referenced in ORDER BY clause -- "
5072 "see error log for more details"
5076 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5077 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5078 modulename, field );
5080 osrfAppSessionStatus(
5082 OSRF_STATUS_INTERNALSERVERERROR,
5083 "osrfMethodException",
5085 "Virtual field in ORDER BY clause -- see error log for more details"
5087 buffer_free( order_buf );
5091 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5092 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5093 if( ! transform_str ) {
5095 osrfAppSessionStatus(
5097 OSRF_STATUS_INTERNALSERVERERROR,
5098 "osrfMethodException",
5100 "Severe query error in ORDER BY clause -- "
5101 "see error log for more details"
5103 buffer_free( order_buf );
5107 OSRF_BUFFER_ADD( order_buf, transform_str );
5108 free( transform_str );
5109 } else if( compare_to ) {
5110 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5111 if( ! compare_str ) {
5113 osrfAppSessionStatus(
5115 OSRF_STATUS_INTERNALSERVERERROR,
5116 "osrfMethodException",
5118 "Severe query error in ORDER BY clause -- "
5119 "see error log for more details"
5121 buffer_free( order_buf );
5125 buffer_fadd( order_buf, "(%s)", compare_str );
5126 free( compare_str );
5129 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5131 const char* direction =
5132 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5134 if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5135 OSRF_BUFFER_ADD( order_buf, " DESC" );
5137 OSRF_BUFFER_ADD( order_buf, " ASC" );
5141 return buffer_release( order_buf );
5145 @brief Build a SELECT statement.
5146 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5147 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5148 @param meta Pointer to the class metadata for the core class.
5149 @param ctx Pointer to the method context.
5150 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5152 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5153 "order_by", "limit", and "offset".
5155 The SELECT statements built here are distinct from those built for the json_query method.
5157 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5158 osrfHash* meta, osrfMethodContext* ctx ) {
5160 const char* locale = osrf_message_get_last_locale();
5162 osrfHash* fields = osrfHashGet( meta, "fields" );
5163 const char* core_class = osrfHashGet( meta, "classname" );
5165 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5167 jsonObject* selhash = NULL;
5168 jsonObject* defaultselhash = NULL;
5170 growing_buffer* sql_buf = buffer_init( 128 );
5171 growing_buffer* select_buf = buffer_init( 128 );
5173 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5174 defaultselhash = jsonNewObjectType( JSON_HASH );
5175 selhash = defaultselhash;
5178 // If there's no SELECT list for the core class, build one
5179 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5180 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5182 // Add every non-virtual field to the field list
5183 osrfHash* field_def = NULL;
5184 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5185 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5186 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5187 const char* field = osrfHashIteratorKey( field_itr );
5188 jsonObjectPush( field_list, jsonNewObject( field ) );
5191 osrfHashIteratorFree( field_itr );
5192 jsonObjectSetKey( selhash, core_class, field_list );
5195 // Build a list of columns for the SELECT clause
5197 const jsonObject* snode = NULL;
5198 jsonIterator* class_itr = jsonNewIterator( selhash );
5199 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5201 // If the class isn't in the IDL, ignore it
5202 const char* cname = class_itr->key;
5203 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5207 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5208 if( strcmp( core_class, class_itr->key )) {
5212 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5213 if( !found->size ) {
5214 jsonObjectFree( found );
5218 jsonObjectFree( found );
5221 const jsonObject* node = NULL;
5222 jsonIterator* select_itr = jsonNewIterator( snode );
5223 while( (node = jsonIteratorNext( select_itr )) ) {
5224 const char* item_str = jsonObjectGetString( node );
5225 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5226 char* fname = osrfHashGet( field, "name" );
5231 if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5237 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5242 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5243 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5246 i18n = osrfHashGet( field, "i18n" );
5248 if( str_is_true( i18n ) ) {
5249 char* pkey = osrfHashGet( idlClass, "primarykey" );
5250 char* tname = osrfHashGet( idlClass, "tablename" );
5252 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5253 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5254 tname, cname, fname, pkey, cname, pkey, locale, fname );
5256 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5259 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5263 jsonIteratorFree( select_itr );
5266 jsonIteratorFree( class_itr );
5268 char* col_list = buffer_release( select_buf );
5269 char* table = oilsGetRelation( meta );
5271 table = strdup( "(null)" );
5273 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5277 // Clear the query stack (as a fail-safe precaution against possible
5278 // leftover garbage); then push the first query frame onto the stack.
5279 clear_query_stack();
5281 if( add_query_core( NULL, core_class ) ) {
5283 osrfAppSessionStatus(
5285 OSRF_STATUS_INTERNALSERVERERROR,
5286 "osrfMethodException",
5288 "Unable to build query frame for core class"
5290 buffer_free( sql_buf );
5291 if( defaultselhash )
5292 jsonObjectFree( defaultselhash );
5296 // Add the JOIN clauses, if any
5298 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5299 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5300 OSRF_BUFFER_ADD( sql_buf, join_clause );
5301 free( join_clause );
5304 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5305 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5307 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5309 // Add the conditions in the WHERE clause
5310 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5312 osrfAppSessionStatus(
5314 OSRF_STATUS_INTERNALSERVERERROR,
5315 "osrfMethodException",
5317 "Severe query error -- see error log for more details"
5319 buffer_free( sql_buf );
5320 if( defaultselhash )
5321 jsonObjectFree( defaultselhash );
5322 clear_query_stack();
5325 buffer_add( sql_buf, pred );
5329 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5330 if( rest_of_query ) {
5331 const jsonObject* order_by = NULL;
5332 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5334 char* order_by_list = NULL;
5336 if( JSON_ARRAY == order_by->type ) {
5337 order_by_list = buildOrderByFromArray( ctx, order_by );
5338 if( !order_by_list ) {
5339 buffer_free( sql_buf );
5340 if( defaultselhash )
5341 jsonObjectFree( defaultselhash );
5342 clear_query_stack();
5345 } else if( JSON_HASH == order_by->type ) {
5346 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5347 // and build a list of ORDER BY expressions.
5348 growing_buffer* order_buf = buffer_init( 128 );
5350 jsonIterator* class_itr = jsonNewIterator( order_by );
5351 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5353 ClassInfo* order_class_info = search_alias( class_itr->key );
5354 if( ! order_class_info )
5355 continue; // class not referenced by FROM clause? Ignore it.
5357 if( JSON_HASH == snode->type ) {
5359 // If the data for the current class is a JSON_HASH, then it is
5360 // keyed on field name.
5362 const jsonObject* onode = NULL;
5363 jsonIterator* order_itr = jsonNewIterator( snode );
5364 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5366 osrfHash* field_def = osrfHashGet(
5367 order_class_info->fields, order_itr->key );
5369 continue; // Field not defined in IDL? Ignore it.
5370 if( str_is_true( osrfHashGet( field_def, "virtual")))
5371 continue; // Field is virtual? Ignore it.
5373 char* field_str = NULL;
5374 char* direction = NULL;
5375 if( onode->type == JSON_HASH ) {
5376 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5377 field_str = searchFieldTransform(
5378 class_itr->key, field_def, onode );
5380 osrfAppSessionStatus(
5382 OSRF_STATUS_INTERNALSERVERERROR,
5383 "osrfMethodException",
5385 "Severe query error in ORDER BY clause -- "
5386 "see error log for more details"
5388 jsonIteratorFree( order_itr );
5389 jsonIteratorFree( class_itr );
5390 buffer_free( order_buf );
5391 buffer_free( sql_buf );
5392 if( defaultselhash )
5393 jsonObjectFree( defaultselhash );
5394 clear_query_stack();
5398 growing_buffer* field_buf = buffer_init( 16 );
5399 buffer_fadd( field_buf, "\"%s\".%s",
5400 class_itr->key, order_itr->key );
5401 field_str = buffer_release( field_buf );
5404 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5405 const char* dir = jsonObjectGetString( order_by );
5406 if(!strncasecmp( dir, "d", 1 )) {
5407 direction = " DESC";
5411 field_str = strdup( order_itr->key );
5412 const char* dir = jsonObjectGetString( onode );
5413 if( !strncasecmp( dir, "d", 1 )) {
5414 direction = " DESC";
5423 buffer_add( order_buf, ", " );
5426 buffer_add( order_buf, field_str );
5430 buffer_add( order_buf, direction );
5432 } // end while; looping over ORDER BY expressions
5434 jsonIteratorFree( order_itr );
5436 } else if( JSON_STRING == snode->type ) {
5437 // We expect a comma-separated list of sort fields.
5438 const char* str = jsonObjectGetString( snode );
5439 if( strchr( str, ';' )) {
5440 // No semicolons allowed. It is theoretically possible for a
5441 // legitimate semicolon to occur within quotes, but it's not likely
5442 // to occur in practice in the context of an ORDER BY list.
5443 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5444 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5446 osrfAppSessionStatus(
5448 OSRF_STATUS_INTERNALSERVERERROR,
5449 "osrfMethodException",
5451 "Possible attempt at SOL injection -- "
5452 "semicolon found in ORDER BY list"
5455 jsonIteratorFree( class_itr );
5456 buffer_free( order_buf );
5457 buffer_free( sql_buf );
5458 if( defaultselhash )
5459 jsonObjectFree( defaultselhash );
5460 clear_query_stack();
5463 buffer_add( order_buf, str );
5467 } // end while; looping over order_by classes
5469 jsonIteratorFree( class_itr );
5470 order_by_list = buffer_release( order_buf );
5473 osrfLogWarning( OSRF_LOG_MARK,
5474 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5475 "no ORDER BY generated" );
5478 if( order_by_list && *order_by_list ) {
5479 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5480 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5483 free( order_by_list );
5486 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5488 const char* str = jsonObjectGetString( limit );
5498 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5500 const char* str = jsonObjectGetString( offset );
5511 if( defaultselhash )
5512 jsonObjectFree( defaultselhash );
5513 clear_query_stack();
5515 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5516 return buffer_release( sql_buf );
5519 int doJSONSearch ( osrfMethodContext* ctx ) {
5520 if(osrfMethodVerifyContext( ctx )) {
5521 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5525 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5529 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5533 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5534 flags |= SELECT_DISTINCT;
5536 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5537 flags |= DISABLE_I18N;
5539 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5540 clear_query_stack(); // a possibly needless precaution
5541 char* sql = buildQuery( ctx, hash, flags );
5542 clear_query_stack();
5549 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5552 dbhandle = writehandle;
5554 dbi_result result = dbi_conn_query( dbhandle, sql );
5557 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5559 if( dbi_result_first_row( result )) {
5560 /* JSONify the result */
5561 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5564 jsonObject* return_val = oilsMakeJSONFromResult( result );
5565 osrfAppRespond( ctx, return_val );
5566 jsonObjectFree( return_val );
5567 } while( dbi_result_next_row( result ));
5570 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5573 osrfAppRespondComplete( ctx, NULL );
5575 /* clean up the query */
5576 dbi_result_free( result );
5581 int errnum = dbi_conn_error( dbhandle, &msg );
5582 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5583 modulename, sql, errnum, msg ? msg : "(No description available)" );
5584 osrfAppSessionStatus(
5586 OSRF_STATUS_INTERNALSERVERERROR,
5587 "osrfMethodException",
5589 "Severe query error -- see error log for more details"
5591 if( !oilsIsDBConnected( dbhandle ))
5592 osrfAppSessionPanic( ctx->session );
5599 // The last parameter, err, is used to report an error condition by updating an int owned by
5600 // the calling code.
5602 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5603 // It is the responsibility of the calling code to initialize *err before the
5604 // call, so that it will be able to make sense of the result.
5606 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5607 // redundant anyway.
5608 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5609 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5612 dbhandle = writehandle;
5614 char* core_class = osrfHashGet( class_meta, "classname" );
5615 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5617 char* pkey = osrfHashGet( class_meta, "primarykey" );
5619 if (!ctx->session->userData)
5620 (void) initSessionCache( ctx );
5622 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5623 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5624 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5626 int i_respond_directly = 0;
5627 int flesh_depth = 0;
5629 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5631 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5636 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5638 dbi_result result = dbi_conn_query( dbhandle, sql );
5639 if( NULL == result ) {
5641 int errnum = dbi_conn_error( dbhandle, &msg );
5642 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5643 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5644 msg ? msg : "(No description available)" );
5645 if( !oilsIsDBConnected( dbhandle ))
5646 osrfAppSessionPanic( ctx->session );
5647 osrfAppSessionStatus(
5649 OSRF_STATUS_INTERNALSERVERERROR,
5650 "osrfMethodException",
5652 "Severe query error -- see error log for more details"
5659 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5662 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5663 jsonObject* row_obj = NULL;
5665 // The following two steps are for verifyObjectPCRUD()'s benefit.
5666 // 1. get the flesh depth
5667 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5669 flesh_depth = (int) jsonObjectGetNumber( _tmp );
5670 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5671 flesh_depth = max_flesh_depth;
5674 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
5675 // over the whole life of this request. This means if we've already set
5676 // up a rs_size_req_%d, do nothing.
5677 // a. Incidentally, we can also use this opportunity to set i_respond_directly
5678 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
5679 if( !rs_size ) { // pointer null, so value not set in hash
5680 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
5681 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
5683 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
5684 unsigned long long result_count = dbi_result_get_numrows( result );
5685 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
5686 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
5689 if( dbi_result_first_row( result )) {
5691 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5692 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5693 // eliminate the duplicates.
5694 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5695 osrfHash* dedup = osrfNewHash();
5697 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5698 char* pkey_val = oilsFMGetString( row_obj, pkey );
5699 if( osrfHashGet( dedup, pkey_val ) ) {
5700 jsonObjectFree( row_obj );
5703 if( !enforce_pcrud || !need_to_verify ||
5704 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
5705 osrfHashSet( dedup, pkey_val, pkey_val );
5706 jsonObjectPush( res_list, row_obj );
5709 } while( dbi_result_next_row( result ));
5710 osrfHashFree( dedup );
5713 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5717 /* clean up the query */
5718 dbi_result_free( result );
5721 // If we're asked to flesh, and there's anything to flesh, then flesh it
5722 // (formerly we would skip fleshing if in pcrud mode, but now we support
5723 // fleshing even in PCRUD).
5724 if( res_list->size ) {
5725 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
5726 jsonObject* flesh_fields;
5727 jsonObject* flesh_blob = NULL;
5728 osrfStringArray* link_fields = NULL;
5729 osrfHash* links = NULL;
5733 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
5734 if( temp_blob && flesh_depth > 0 ) {
5736 flesh_blob = jsonObjectClone( temp_blob );
5737 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
5739 links = osrfHashGet( class_meta, "links" );
5741 // Make an osrfStringArray of the names of fields to be fleshed
5742 if( flesh_fields ) {
5743 if( flesh_fields->size == 1 ) {
5744 const char* _t = jsonObjectGetString(
5745 jsonObjectGetIndex( flesh_fields, 0 ) );
5746 if( !strcmp( _t, "*" ))
5747 link_fields = osrfHashKeys( links );
5750 if( !link_fields ) {
5752 link_fields = osrfNewStringArray( 1 );
5753 jsonIterator* _i = jsonNewIterator( flesh_fields );
5754 while ((_f = jsonIteratorNext( _i ))) {
5755 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5757 jsonIteratorFree( _i );
5760 want_flesh = link_fields ? 1 : 0;
5764 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5766 // Iterate over the JSON_ARRAY of rows
5768 unsigned long res_idx = 0;
5769 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5772 const char* link_field;
5774 // Iterate over the list of fleshable fields
5776 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5778 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5780 osrfHash* kid_link = osrfHashGet( links, link_field );
5782 continue; // Not a link field; skip it
5784 osrfHash* field = osrfHashGet( fields, link_field );
5786 continue; // Not a field at all; skip it (IDL is ill-formed)
5788 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5789 osrfHashGet( kid_link, "class" ));
5791 continue; // The class it links to doesn't exist; skip it
5793 const char* reltype = osrfHashGet( kid_link, "reltype" );
5795 continue; // No reltype; skip it (IDL is ill-formed)
5797 osrfHash* value_field = field;
5799 if( !strcmp( reltype, "has_many" )
5800 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5801 value_field = osrfHashGet(
5802 fields, osrfHashGet( class_meta, "primarykey" ) );
5805 int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
5806 // fleshing pcrud case: we require the controller in need_to_verify mode
5807 if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
5808 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
5812 (unsigned long) atoi( osrfHashGet(field, "array_position") ),
5814 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
5820 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5822 if( link_map->size > 0 ) {
5823 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5826 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5831 osrfHashGet( kid_link, "class" ),
5838 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5839 osrfHashGet( kid_link, "field" ),
5840 osrfHashGet( kid_link, "class" ),
5841 osrfHashGet( kid_link, "key" ),
5842 osrfHashGet( kid_link, "reltype" )
5845 const char* search_key = jsonObjectGetString(
5846 jsonObjectGetIndex( cur,
5847 atoi( osrfHashGet( value_field, "array_position" ) )
5852 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5856 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5858 // construct WHERE clause
5859 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5862 osrfHashGet( kid_link, "key" ),
5863 jsonNewObject( search_key )
5866 // construct the rest of the query, mostly
5867 // by copying pieces of the previous level of query
5868 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5869 jsonObjectSetKey( rest_of_query, "flesh",
5870 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5874 jsonObjectSetKey( rest_of_query, "flesh_fields",
5875 jsonObjectClone( flesh_blob ));
5877 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5878 jsonObjectSetKey( rest_of_query, "order_by",
5879 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5883 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5884 jsonObjectSetKey( rest_of_query, "select",
5885 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5889 // do the query, recursively, to expand the fleshable field
5890 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5891 where_clause, rest_of_query, err );
5893 jsonObjectFree( where_clause );
5894 jsonObjectFree( rest_of_query );
5897 osrfStringArrayFree( link_fields );
5898 jsonObjectFree( res_list );
5899 jsonObjectFree( flesh_blob );
5903 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5904 osrfHashGet( kid_link, "class" ), kids->size );
5906 // Traverse the result set
5907 jsonObject* X = NULL;
5908 if( link_map->size > 0 && kids->size > 0 ) {
5910 kids = jsonNewObjectType( JSON_ARRAY );
5912 jsonObject* _k_node;
5913 unsigned long res_idx = 0;
5914 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5920 (unsigned long) atoi(
5926 osrfHashGet( kid_link, "class" )
5930 osrfStringArrayGetString( link_map, 0 )
5938 } // end while loop traversing X
5941 if (kids->size > 0) {
5943 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5944 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
5946 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5947 osrfHashGet( kid_link, "field" ));
5950 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5951 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5956 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5958 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5959 osrfHashGet( kid_link, "field" ) );
5962 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5963 jsonObjectClone( kids )
5968 jsonObjectFree( kids );
5972 jsonObjectFree( kids );
5974 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5975 osrfHashGet( kid_link, "field" ) );
5976 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5978 } // end while loop traversing list of fleshable fields
5981 if( i_respond_directly ) {
5982 if ( *methodtype == 'i' ) {
5983 osrfAppRespond( ctx,
5984 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
5986 osrfAppRespond( ctx, cur );
5989 } // end while loop traversing res_list
5990 jsonObjectFree( flesh_blob );
5991 osrfStringArrayFree( link_fields );
5994 if( i_respond_directly ) {
5995 jsonObjectFree( res_list );
5996 return jsonNewObjectType( JSON_ARRAY );
6003 int doUpdate( osrfMethodContext* ctx ) {
6004 if( osrfMethodVerifyContext( ctx )) {
6005 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6010 timeout_needs_resetting = 1;
6012 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6014 jsonObject* target = NULL;
6016 target = jsonObjectGetIndex( ctx->params, 1 );
6018 target = jsonObjectGetIndex( ctx->params, 0 );
6020 if(!verifyObjectClass( ctx, target )) {
6021 osrfAppRespondComplete( ctx, NULL );
6025 if( getXactId( ctx ) == NULL ) {
6026 osrfAppSessionStatus(
6028 OSRF_STATUS_BADREQUEST,
6029 "osrfMethodException",
6031 "No active transaction -- required for UPDATE"
6033 osrfAppRespondComplete( ctx, NULL );
6037 // The following test is harmless but redundant. If a class is
6038 // readonly, we don't register an update method for it.
6039 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6040 osrfAppSessionStatus(
6042 OSRF_STATUS_BADREQUEST,
6043 "osrfMethodException",
6045 "Cannot UPDATE readonly class"
6047 osrfAppRespondComplete( ctx, NULL );
6051 const char* trans_id = getXactId( ctx );
6053 // Set the last_xact_id
6054 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6056 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6057 trans_id, target->classname, index );
6058 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6061 char* pkey = osrfHashGet( meta, "primarykey" );
6062 osrfHash* fields = osrfHashGet( meta, "fields" );
6064 char* id = oilsFMGetString( target, pkey );
6068 "%s updating %s object with %s = %s",
6070 osrfHashGet( meta, "fieldmapper" ),
6075 dbhandle = writehandle;
6076 growing_buffer* sql = buffer_init( 128 );
6077 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6080 osrfHash* field_def = NULL;
6081 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6082 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6084 // Skip virtual fields, and the primary key
6085 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6088 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6092 const char* field_name = osrfHashIteratorKey( field_itr );
6093 if( ! strcmp( field_name, pkey ) )
6096 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6098 int value_is_numeric = 0; // boolean
6100 if( field_object && field_object->classname ) {
6101 value = oilsFMGetString(
6103 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6105 } else if( field_object && JSON_BOOL == field_object->type ) {
6106 if( jsonBoolIsTrue( field_object ) )
6107 value = strdup( "t" );
6109 value = strdup( "f" );
6111 value = jsonObjectToSimpleString( field_object );
6112 if( field_object && JSON_NUMBER == field_object->type )
6113 value_is_numeric = 1;
6116 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6117 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6119 if( !field_object || field_object->type == JSON_NULL ) {
6120 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6121 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6125 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6126 buffer_fadd( sql, " %s = NULL", field_name );
6129 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6133 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6135 const char* numtype = get_datatype( field_def );
6136 if( !strncmp( numtype, "INT", 3 ) ) {
6137 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6138 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6139 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6141 // Must really be intended as a string, so quote it
6142 if( dbi_conn_quote_string( dbhandle, &value )) {
6143 buffer_fadd( sql, " %s = %s", field_name, value );
6145 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6146 modulename, value );
6147 osrfAppSessionStatus(
6149 OSRF_STATUS_INTERNALSERVERERROR,
6150 "osrfMethodException",
6152 "Error quoting string -- please see the error log for more details"
6156 osrfHashIteratorFree( field_itr );
6158 osrfAppRespondComplete( ctx, NULL );
6163 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6166 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6170 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6171 buffer_fadd( sql, " %s = %s", field_name, value );
6173 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6174 osrfAppSessionStatus(
6176 OSRF_STATUS_INTERNALSERVERERROR,
6177 "osrfMethodException",
6179 "Error quoting string -- please see the error log for more details"
6183 osrfHashIteratorFree( field_itr );
6185 osrfAppRespondComplete( ctx, NULL );
6194 osrfHashIteratorFree( field_itr );
6196 jsonObject* obj = jsonNewObject( id );
6198 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6199 dbi_conn_quote_string( dbhandle, &id );
6201 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6203 char* query = buffer_release( sql );
6204 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6206 dbi_result result = dbi_conn_query( dbhandle, query );
6211 jsonObjectFree( obj );
6212 obj = jsonNewObject( NULL );
6214 int errnum = dbi_conn_error( dbhandle, &msg );
6217 "%s ERROR updating %s object with %s = %s: %d %s",
6219 osrfHashGet( meta, "fieldmapper" ),
6223 msg ? msg : "(No description available)"
6225 osrfAppSessionStatus(
6227 OSRF_STATUS_INTERNALSERVERERROR,
6228 "osrfMethodException",
6230 "Error in updating a row -- please see the error log for more details"
6232 if( !oilsIsDBConnected( dbhandle ))
6233 osrfAppSessionPanic( ctx->session );
6236 dbi_result_free( result );
6239 osrfAppRespondComplete( ctx, obj );
6240 jsonObjectFree( obj );
6244 int doDelete( osrfMethodContext* ctx ) {
6245 if( osrfMethodVerifyContext( ctx )) {
6246 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6251 timeout_needs_resetting = 1;
6253 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6255 if( getXactId( ctx ) == NULL ) {
6256 osrfAppSessionStatus(
6258 OSRF_STATUS_BADREQUEST,
6259 "osrfMethodException",
6261 "No active transaction -- required for DELETE"
6263 osrfAppRespondComplete( ctx, NULL );
6267 // The following test is harmless but redundant. If a class is
6268 // readonly, we don't register a delete method for it.
6269 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6270 osrfAppSessionStatus(
6272 OSRF_STATUS_BADREQUEST,
6273 "osrfMethodException",
6275 "Cannot DELETE readonly class"
6277 osrfAppRespondComplete( ctx, NULL );
6281 dbhandle = writehandle;
6283 char* pkey = osrfHashGet( meta, "primarykey" );
6290 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6291 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6292 osrfAppRespondComplete( ctx, NULL );
6296 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6298 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6299 osrfAppRespondComplete( ctx, NULL );
6302 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6307 "%s deleting %s object with %s = %s",
6309 osrfHashGet( meta, "fieldmapper" ),
6314 jsonObject* obj = jsonNewObject( id );
6316 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6317 dbi_conn_quote_string( writehandle, &id );
6319 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6320 osrfHashGet( meta, "tablename" ), pkey, id );
6325 jsonObjectFree( obj );
6326 obj = jsonNewObject( NULL );
6328 int errnum = dbi_conn_error( writehandle, &msg );
6331 "%s ERROR deleting %s object with %s = %s: %d %s",
6333 osrfHashGet( meta, "fieldmapper" ),
6337 msg ? msg : "(No description available)"
6339 osrfAppSessionStatus(
6341 OSRF_STATUS_INTERNALSERVERERROR,
6342 "osrfMethodException",
6344 "Error in deleting a row -- please see the error log for more details"
6346 if( !oilsIsDBConnected( writehandle ))
6347 osrfAppSessionPanic( ctx->session );
6349 dbi_result_free( result );
6353 osrfAppRespondComplete( ctx, obj );
6354 jsonObjectFree( obj );
6359 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6360 @param result An iterator for a result set; we only look at the current row.
6361 @param @meta Pointer to the class metadata for the core class.
6362 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6364 If a column is not defined in the IDL, or if it has no array_position defined for it in
6365 the IDL, or if it is defined as virtual, ignore it.
6367 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6368 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6369 array_position in the IDL.
6371 A field defined in the IDL but not represented in the returned row will leave a hole
6372 in the JSON_ARRAY. In effect it will be treated as a null value.
6374 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6375 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6376 classname corresponding to the @a meta argument.
6378 The calling code is responsible for freeing the the resulting jsonObject by calling
6381 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6382 if( !( result && meta )) return NULL;
6384 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6385 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6386 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6388 osrfHash* fields = osrfHashGet( meta, "fields" );
6390 int columnIndex = 1;
6391 const char* columnName;
6393 /* cycle through the columns in the row returned from the database */
6394 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6396 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6398 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6400 /* determine the field type and storage attributes */
6401 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6402 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6404 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6405 // or if it has no sequence number there, or if it's virtual, skip it.
6406 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6409 if( str_is_true( osrfHashGet( _f, "virtual" )))
6410 continue; // skip this column: IDL says it's virtual
6412 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6413 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6414 continue; // since we assign sequence numbers dynamically as we load the IDL.
6416 fmIndex = atoi( pos );
6417 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6419 continue; // This field is not defined in the IDL
6422 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6423 // sequence number from the IDL (which is likely to be different from the sequence
6424 // of columns in the SELECT clause).
6425 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6426 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6431 case DBI_TYPE_INTEGER :
6433 if( attr & DBI_INTEGER_SIZE8 )
6434 jsonObjectSetIndex( object, fmIndex,
6435 jsonNewNumberObject(
6436 dbi_result_get_longlong_idx( result, columnIndex )));
6438 jsonObjectSetIndex( object, fmIndex,
6439 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6443 case DBI_TYPE_DECIMAL :
6444 jsonObjectSetIndex( object, fmIndex,
6445 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6448 case DBI_TYPE_STRING :
6453 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6458 case DBI_TYPE_DATETIME : {
6460 char dt_string[ 256 ] = "";
6463 // Fetch the date column as a time_t
6464 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6466 // Translate the time_t to a human-readable string
6467 if( !( attr & DBI_DATETIME_DATE )) {
6468 gmtime_r( &_tmp_dt, &gmdt );
6469 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6470 } else if( !( attr & DBI_DATETIME_TIME )) {
6471 localtime_r( &_tmp_dt, &gmdt );
6472 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6474 localtime_r( &_tmp_dt, &gmdt );
6475 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6478 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6482 case DBI_TYPE_BINARY :
6483 osrfLogError( OSRF_LOG_MARK,
6484 "Can't do binary at column %s : index %d", columnName, columnIndex );
6493 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6494 if( !result ) return NULL;
6496 jsonObject* object = jsonNewObject( NULL );
6499 char dt_string[ 256 ];
6503 int columnIndex = 1;
6505 unsigned short type;
6506 const char* columnName;
6508 /* cycle through the column list */
6509 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6511 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6513 fmIndex = -1; // reset the position
6515 /* determine the field type and storage attributes */
6516 type = dbi_result_get_field_type_idx( result, columnIndex );
6517 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6519 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6520 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6525 case DBI_TYPE_INTEGER :
6527 if( attr & DBI_INTEGER_SIZE8 )
6528 jsonObjectSetKey( object, columnName,
6529 jsonNewNumberObject( dbi_result_get_longlong_idx(
6530 result, columnIndex )) );
6532 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6533 dbi_result_get_int_idx( result, columnIndex )) );
6536 case DBI_TYPE_DECIMAL :
6537 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6538 dbi_result_get_double_idx( result, columnIndex )) );
6541 case DBI_TYPE_STRING :
6542 jsonObjectSetKey( object, columnName,
6543 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6546 case DBI_TYPE_DATETIME :
6548 memset( dt_string, '\0', sizeof( dt_string ));
6549 memset( &gmdt, '\0', sizeof( gmdt ));
6551 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6553 if( !( attr & DBI_DATETIME_DATE )) {
6554 gmtime_r( &_tmp_dt, &gmdt );
6555 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6556 } else if( !( attr & DBI_DATETIME_TIME )) {
6557 localtime_r( &_tmp_dt, &gmdt );
6558 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6560 localtime_r( &_tmp_dt, &gmdt );
6561 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6564 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6567 case DBI_TYPE_BINARY :
6568 osrfLogError( OSRF_LOG_MARK,
6569 "Can't do binary at column %s : index %d", columnName, columnIndex );
6573 } // end while loop traversing result
6578 // Interpret a string as true or false
6579 int str_is_true( const char* str ) {
6580 if( NULL == str || strcasecmp( str, "true" ) )
6586 // Interpret a jsonObject as true or false
6587 static int obj_is_true( const jsonObject* obj ) {
6590 else switch( obj->type )
6598 if( strcasecmp( obj->value.s, "true" ) )
6602 case JSON_NUMBER : // Support 1/0 for perl's sake
6603 if( jsonObjectGetNumber( obj ) == 1.0 )
6612 // Translate a numeric code into a text string identifying a type of
6613 // jsonObject. To be used for building error messages.
6614 static const char* json_type( int code ) {
6620 return "JSON_ARRAY";
6622 return "JSON_STRING";
6624 return "JSON_NUMBER";
6630 return "(unrecognized)";
6634 // Extract the "primitive" attribute from an IDL field definition.
6635 // If we haven't initialized the app, then we must be running in
6636 // some kind of testbed. In that case, default to "string".
6637 static const char* get_primitive( osrfHash* field ) {
6638 const char* s = osrfHashGet( field, "primitive" );
6640 if( child_initialized )
6643 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6645 osrfHashGet( field, "name" )
6653 // Extract the "datatype" attribute from an IDL field definition.
6654 // If we haven't initialized the app, then we must be running in
6655 // some kind of testbed. In that case, default to to NUMERIC,
6656 // since we look at the datatype only for numbers.
6657 static const char* get_datatype( osrfHash* field ) {
6658 const char* s = osrfHashGet( field, "datatype" );
6660 if( child_initialized )
6663 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6665 osrfHashGet( field, "name" )
6674 @brief Determine whether a string is potentially a valid SQL identifier.
6675 @param s The identifier to be tested.
6676 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6678 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6679 need to follow all the rules exactly, such as requiring that the first character not
6682 We allow leading and trailing white space. In between, we do not allow punctuation
6683 (except for underscores and dollar signs), control characters, or embedded white space.
6685 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6686 for the foreseeable future such quoted identifiers are not likely to be an issue.
6688 int is_identifier( const char* s) {
6692 // Skip leading white space
6693 while( isspace( (unsigned char) *s ) )
6697 return 0; // Nothing but white space? Not okay.
6699 // Check each character until we reach white space or
6700 // end-of-string. Letters, digits, underscores, and
6701 // dollar signs are okay. With the exception of periods
6702 // (as in schema.identifier), control characters and other
6703 // punctuation characters are not okay. Anything else
6704 // is okay -- it could for example be part of a multibyte
6705 // UTF8 character such as a letter with diacritical marks,
6706 // and those are allowed.
6708 if( isalnum( (unsigned char) *s )
6712 ; // Fine; keep going
6713 else if( ispunct( (unsigned char) *s )
6714 || iscntrl( (unsigned char) *s ) )
6717 } while( *s && ! isspace( (unsigned char) *s ) );
6719 // If we found any white space in the above loop,
6720 // the rest had better be all white space.
6722 while( isspace( (unsigned char) *s ) )
6726 return 0; // White space was embedded within non-white space
6732 @brief Determine whether to accept a character string as a comparison operator.
6733 @param op The candidate comparison operator.
6734 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6736 We don't validate the operator for real. We just make sure that it doesn't contain
6737 any semicolons or white space (with special exceptions for a few specific operators).
6738 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6739 space but it's still not a valid operator, then the database will complain.
6741 Another approach would be to compare the string against a short list of approved operators.
6742 We don't do that because we want to allow custom operators like ">100*", which at this
6743 writing would be difficult or impossible to express otherwise in a JSON query.
6745 int is_good_operator( const char* op ) {
6746 if( !op ) return 0; // Sanity check
6750 if( isspace( (unsigned char) *s ) ) {
6751 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6752 // and IS NOT DISTINCT FROM.
6753 if( !strcasecmp( op, "similar to" ) )
6755 else if( !strcasecmp( op, "is distinct from" ) )
6757 else if( !strcasecmp( op, "is not distinct from" ) )
6762 else if( ';' == *s )
6770 @name Query Frame Management
6772 The following machinery supports a stack of query frames for use by SELECT().
6774 A query frame caches information about one level of a SELECT query. When we enter
6775 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6777 The query frame stores information about the core class, and about any joined classes
6780 The main purpose is to map table aliases to classes and tables, so that a query can
6781 join to the same table more than once. A secondary goal is to reduce the number of
6782 lookups in the IDL by caching the results.
6786 #define STATIC_CLASS_INFO_COUNT 3
6788 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6791 @brief Allocate a ClassInfo as raw memory.
6792 @return Pointer to the newly allocated ClassInfo.
6794 Except for the in_use flag, which is used only by the allocation and deallocation
6795 logic, we don't initialize the ClassInfo here.
6797 static ClassInfo* allocate_class_info( void ) {
6798 // In order to reduce the number of mallocs and frees, we return a static
6799 // instance of ClassInfo, if we can find one that we're not already using.
6800 // We rely on the fact that the compiler will implicitly initialize the
6801 // static instances so that in_use == 0.
6804 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6805 if( ! static_class_info[ i ].in_use ) {
6806 static_class_info[ i ].in_use = 1;
6807 return static_class_info + i;
6811 // The static ones are all in use. Malloc one.
6813 return safe_malloc( sizeof( ClassInfo ) );
6817 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6818 @param info Pointer to the ClassInfo to be cleared.
6820 static void clear_class_info( ClassInfo* info ) {
6825 // Free any malloc'd strings
6827 if( info->alias != info->alias_store )
6828 free( info->alias );
6830 if( info->class_name != info->class_name_store )
6831 free( info->class_name );
6833 free( info->source_def );
6835 info->alias = info->class_name = info->source_def = NULL;
6840 @brief Free a ClassInfo and everything it owns.
6841 @param info Pointer to the ClassInfo to be freed.
6843 static void free_class_info( ClassInfo* info ) {
6848 clear_class_info( info );
6850 // If it's one of the static instances, just mark it as not in use
6853 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6854 if( info == static_class_info + i ) {
6855 static_class_info[ i ].in_use = 0;
6860 // Otherwise it must have been malloc'd, so free it
6866 @brief Populate an already-allocated ClassInfo.
6867 @param info Pointer to the ClassInfo to be populated.
6868 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6870 @param class Name of the class.
6871 @return Zero if successful, or 1 if not.
6873 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6874 the relevant portions of the IDL for the specified class.
6876 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6879 osrfLogError( OSRF_LOG_MARK,
6880 "%s ERROR: No ClassInfo available to populate", modulename );
6881 info->alias = info->class_name = info->source_def = NULL;
6882 info->class_def = info->fields = info->links = NULL;
6887 osrfLogError( OSRF_LOG_MARK,
6888 "%s ERROR: No class name provided for lookup", modulename );
6889 info->alias = info->class_name = info->source_def = NULL;
6890 info->class_def = info->fields = info->links = NULL;
6894 // Alias defaults to class name if not supplied
6895 if( ! alias || ! alias[ 0 ] )
6898 // Look up class info in the IDL
6899 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6901 osrfLogError( OSRF_LOG_MARK,
6902 "%s ERROR: Class %s not defined in IDL", modulename, class );
6903 info->alias = info->class_name = info->source_def = NULL;
6904 info->class_def = info->fields = info->links = NULL;
6906 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6907 osrfLogError( OSRF_LOG_MARK,
6908 "%s ERROR: Class %s is defined as virtual", modulename, class );
6909 info->alias = info->class_name = info->source_def = NULL;
6910 info->class_def = info->fields = info->links = NULL;
6914 osrfHash* links = osrfHashGet( class_def, "links" );
6916 osrfLogError( OSRF_LOG_MARK,
6917 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6918 info->alias = info->class_name = info->source_def = NULL;
6919 info->class_def = info->fields = info->links = NULL;
6923 osrfHash* fields = osrfHashGet( class_def, "fields" );
6925 osrfLogError( OSRF_LOG_MARK,
6926 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6927 info->alias = info->class_name = info->source_def = NULL;
6928 info->class_def = info->fields = info->links = NULL;
6932 char* source_def = oilsGetRelation( class_def );
6936 // We got everything we need, so populate the ClassInfo
6937 if( strlen( alias ) > ALIAS_STORE_SIZE )
6938 info->alias = strdup( alias );
6940 strcpy( info->alias_store, alias );
6941 info->alias = info->alias_store;
6944 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6945 info->class_name = strdup( class );
6947 strcpy( info->class_name_store, class );
6948 info->class_name = info->class_name_store;
6951 info->source_def = source_def;
6953 info->class_def = class_def;
6954 info->links = links;
6955 info->fields = fields;
6960 #define STATIC_FRAME_COUNT 3
6962 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6965 @brief Allocate a QueryFrame as raw memory.
6966 @return Pointer to the newly allocated QueryFrame.
6968 Except for the in_use flag, which is used only by the allocation and deallocation
6969 logic, we don't initialize the QueryFrame here.
6971 static QueryFrame* allocate_frame( void ) {
6972 // In order to reduce the number of mallocs and frees, we return a static
6973 // instance of QueryFrame, if we can find one that we're not already using.
6974 // We rely on the fact that the compiler will implicitly initialize the
6975 // static instances so that in_use == 0.
6978 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6979 if( ! static_frame[ i ].in_use ) {
6980 static_frame[ i ].in_use = 1;
6981 return static_frame + i;
6985 // The static ones are all in use. Malloc one.
6987 return safe_malloc( sizeof( QueryFrame ) );
6991 @brief Free a QueryFrame, and all the memory it owns.
6992 @param frame Pointer to the QueryFrame to be freed.
6994 static void free_query_frame( QueryFrame* frame ) {
6999 clear_class_info( &frame->core );
7001 // Free the join list
7003 ClassInfo* info = frame->join_list;
7006 free_class_info( info );
7010 frame->join_list = NULL;
7013 // If the frame is a static instance, just mark it as unused
7015 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7016 if( frame == static_frame + i ) {
7017 static_frame[ i ].in_use = 0;
7022 // Otherwise it must have been malloc'd, so free it
7028 @brief Search a given QueryFrame for a specified alias.
7029 @param frame Pointer to the QueryFrame to be searched.
7030 @param target The alias for which to search.
7031 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7033 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7034 if( ! frame || ! target ) {
7038 ClassInfo* found_class = NULL;
7040 if( !strcmp( target, frame->core.alias ) )
7041 return &(frame->core);
7043 ClassInfo* curr_class = frame->join_list;
7044 while( curr_class ) {
7045 if( strcmp( target, curr_class->alias ) )
7046 curr_class = curr_class->next;
7048 found_class = curr_class;
7058 @brief Push a new (blank) QueryFrame onto the stack.
7060 static void push_query_frame( void ) {
7061 QueryFrame* frame = allocate_frame();
7062 frame->join_list = NULL;
7063 frame->next = curr_query;
7065 // Initialize the ClassInfo for the core class
7066 ClassInfo* core = &frame->core;
7067 core->alias = core->class_name = core->source_def = NULL;
7068 core->class_def = core->fields = core->links = NULL;
7074 @brief Pop a QueryFrame off the stack and destroy it.
7076 static void pop_query_frame( void ) {
7081 QueryFrame* popped = curr_query;
7082 curr_query = popped->next;
7084 free_query_frame( popped );
7088 @brief Populate the ClassInfo for the core class.
7089 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7090 class name as an alias.
7091 @param class_name Name of the core class.
7092 @return Zero if successful, or 1 if not.
7094 Populate the ClassInfo of the core class with copies of the alias and class name, and
7095 with pointers to the relevant portions of the IDL for the core class.
7097 static int add_query_core( const char* alias, const char* class_name ) {
7100 if( ! curr_query ) {
7101 osrfLogError( OSRF_LOG_MARK,
7102 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7104 } else if( curr_query->core.alias ) {
7105 osrfLogError( OSRF_LOG_MARK,
7106 "%s ERROR: Core class %s already populated as %s",
7107 modulename, curr_query->core.class_name, curr_query->core.alias );
7111 build_class_info( &curr_query->core, alias, class_name );
7112 if( curr_query->core.alias )
7115 osrfLogError( OSRF_LOG_MARK,
7116 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7122 @brief Search the current QueryFrame for a specified alias.
7123 @param target The alias for which to search.
7124 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7126 static inline ClassInfo* search_alias( const char* target ) {
7127 return search_alias_in_frame( curr_query, target );
7131 @brief Search all levels of query for a specified alias, starting with the current query.
7132 @param target The alias for which to search.
7133 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7135 static ClassInfo* search_all_alias( const char* target ) {
7136 ClassInfo* found_class = NULL;
7137 QueryFrame* curr_frame = curr_query;
7139 while( curr_frame ) {
7140 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7143 curr_frame = curr_frame->next;
7150 @brief Add a class to the list of classes joined to the current query.
7151 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7152 the class name as an alias.
7153 @param classname The name of the class to be added.
7154 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7156 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7158 if( ! classname || ! *classname ) { // sanity check
7159 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7166 const ClassInfo* conflict = search_alias( alias );
7168 osrfLogError( OSRF_LOG_MARK,
7169 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7170 modulename, alias, conflict->class_name );
7174 ClassInfo* info = allocate_class_info();
7176 if( build_class_info( info, alias, classname ) ) {
7177 free_class_info( info );
7181 // Add the new ClassInfo to the join list of the current QueryFrame
7182 info->next = curr_query->join_list;
7183 curr_query->join_list = info;
7189 @brief Destroy all nodes on the query stack.
7191 static void clear_query_stack( void ) {
7197 @brief Implement the set_audit_info method.
7198 @param ctx Pointer to the method context.
7199 @return Zero if successful, or -1 if not.
7201 Issue a SAVEPOINT to the database server.
7206 - workstation id (int)
7208 If user id is not provided the authkey will be used.
7209 For PCRUD the authkey is always used, even if a user is provided.
7211 int setAuditInfo( osrfMethodContext* ctx ) {
7212 if(osrfMethodVerifyContext( ctx )) {
7213 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
7217 // Get the user id from the parameters
7218 const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7220 if( enforce_pcrud || !user_id ) {
7221 timeout_needs_resetting = 1;
7222 const jsonObject* user = verifyUserPCRUD( ctx );
7225 osrfAppRespondComplete( ctx, NULL );
7229 // Not PCRUD and have a user_id?
7230 int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7231 osrfAppRespondComplete( ctx, NULL );
7236 @brief Save a audit info
7237 @param ctx Pointer to the method context.
7238 @param user_id User ID to write as a string
7239 @param ws_id Workstation ID to write as a string
7241 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7242 if( ctx && ctx->session ) {
7243 osrfAppSession* session = ctx->session;
7245 osrfHash* cache = session->userData;
7247 // If the session doesn't already have a hash, create one. Make sure
7248 // that the application session frees the hash when it terminates.
7249 if( NULL == cache ) {
7250 session->userData = cache = osrfNewHash();
7251 osrfHashSetCallback( cache, &sessionDataFree );
7252 ctx->session->userDataFree = &userDataFree;
7255 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7257 osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7259 int errnum = dbi_conn_error( writehandle, &msg );
7262 "%s: Error setting auditor information: %d %s",
7265 msg ? msg : "(No description available)"
7267 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7268 "osrfMethodException", ctx->request, "Error setting auditor info" );
7269 if( !oilsIsDBConnected( writehandle ))
7270 osrfAppSessionPanic( ctx->session );
7273 dbi_result_free( result );
7280 @brief Remove all but safe character from savepoint name
7281 @param sp User-supplied savepoint name
7282 @return sanitized savepoint name, or NULL
7284 The caller is expected to free the returned string. Note that
7285 this function exists only because we can't use PQescapeLiteral
7286 without either forking libdbi or abandoning it.
7288 static char* _sanitize_savepoint_name( const char* sp ) {
7290 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_";
7292 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7293 // and the default value of NAMEDATALEN is 64; that should be long enough
7294 // for our purposes, and it's unlikely that anyone is going to recompile
7295 // PostgreSQL to have a smaller value, so cap the identifier name
7296 // accordingly to avoid the remote chance that someone manages to pass in a
7297 // 12GB savepoint name
7298 const int MAX_LITERAL_NAMELEN = 63;
7301 if (len > MAX_LITERAL_NAMELEN) {
7302 len = MAX_LITERAL_NAMELEN;
7305 char* safeSpName = safe_malloc( len + 1 );
7309 for (j = 0; j < len; j++) {
7310 found = strchr(safe_chars, sp[j]);
7312 safeSpName[ i++ ] = found[0];
7315 safeSpName[ i ] = '\0';