3 @brief Utility routines for translating JSON into SQL.
11 #include "opensrf/utils.h"
12 #include "opensrf/log.h"
13 #include "opensrf/osrf_application.h"
14 #include "openils/oils_utils.h"
15 #include "openils/oils_sql.h"
17 // The next four macros are OR'd together as needed to form a set
18 // of bitflags. SUBCOMBO enables an extra pair of parentheses when
19 // nesting one UNION, INTERSECT or EXCEPT inside another.
20 // SUBSELECT tells us we're in a subquery, so don't add the
21 // terminal semicolon yet.
24 #define DISABLE_I18N 2
25 #define SELECT_DISTINCT 1
30 struct ClassInfoStruct;
31 typedef struct ClassInfoStruct ClassInfo;
33 #define ALIAS_STORE_SIZE 16
34 #define CLASS_NAME_STORE_SIZE 16
36 struct ClassInfoStruct {
40 osrfHash* class_def; // Points into IDL
41 osrfHash* fields; // Points into IDL
42 osrfHash* links; // Points into IDL
44 // The remaining members are private and internal. Client code should not
45 // access them directly.
47 ClassInfo* next; // Supports linked list of joined classes
48 int in_use; // boolean
50 // We usually store the alias and class name in the following arrays, and
51 // point the corresponding pointers at them. When the string is too big
52 // for the array (which will probably never happen in practice), we strdup it.
54 char alias_store[ ALIAS_STORE_SIZE + 1 ];
55 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
58 struct QueryFrameStruct;
59 typedef struct QueryFrameStruct QueryFrame;
61 struct QueryFrameStruct {
63 ClassInfo* join_list; // linked list of classes joined to the core class
64 QueryFrame* next; // implements stack as linked list
65 int in_use; // boolean
68 static int timeout_needs_resetting;
69 static time_t time_next_reset;
71 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
73 static void setXactId( osrfMethodContext* ctx );
74 static inline const char* getXactId( osrfMethodContext* ctx );
75 static inline void clearXactId( osrfMethodContext* ctx );
77 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
78 jsonObject* where_hash, jsonObject* query_hash, int* err );
79 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
80 static jsonObject* oilsMakeJSONFromResult( dbi_result );
82 static char* searchSimplePredicate ( const char* op, const char* class_alias,
83 osrfHash* field, const jsonObject* node );
84 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
85 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject* );
86 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*,
88 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
89 static char* searchINPredicate ( const char*, osrfHash*,
90 jsonObject*, const char*, osrfMethodContext* );
91 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
92 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
93 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo*, int, osrfMethodContext* );
94 static char* buildSELECT( const jsonObject*, jsonObject* rest_of_query,
95 osrfHash* meta, osrfMethodContext* ctx );
96 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array );
98 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
100 char* SELECT ( osrfMethodContext*, jsonObject*, const jsonObject*, const jsonObject*,
101 const jsonObject*, const jsonObject*, const jsonObject*, const jsonObject*, int );
103 static osrfStringArray* getPermLocationCache( osrfMethodContext*, const char* );
104 static void setPermLocationCache( osrfMethodContext*, const char*, osrfStringArray* );
106 void userDataFree( void* );
107 static void sessionDataFree( char*, void* );
108 static void pcacheFree( char*, void* );
109 static int obj_is_true( const jsonObject* obj );
110 static const char* json_type( int code );
111 static const char* get_primitive( osrfHash* field );
112 static const char* get_datatype( osrfHash* field );
113 static void pop_query_frame( void );
114 static void push_query_frame( void );
115 static int add_query_core( const char* alias, const char* class_name );
116 static inline ClassInfo* search_alias( const char* target );
117 static ClassInfo* search_all_alias( const char* target );
118 static ClassInfo* add_joined_class( const char* alias, const char* classname );
119 static void clear_query_stack( void );
121 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
122 static int verifyObjectPCRUD( osrfMethodContext*, osrfHash*, const jsonObject*, int );
123 static const char* org_tree_root( osrfMethodContext* ctx );
124 static jsonObject* single_hash( const char* key, const char* value );
126 static int child_initialized = 0; /* boolean */
128 static dbi_conn writehandle; /* our MASTER db connection */
129 static dbi_conn dbhandle; /* our CURRENT db connection */
130 //static osrfHash * readHandles;
132 // The following points to the top of a stack of QueryFrames. It's a little
133 // confusing because the top level of the query is at the bottom of the stack.
134 static QueryFrame* curr_query = NULL;
136 static dbi_conn writehandle; /* our MASTER db connection */
137 static dbi_conn dbhandle; /* our CURRENT db connection */
138 //static osrfHash * readHandles;
140 static int max_flesh_depth = 100;
142 static int perm_at_threshold = 5;
143 static int enforce_pcrud = 0; // Boolean
144 static char* modulename = NULL;
146 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id);
148 static char* _sanitize_savepoint_name( const char* sp );
151 @brief Connect to the database.
152 @return A database connection if successful, or NULL if not.
154 dbi_conn oilsConnectDB( const char* mod_name ) {
156 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
157 if( dbi_initialize( NULL ) == -1 ) {
158 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
161 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
163 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
164 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
165 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
166 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
167 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
168 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
170 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
171 dbi_conn handle = dbi_conn_new( driver );
174 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
177 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
179 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
180 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
182 if( host ) dbi_conn_set_option( handle, "host", host );
183 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
184 if( user ) dbi_conn_set_option( handle, "username", user );
185 if( pw ) dbi_conn_set_option( handle, "password", pw );
186 if( db ) dbi_conn_set_option( handle, "dbname", db );
194 if( dbi_conn_connect( handle ) < 0 ) {
196 if( dbi_conn_connect( handle ) < 0 ) {
198 dbi_conn_error( handle, &msg );
199 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
200 msg ? msg : "(No description available)" );
205 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
211 @brief Select some options.
212 @param module_name: Name of the server.
213 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
215 This source file is used (at this writing) to implement three different servers:
216 - open-ils.reporter-store
220 These servers behave mostly the same, but they implement different combinations of
221 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
223 Here we use the server name in messages to identify which kind of server issued them.
224 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
226 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
228 module_name = "open-ils.cstore"; // bulletproofing with a default
233 modulename = strdup( module_name );
234 enforce_pcrud = do_pcrud;
235 max_flesh_depth = flesh_depth;
239 @brief Install a database connection.
240 @param conn Pointer to a database connection.
242 In some contexts, @a conn may merely provide a driver so that we can process strings
243 properly, without providing an open database connection.
245 void oilsSetDBConnection( dbi_conn conn ) {
246 dbhandle = writehandle = conn;
250 @brief Determine whether a database connection is alive.
251 @param handle Handle for a database connection.
252 @return 1 if the connection is alive, or zero if it isn't.
254 int oilsIsDBConnected( dbi_conn handle ) {
255 // Do an innocuous SELECT. If it succeeds, the database connection is still good.
256 dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
258 dbi_result_free( result );
261 // This is a terrible, horrible, no good, very bad kludge.
262 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
263 // but because (due to a previous error) the database is ignoring all commands,
264 // even innocuous SELECTs, until the current transaction is rolled back. The only
265 // known way to detect this condition via the dbi library is by looking at the error
266 // message. This approach will break if the language or wording of the message ever
268 // Note: the dbi_conn_ping function purports to determine whether the database
269 // connection is live, but at this writing this function is unreliable and useless.
270 static const char* ok_msg = "ERROR: current transaction is aborted, commands "
271 "ignored until end of transaction block\n";
273 dbi_conn_error( handle, &msg );
274 if( strcmp( msg, ok_msg )) {
275 osrfLogError( OSRF_LOG_MARK, "Database connection isn't working" );
278 return 1; // ignoring SELECT due to previous error; that's okay
283 @brief Get a table name, view name, or subquery for use in a FROM clause.
284 @param class Pointer to the IDL class entry.
285 @return A table name, a view name, or a subquery in parentheses.
287 In some cases the IDL defines a class, not with a table name or a view name, but with
288 a SELECT statement, which may be used as a subquery.
290 char* oilsGetRelation( osrfHash* classdef ) {
292 char* source_def = NULL;
293 const char* tabledef = osrfHashGet( classdef, "tablename" );
296 source_def = strdup( tabledef ); // Return the name of a table or view
298 tabledef = osrfHashGet( classdef, "source_definition" );
300 // Return a subquery, enclosed in parentheses
301 source_def = safe_malloc( strlen( tabledef ) + 3 );
302 source_def[ 0 ] = '(';
303 strcpy( source_def + 1, tabledef );
304 strcat( source_def, ")" );
306 // Not found: return an error
307 const char* classname = osrfHashGet( classdef, "classname" );
312 "%s ERROR No tablename or source_definition for class \"%s\"",
323 @brief Add datatypes from the database to the fields in the IDL.
324 @param handle Handle for a database connection
325 @return Zero if successful, or 1 upon error.
327 For each relevant class in the IDL: ask the database for the datatype of every field.
328 In particular, determine which fields are text fields and which fields are numeric
329 fields, so that we know whether to enclose their values in quotes.
331 int oilsExtendIDL( dbi_conn handle ) {
332 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
333 osrfHash* class = NULL;
334 growing_buffer* query_buf = buffer_init( 64 );
335 int results_found = 0; // boolean
337 // For each class in the IDL...
338 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
339 const char* classname = osrfHashIteratorKey( class_itr );
340 osrfHash* fields = osrfHashGet( class, "fields" );
342 // If the class is virtual, ignore it
343 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
344 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
348 char* tabledef = oilsGetRelation( class );
350 continue; // No such relation -- a query of it would be doomed to failure
352 buffer_reset( query_buf );
353 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
357 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
358 modulename, OSRF_BUFFER_C_STR( query_buf ) );
360 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
365 const char* columnName;
366 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
368 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
371 /* fetch the fieldmapper index */
372 osrfHash* _f = osrfHashGet(fields, columnName);
375 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
377 /* determine the field type and storage attributes */
379 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
381 case DBI_TYPE_INTEGER : {
383 if( !osrfHashGet(_f, "primitive") )
384 osrfHashSet(_f, "number", "primitive");
386 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
387 if( attr & DBI_INTEGER_SIZE8 )
388 osrfHashSet( _f, "INT8", "datatype" );
390 osrfHashSet( _f, "INT", "datatype" );
393 case DBI_TYPE_DECIMAL :
394 if( !osrfHashGet( _f, "primitive" ))
395 osrfHashSet( _f, "number", "primitive" );
397 osrfHashSet( _f, "NUMERIC", "datatype" );
400 case DBI_TYPE_STRING :
401 if( !osrfHashGet( _f, "primitive" ))
402 osrfHashSet( _f, "string", "primitive" );
404 osrfHashSet( _f,"TEXT", "datatype" );
407 case DBI_TYPE_DATETIME :
408 if( !osrfHashGet( _f, "primitive" ))
409 osrfHashSet( _f, "string", "primitive" );
411 osrfHashSet( _f, "TIMESTAMP", "datatype" );
414 case DBI_TYPE_BINARY :
415 if( !osrfHashGet( _f, "primitive" ))
416 osrfHashSet( _f, "string", "primitive" );
418 osrfHashSet( _f, "BYTEA", "datatype" );
423 "Setting [%s] to primitive [%s] and datatype [%s]...",
425 osrfHashGet( _f, "primitive" ),
426 osrfHashGet( _f, "datatype" )
430 } // end while loop for traversing columns of result
431 dbi_result_free( result );
434 int errnum = dbi_conn_error( handle, &msg );
435 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
436 errnum, msg ? msg : "(No description available)" );
437 // We don't check the database connection here. It's routine to get failures at
438 // this point; we routinely try to query tables that don't exist, because they
439 // are defined in the IDL but not in the database.
441 } // end for each class in IDL
443 buffer_free( query_buf );
444 osrfHashIteratorFree( class_itr );
445 child_initialized = 1;
447 if( !results_found ) {
448 osrfLogError( OSRF_LOG_MARK,
449 "No results found for any class -- bad database connection?" );
451 } else if( ! oilsIsDBConnected( handle )) {
452 osrfLogError( OSRF_LOG_MARK,
453 "Unable to extend IDL: database connection isn't working" );
461 @brief Free an osrfHash that stores a transaction ID.
462 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
464 This function is a callback, to be called by the application session when it ends.
465 The application session stores the osrfHash via an opaque pointer.
467 If the osrfHash contains an entry for the key "xact_id", it means that an
468 uncommitted transaction is pending. Roll it back.
470 void userDataFree( void* blob ) {
471 osrfHash* hash = (osrfHash*) blob;
472 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
473 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
475 int errnum = dbi_conn_error( writehandle, &msg );
476 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
477 errnum, msg ? msg : "(No description available)" );
481 if( !dbi_conn_query( writehandle, "SELECT auditor.clear_audit_info();" ) ) {
483 int errnum = dbi_conn_error( writehandle, &msg );
484 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform audit info clearing: %d %s",
485 errnum, msg ? msg : "(No description available)" );
489 osrfHashFree( hash );
493 @name Managing session data
494 @brief Maintain data stored via the userData pointer of the application session.
496 Currently, session-level data is stored in an osrfHash. Other arrangements are
497 possible, and some would be more efficient. The application session calls a
498 callback function to free userData before terminating.
500 Currently, the only data we store at the session level is the transaction id. By this
501 means we can ensure that any pending transactions are rolled back before the application
507 @brief Free an item in the application session's userData.
508 @param key The name of a key for an osrfHash.
509 @param item An opaque pointer to the item associated with the key.
511 We store an osrfHash as userData with the application session, and arrange (by
512 installing userDataFree() as a different callback) for the session to free that
513 osrfHash before terminating.
515 This function is a callback for freeing items in the osrfHash. Currently we store
517 - Transaction id of a pending transaction; a character string. Key: "xact_id".
518 - Authkey; a character string. Key: "authkey".
519 - User object from the authentication server; a jsonObject. Key: "user_login".
521 If we ever store anything else in userData, we will need to revisit this function so
522 that it will free whatever else needs freeing.
524 static void sessionDataFree( char* key, void* item ) {
525 if( !strcmp( key, "xact_id" ) || !strcmp( key, "authkey" ) || !strncmp( key, "rs_size_", 8) )
527 else if( !strcmp( key, "user_login" ) )
528 jsonObjectFree( (jsonObject*) item );
529 else if( !strcmp( key, "pcache" ) )
530 osrfHashFree( (osrfHash*) item );
533 static void pcacheFree( char* key, void* item ) {
534 osrfStringArrayFree( (osrfStringArray*) item );
538 @brief Initialize session cache.
539 @param ctx Pointer to the method context.
541 Create a cache for the session by making the session's userData member point
542 to an osrfHash instance.
544 static osrfHash* initSessionCache( osrfMethodContext* ctx ) {
545 ctx->session->userData = osrfNewHash();
546 osrfHashSetCallback( (osrfHash*) ctx->session->userData, &sessionDataFree );
547 ctx->session->userDataFree = &userDataFree;
548 return ctx->session->userData;
552 @brief Save a transaction id.
553 @param ctx Pointer to the method context.
555 Save the session_id of the current application session as a transaction id.
557 static void setXactId( osrfMethodContext* ctx ) {
558 if( ctx && ctx->session ) {
559 osrfAppSession* session = ctx->session;
561 osrfHash* cache = session->userData;
563 // If the session doesn't already have a hash, create one. Make sure
564 // that the application session frees the hash when it terminates.
566 cache = initSessionCache( ctx );
568 // Save the transaction id in the hash, with the key "xact_id"
569 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
574 @brief Get the transaction ID for the current transaction, if any.
575 @param ctx Pointer to the method context.
576 @return Pointer to the transaction ID.
578 The return value points to an internal buffer, and will become invalid upon issuing
579 a commit or rollback.
581 static inline const char* getXactId( osrfMethodContext* ctx ) {
582 if( ctx && ctx->session && ctx->session->userData )
583 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
589 @brief Clear the current transaction id.
590 @param ctx Pointer to the method context.
592 static inline void clearXactId( osrfMethodContext* ctx ) {
593 if( ctx && ctx->session && ctx->session->userData )
594 osrfHashRemove( ctx->session->userData, "xact_id" );
599 @brief Stash the location for a particular perm in the sessionData cache
600 @param ctx Pointer to the method context.
601 @param perm Name of the permission we're looking at
602 @param array StringArray of perm location ids
604 static void setPermLocationCache( osrfMethodContext* ctx, const char* perm, osrfStringArray* locations ) {
605 if( ctx && ctx->session ) {
606 osrfAppSession* session = ctx->session;
608 osrfHash* cache = session->userData;
610 // If the session doesn't already have a hash, create one. Make sure
611 // that the application session frees the hash when it terminates.
613 cache = initSessionCache( ctx );
615 osrfHash* pcache = osrfHashGet(cache, "pcache");
617 if( NULL == pcache ) {
618 pcache = osrfNewHash();
619 osrfHashSetCallback( pcache, &pcacheFree );
620 osrfHashSet( cache, pcache, "pcache" );
623 if( perm && locations )
624 osrfHashSet( pcache, locations, strdup(perm) );
629 @brief Grab stashed location for a particular perm in the sessionData cache
630 @param ctx Pointer to the method context.
631 @param perm Name of the permission we're looking at
633 static osrfStringArray* getPermLocationCache( osrfMethodContext* ctx, const char* perm ) {
634 if( ctx && ctx->session ) {
635 osrfAppSession* session = ctx->session;
636 osrfHash* cache = session->userData;
638 osrfHash* pcache = osrfHashGet(cache, "pcache");
640 return osrfHashGet( pcache, perm );
649 @brief Save the user's login in the userData for the current application session.
650 @param ctx Pointer to the method context.
651 @param user_login Pointer to the user login object to be cached (we cache the original,
654 If @a user_login is NULL, remove the user login if one is already cached.
656 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
657 if( ctx && ctx->session ) {
658 osrfAppSession* session = ctx->session;
660 osrfHash* cache = session->userData;
662 // If the session doesn't already have a hash, create one. Make sure
663 // that the application session frees the hash when it terminates.
665 cache = initSessionCache( ctx );
668 osrfHashSet( cache, user_login, "user_login" );
670 osrfHashRemove( cache, "user_login" );
675 @brief Get the user login object for the current application session, if any.
676 @param ctx Pointer to the method context.
677 @return Pointer to the user login object if found; otherwise NULL.
679 The user login object was returned from the authentication server, and then cached so
680 we don't have to call the authentication server again for the same user.
682 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
683 if( ctx && ctx->session && ctx->session->userData )
684 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
690 @brief Save a copy of an authkey in the userData of the current application session.
691 @param ctx Pointer to the method context.
692 @param authkey The authkey to be saved.
694 If @a authkey is NULL, remove the authkey if one is already cached.
696 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
697 if( ctx && ctx->session && authkey ) {
698 osrfAppSession* session = ctx->session;
699 osrfHash* cache = session->userData;
701 // If the session doesn't already have a hash, create one. Make sure
702 // that the application session frees the hash when it terminates.
704 cache = initSessionCache( ctx );
706 // Save the transaction id in the hash, with the key "xact_id"
707 if( authkey && *authkey )
708 osrfHashSet( cache, strdup( authkey ), "authkey" );
710 osrfHashRemove( cache, "authkey" );
715 @brief Reset the login timeout.
716 @param authkey The authentication key for the current login session.
717 @param now The current time.
718 @return Zero if successful, or 1 if not.
720 Tell the authentication server to reset the timeout so that the login session won't
721 expire for a while longer.
723 We could dispense with the @a now parameter by calling time(). But we just called
724 time() in order to decide whether to reset the timeout, so we might as well reuse
725 the result instead of calling time() again.
727 static int reset_timeout( const char* authkey, time_t now ) {
728 jsonObject* auth_object = jsonNewObject( authkey );
730 // Ask the authentication server to reset the timeout. It returns an event
731 // indicating success or failure.
732 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
733 "open-ils.auth.session.reset_timeout", auth_object );
734 jsonObjectFree( auth_object );
736 if( !result || result->type != JSON_HASH ) {
737 osrfLogError( OSRF_LOG_MARK,
738 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
739 jsonObjectFree( result );
740 return 1; // Not the right sort of object returned
743 const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
744 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
745 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
746 jsonObjectFree( result );
747 return 1; // Return code from method not available
750 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
751 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
753 desc = "(No reason available)"; // failsafe; shouldn't happen
754 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
755 jsonObjectFree( result );
759 // Revise our local proxy for the timeout deadline
760 // by a smallish fraction of the timeout interval
761 const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
763 timeout = "1"; // failsafe; shouldn't happen
764 time_next_reset = now + atoi( timeout ) / 15;
766 jsonObjectFree( result );
767 return 0; // Successfully reset timeout
771 @brief Get the authkey string for the current application session, if any.
772 @param ctx Pointer to the method context.
773 @return Pointer to the cached authkey if found; otherwise NULL.
775 If present, the authkey string was cached from a previous method call.
777 static const char* getAuthkey( osrfMethodContext* ctx ) {
778 if( ctx && ctx->session && ctx->session->userData ) {
779 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
780 // LFW recent changes mean the userData hash gets set up earlier, but
781 // doesn't necessarily have an authkey yet
785 // Possibly reset the authentication timeout to keep the login alive. We do so
786 // no more than once per method call, and not at all if it has been only a short
787 // time since the last reset.
789 // Here we reset explicitly, if at all. We also implicitly reset the timeout
790 // whenever we call the "open-ils.auth.session.retrieve" method.
791 if( timeout_needs_resetting ) {
792 time_t now = time( NULL );
793 if( now >= time_next_reset && reset_timeout( authkey, now ) )
794 authkey = NULL; // timeout has apparently expired already
797 timeout_needs_resetting = 0;
805 @brief Implement the transaction.begin method.
806 @param ctx Pointer to the method context.
807 @return Zero if successful, or -1 upon error.
809 Start a transaction. Save a transaction ID for future reference.
812 - authkey (PCRUD only)
814 Return to client: Transaction ID
816 int beginTransaction( osrfMethodContext* ctx ) {
817 if(osrfMethodVerifyContext( ctx )) {
818 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
822 if( enforce_pcrud ) {
823 timeout_needs_resetting = 1;
824 const jsonObject* user = verifyUserPCRUD( ctx );
829 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
832 int errnum = dbi_conn_error( writehandle, &msg );
833 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
834 modulename, errnum, msg ? msg : "(No description available)" );
835 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
836 "osrfMethodException", ctx->request, "Error starting transaction" );
837 if( !oilsIsDBConnected( writehandle ))
838 osrfAppSessionPanic( ctx->session );
841 dbi_result_free( result );
843 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
844 osrfAppRespondComplete( ctx, ret );
845 jsonObjectFree( ret );
851 @brief Implement the savepoint.set method.
852 @param ctx Pointer to the method context.
853 @return Zero if successful, or -1 if not.
855 Issue a SAVEPOINT to the database server.
858 - authkey (PCRUD only)
861 Return to client: Savepoint name
863 int setSavepoint( osrfMethodContext* ctx ) {
864 if(osrfMethodVerifyContext( ctx )) {
865 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
870 if( enforce_pcrud ) {
872 timeout_needs_resetting = 1;
873 const jsonObject* user = verifyUserPCRUD( ctx );
878 // Verify that a transaction is pending
879 const char* trans_id = getXactId( ctx );
880 if( NULL == trans_id ) {
881 osrfAppSessionStatus(
883 OSRF_STATUS_INTERNALSERVERERROR,
884 "osrfMethodException",
886 "No active transaction -- required for savepoints"
891 // Get the savepoint name from the method params
892 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
895 osrfLogWarning(OSRF_LOG_MARK, "savepoint.set called with no name");
899 char *safeSpName = _sanitize_savepoint_name( spName );
901 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", safeSpName );
905 int errnum = dbi_conn_error( writehandle, &msg );
908 "%s: Error creating savepoint %s in transaction %s: %d %s",
913 msg ? msg : "(No description available)"
915 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
916 "osrfMethodException", ctx->request, "Error creating savepoint" );
917 if( !oilsIsDBConnected( writehandle ))
918 osrfAppSessionPanic( ctx->session );
921 dbi_result_free( result );
922 jsonObject* ret = jsonNewObject( spName );
923 osrfAppRespondComplete( ctx, ret );
924 jsonObjectFree( ret );
930 @brief Implement the savepoint.release method.
931 @param ctx Pointer to the method context.
932 @return Zero if successful, or -1 if not.
934 Issue a RELEASE SAVEPOINT to the database server.
937 - authkey (PCRUD only)
940 Return to client: Savepoint name
942 int releaseSavepoint( osrfMethodContext* ctx ) {
943 if(osrfMethodVerifyContext( ctx )) {
944 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
949 if( enforce_pcrud ) {
951 timeout_needs_resetting = 1;
952 const jsonObject* user = verifyUserPCRUD( ctx );
957 // Verify that a transaction is pending
958 const char* trans_id = getXactId( ctx );
959 if( NULL == trans_id ) {
960 osrfAppSessionStatus(
962 OSRF_STATUS_INTERNALSERVERERROR,
963 "osrfMethodException",
965 "No active transaction -- required for savepoints"
970 // Get the savepoint name from the method params
971 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
974 osrfLogWarning(OSRF_LOG_MARK, "savepoint.release called with no name");
978 char *safeSpName = _sanitize_savepoint_name( spName );
980 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", safeSpName );
984 int errnum = dbi_conn_error( writehandle, &msg );
987 "%s: Error releasing savepoint %s in transaction %s: %d %s",
992 msg ? msg : "(No description available)"
994 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
995 "osrfMethodException", ctx->request, "Error releasing savepoint" );
996 if( !oilsIsDBConnected( writehandle ))
997 osrfAppSessionPanic( ctx->session );
1000 dbi_result_free( result );
1001 jsonObject* ret = jsonNewObject( spName );
1002 osrfAppRespondComplete( ctx, ret );
1003 jsonObjectFree( ret );
1009 @brief Implement the savepoint.rollback method.
1010 @param ctx Pointer to the method context.
1011 @return Zero if successful, or -1 if not.
1013 Issue a ROLLBACK TO SAVEPOINT to the database server.
1016 - authkey (PCRUD only)
1019 Return to client: Savepoint name
1021 int rollbackSavepoint( osrfMethodContext* ctx ) {
1022 if(osrfMethodVerifyContext( ctx )) {
1023 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1028 if( enforce_pcrud ) {
1030 timeout_needs_resetting = 1;
1031 const jsonObject* user = verifyUserPCRUD( ctx );
1036 // Verify that a transaction is pending
1037 const char* trans_id = getXactId( ctx );
1038 if( NULL == trans_id ) {
1039 osrfAppSessionStatus(
1041 OSRF_STATUS_INTERNALSERVERERROR,
1042 "osrfMethodException",
1044 "No active transaction -- required for savepoints"
1049 // Get the savepoint name from the method params
1050 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1053 osrfLogWarning(OSRF_LOG_MARK, "savepoint.rollback called with no name");
1057 char *safeSpName = _sanitize_savepoint_name( spName );
1059 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", safeSpName );
1063 int errnum = dbi_conn_error( writehandle, &msg );
1066 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
1071 msg ? msg : "(No description available)"
1073 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1074 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1075 if( !oilsIsDBConnected( writehandle ))
1076 osrfAppSessionPanic( ctx->session );
1079 dbi_result_free( result );
1080 jsonObject* ret = jsonNewObject( spName );
1081 osrfAppRespondComplete( ctx, ret );
1082 jsonObjectFree( ret );
1088 @brief Implement the transaction.commit method.
1089 @param ctx Pointer to the method context.
1090 @return Zero if successful, or -1 if not.
1092 Issue a COMMIT to the database server.
1095 - authkey (PCRUD only)
1097 Return to client: Transaction ID.
1099 int commitTransaction( osrfMethodContext* ctx ) {
1100 if(osrfMethodVerifyContext( ctx )) {
1101 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1105 if( enforce_pcrud ) {
1106 timeout_needs_resetting = 1;
1107 const jsonObject* user = verifyUserPCRUD( ctx );
1112 // Verify that a transaction is pending
1113 const char* trans_id = getXactId( ctx );
1114 if( NULL == trans_id ) {
1115 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1116 "osrfMethodException", ctx->request, "No active transaction to commit" );
1120 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1123 int errnum = dbi_conn_error( writehandle, &msg );
1124 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1125 modulename, errnum, msg ? msg : "(No description available)" );
1126 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1127 "osrfMethodException", ctx->request, "Error committing transaction" );
1128 if( !oilsIsDBConnected( writehandle ))
1129 osrfAppSessionPanic( ctx->session );
1132 dbi_result_free( result );
1133 jsonObject* ret = jsonNewObject( trans_id );
1134 osrfAppRespondComplete( ctx, ret );
1135 jsonObjectFree( ret );
1142 @brief Implement the transaction.rollback method.
1143 @param ctx Pointer to the method context.
1144 @return Zero if successful, or -1 if not.
1146 Issue a ROLLBACK to the database server.
1149 - authkey (PCRUD only)
1151 Return to client: Transaction ID
1153 int rollbackTransaction( osrfMethodContext* ctx ) {
1154 if( osrfMethodVerifyContext( ctx )) {
1155 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1159 if( enforce_pcrud ) {
1160 timeout_needs_resetting = 1;
1161 const jsonObject* user = verifyUserPCRUD( ctx );
1166 // Verify that a transaction is pending
1167 const char* trans_id = getXactId( ctx );
1168 if( NULL == trans_id ) {
1169 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1170 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1174 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1177 int errnum = dbi_conn_error( writehandle, &msg );
1178 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1179 modulename, errnum, msg ? msg : "(No description available)" );
1180 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1181 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1182 if( !oilsIsDBConnected( writehandle ))
1183 osrfAppSessionPanic( ctx->session );
1186 dbi_result_free( result );
1187 jsonObject* ret = jsonNewObject( trans_id );
1188 osrfAppRespondComplete( ctx, ret );
1189 jsonObjectFree( ret );
1196 @brief Implement the "search" method.
1197 @param ctx Pointer to the method context.
1198 @return Zero if successful, or -1 if not.
1201 - authkey (PCRUD only)
1202 - WHERE clause, as jsonObject
1203 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1205 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1206 Optionally flesh linked fields.
1208 int doSearch( osrfMethodContext* ctx ) {
1209 if( osrfMethodVerifyContext( ctx )) {
1210 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1215 timeout_needs_resetting = 1;
1217 jsonObject* where_clause;
1218 jsonObject* rest_of_query;
1220 if( enforce_pcrud ) {
1221 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1222 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1224 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1225 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1228 if( !where_clause ) {
1229 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1233 // Get the class metadata
1234 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1235 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1239 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1241 osrfAppRespondComplete( ctx, NULL );
1245 // doFieldmapperSearch() now takes care of our responding for us
1246 // // Return each row to the client
1247 // jsonObject* cur = 0;
1248 // unsigned long res_idx = 0;
1250 // while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1251 // // We used to discard based on perms here, but now that's
1252 // // inside doFieldmapperSearch()
1253 // osrfAppRespond( ctx, cur );
1256 jsonObjectFree( obj );
1258 osrfAppRespondComplete( ctx, NULL );
1263 @brief Implement the "id_list" method.
1264 @param ctx Pointer to the method context.
1265 @param err Pointer through which to return an error code.
1266 @return Zero if successful, or -1 if not.
1269 - authkey (PCRUD only)
1270 - WHERE clause, as jsonObject
1271 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1273 Return to client: The primary key values for all rows of the relevant class that
1274 satisfy a specified WHERE clause.
1276 This method relies on the assumption that every class has a primary key consisting of
1279 int doIdList( osrfMethodContext* ctx ) {
1280 if( osrfMethodVerifyContext( ctx )) {
1281 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1286 timeout_needs_resetting = 1;
1288 jsonObject* where_clause;
1289 jsonObject* rest_of_query;
1291 // We use the where clause without change. But we need to massage the rest of the
1292 // query, so we work with a copy of it instead of modifying the original.
1294 if( enforce_pcrud ) {
1295 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1296 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1298 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1299 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1302 if( !where_clause ) {
1303 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1307 // Eliminate certain SQL clauses, if present.
1308 if( rest_of_query ) {
1309 jsonObjectRemoveKey( rest_of_query, "select" );
1310 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1311 jsonObjectRemoveKey( rest_of_query, "flesh" );
1312 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1314 rest_of_query = jsonNewObjectType( JSON_HASH );
1317 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1319 // Get the class metadata
1320 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1321 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1323 // Build a SELECT list containing just the primary key,
1324 // i.e. like { "classname":["keyname"] }
1325 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1327 // Load array with name of primary key
1328 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1329 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1330 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1332 jsonObjectSetKey( rest_of_query, "select", select_clause );
1337 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1339 jsonObjectFree( rest_of_query );
1341 osrfAppRespondComplete( ctx, NULL );
1345 // Return each primary key value to the client
1347 unsigned long res_idx = 0;
1348 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1349 // We used to discard based on perms here, but now that's
1350 // inside doFieldmapperSearch()
1351 osrfAppRespond( ctx,
1352 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1355 jsonObjectFree( obj );
1356 osrfAppRespondComplete( ctx, NULL );
1361 @brief Verify that we have a valid class reference.
1362 @param ctx Pointer to the method context.
1363 @param param Pointer to the method parameters.
1364 @return 1 if the class reference is valid, or zero if it isn't.
1366 The class of the method params must match the class to which the method id devoted.
1367 For PCRUD there are additional restrictions.
1369 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1371 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1372 osrfHash* class = osrfHashGet( method_meta, "class" );
1374 // Compare the method's class to the parameters' class
1375 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1377 // Oops -- they don't match. Complain.
1378 growing_buffer* msg = buffer_init( 128 );
1381 "%s: %s method for type %s was passed a %s",
1383 osrfHashGet( method_meta, "methodtype" ),
1384 osrfHashGet( class, "classname" ),
1385 param->classname ? param->classname : "(null)"
1388 char* m = buffer_release( msg );
1389 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1397 return verifyObjectPCRUD( ctx, class, param, 1 );
1403 @brief (PCRUD only) Verify that the user is properly logged in.
1404 @param ctx Pointer to the method context.
1405 @return If the user is logged in, a pointer to the user object from the authentication
1406 server; otherwise NULL.
1408 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1410 // Get the authkey (the first method parameter)
1411 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1413 // See if we have the same authkey, and a user object,
1414 // locally cached from a previous call
1415 const char* cached_authkey = getAuthkey( ctx );
1416 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1417 const jsonObject* cached_user = getUserLogin( ctx );
1422 // We have no matching authentication data in the cache. Authenticate from scratch.
1423 jsonObject* auth_object = jsonNewObject( auth );
1425 // Fetch the user object from the authentication server
1426 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1428 jsonObjectFree( auth_object );
1430 if( !user->classname || strcmp(user->classname, "au" )) {
1432 growing_buffer* msg = buffer_init( 128 );
1435 "%s: permacrud received a bad auth token: %s",
1440 char* m = buffer_release( msg );
1441 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1445 jsonObjectFree( user );
1447 } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
1448 // Failed to set audit information - But note that write_audit_info already set error information.
1449 jsonObjectFree( user );
1453 setUserLogin( ctx, user );
1454 setAuthkey( ctx, auth );
1456 // Allow ourselves up to a second before we have to reset the login timeout.
1457 // It would be nice to use some fraction of the timeout interval enforced by the
1458 // authentication server, but that value is not readily available at this point.
1459 // Instead, we use a conservative default interval.
1460 time_next_reset = time( NULL ) + 1;
1466 @brief For PCRUD: Determine whether the current user may access the current row.
1467 @param ctx Pointer to the method context.
1468 @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1469 @param obj Pointer to the row being potentially accessed.
1470 @return 1 if access is permitted, or 0 if it isn't.
1472 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1474 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1476 dbhandle = writehandle;
1478 // Figure out what class and method are involved
1479 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1480 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1483 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1484 if (rs_size_from_hash) {
1485 rs_size = *rs_size_from_hash;
1486 osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1490 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1491 // contexts we will do another lookup of the current row, even if we already have a
1492 // previously fetched row image, because the row image in hand may not include the
1493 // foreign key(s) that we need.
1495 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1496 // but they aren't implemented yet.
1499 if( *method_type == 's' || *method_type == 'i' ) {
1500 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1502 } else if( *method_type == 'u' || *method_type == 'd' ) {
1503 fetch = 1; // MUST go to the db for the object for update and delete
1506 // Get the appropriate permacrud entry from the IDL, depending on method type
1507 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1509 // No permacrud for this method type on this class
1511 growing_buffer* msg = buffer_init( 128 );
1514 "%s: %s on class %s has no permacrud IDL entry",
1516 osrfHashGet( method_metadata, "methodtype" ),
1517 osrfHashGet( class, "classname" )
1520 char* m = buffer_release( msg );
1521 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1522 "osrfMethodException", ctx->request, m );
1529 // Get the user id, and make sure the user is logged in
1530 const jsonObject* user = verifyUserPCRUD( ctx );
1532 return 0; // Not logged in? No access.
1534 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1536 // Get a list of permissions from the permacrud entry.
1537 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1538 if( permission->size == 0 ) {
1541 "No permissions required for this action (class %s), passing through",
1542 osrfHashGet(class, "classname")
1547 // Build a list of org units that own the row. This is fairly convoluted because there
1548 // are several different ways that an org unit may own the row, as defined by the
1551 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1552 // identifying an owning org_unit..
1553 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1555 // Foreign context adds a layer of indirection. The row points to some other row that
1556 // an org unit may own. The "jump" attribute, if present, adds another layer of
1558 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1560 // The following string array stores the list of org units. (We don't have a thingie
1561 // for storing lists of integers, so we fake it with a list of strings.)
1562 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1565 const char* pkey_value = NULL;
1566 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1567 // If the global_required attribute is present and true, then the only owning
1568 // org unit is the root org unit, i.e. the one with no parent.
1569 osrfLogDebug( OSRF_LOG_MARK,
1570 "global-level permissions required, fetching top of the org tree" );
1572 // no need to check perms for org tree root retrieval
1573 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1574 // check for perm at top of org tree
1575 const char* org_tree_root_id = org_tree_root( ctx );
1576 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1578 if( org_tree_root_id ) {
1579 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1580 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1582 osrfStringArrayFree( context_org_array );
1587 // If the global_required attribute is absent or false, then we look for
1588 // local and/or foreign context. In order to find the relevant foreign
1589 // keys, we must either read the relevant row from the database, or look at
1590 // the image of the row that we already have in memory.
1592 // Even if we have an image of the row in memory, that image may not include the
1593 // foreign key column(s) that we need. So whenever possible, we do a fresh read
1594 // of the row to make sure that we have what we need.
1596 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1597 "fetching context org ids" );
1598 const char* pkey = osrfHashGet( class, "primarykey" );
1599 jsonObject *param = NULL;
1602 // There is no primary key, so we can't do a fresh lookup. Use the row
1603 // image that we already have. If it doesn't have everything we need, too bad.
1605 param = jsonObjectClone( obj );
1606 osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1607 } else if( obj->classname ) {
1608 pkey_value = oilsFMGetStringConst( obj, pkey );
1610 param = jsonObjectClone( obj );
1611 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1614 pkey_value = jsonObjectGetString( obj );
1616 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1617 "of %s and retrieving from the database", pkey_value );
1621 // Fetch the row so that we can look at the foreign key(s)
1622 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1623 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1624 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1625 jsonObjectFree( _tmp_params );
1626 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1628 param = jsonObjectExtractIndex( _list, 0 );
1629 jsonObjectFree( _list );
1633 // The row doesn't exist. Complain, and deny access.
1634 osrfLogDebug( OSRF_LOG_MARK,
1635 "Object not found in the database with primary key %s of %s",
1638 growing_buffer* msg = buffer_init( 128 );
1641 "%s: no object found with primary key %s of %s",
1647 char* m = buffer_release( msg );
1648 osrfAppSessionStatus(
1650 OSRF_STATUS_INTERNALSERVERERROR,
1651 "osrfMethodException",
1660 if( local_context && local_context->size > 0 ) {
1661 // The IDL provides a list of column names for the foreign keys denoting
1662 // local context, i.e. columns identifying owing org units directly. Look up
1663 // the value of each one, and if it isn't null, add it to the list of org units.
1664 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1665 local_context->size );
1667 const char* lcontext = NULL;
1668 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1669 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1670 if( fkey_value ) { // if not null
1671 osrfStringArrayAdd( context_org_array, fkey_value );
1674 "adding class-local field %s (value: %s) to the context org list",
1676 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1682 if( foreign_context ) {
1683 unsigned long class_count = osrfHashGetCount( foreign_context );
1684 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1686 if( class_count > 0 ) {
1688 // The IDL provides a list of foreign key columns pointing to rows that
1689 // an org unit may own. Follow each link, identify the owning org unit,
1690 // and add it to the list.
1691 osrfHash* fcontext = NULL;
1692 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1693 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1694 // For each class to which a foreign key points:
1695 const char* class_name = osrfHashIteratorKey( class_itr );
1696 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1700 "%d foreign context fields(s) specified for class %s",
1701 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1705 // Get the name of the key field in the foreign table
1706 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1708 // Get the value of the foreign key pointing to the foreign table
1709 char* foreign_pkey_value =
1710 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1711 if( !foreign_pkey_value )
1712 continue; // Foreign key value is null; skip it
1714 // Look up the row to which the foreign key points
1715 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1717 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1718 jsonObject* _list = doFieldmapperSearch(
1719 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1720 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1722 jsonObject* _fparam = NULL;
1723 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1724 _fparam = jsonObjectExtractIndex( _list, 0 );
1726 jsonObjectFree( _tmp_params );
1727 jsonObjectFree( _list );
1729 // At this point _fparam either points to the row identified by the
1730 // foreign key, or it's NULL (no such row found).
1732 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1734 const char* bad_class = NULL; // For noting failed lookups
1736 bad_class = class_name; // Referenced row not found
1737 else if( jump_list ) {
1738 // Follow a chain of rows, linked by foreign keys, to find an owner
1739 const char* flink = NULL;
1741 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1742 // For each entry in the jump list. Each entry (i.e. flink) is
1743 // the name of a foreign key column in the current row.
1745 // From the IDL, get the linkage information for the next jump
1746 osrfHash* foreign_link_hash =
1747 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1749 // Get the class metadata for the class
1750 // to which the foreign key points
1751 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1752 osrfHashGet( foreign_link_hash, "class" ));
1754 // Get the name of the referenced key of that class
1755 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1757 // Get the value of the foreign key pointing to that class
1758 free( foreign_pkey_value );
1759 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1760 if( !foreign_pkey_value )
1761 break; // Foreign key is null; quit looking
1763 // Build a WHERE clause for the lookup
1764 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1767 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1768 _tmp_params, NULL, &err );
1770 // Get the resulting row
1771 jsonObjectFree( _fparam );
1772 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1773 _fparam = jsonObjectExtractIndex( _list, 0 );
1775 // Referenced row not found
1777 bad_class = osrfHashGet( foreign_link_hash, "class" );
1780 jsonObjectFree( _tmp_params );
1781 jsonObjectFree( _list );
1787 // We had a foreign key pointing to such-and-such a row, but then
1788 // we couldn't fetch that row. The data in the database are in an
1789 // inconsistent state; the database itself may even be corrupted.
1790 growing_buffer* msg = buffer_init( 128 );
1793 "%s: no object of class %s found with primary key %s of %s",
1797 foreign_pkey_value ? foreign_pkey_value : "(null)"
1800 char* m = buffer_release( msg );
1801 osrfAppSessionStatus(
1803 OSRF_STATUS_INTERNALSERVERERROR,
1804 "osrfMethodException",
1810 osrfHashIteratorFree( class_itr );
1811 free( foreign_pkey_value );
1812 jsonObjectFree( param );
1817 free( foreign_pkey_value );
1820 // Examine each context column of the foreign row,
1821 // and add its value to the list of org units.
1823 const char* foreign_field = NULL;
1824 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1825 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1826 osrfStringArrayAdd( context_org_array,
1827 oilsFMGetStringConst( _fparam, foreign_field ));
1828 osrfLogDebug( OSRF_LOG_MARK,
1829 "adding foreign class %s field %s (value: %s) "
1830 "to the context org list",
1833 osrfStringArrayGetString(
1834 context_org_array, context_org_array->size - 1 )
1838 jsonObjectFree( _fparam );
1842 osrfHashIteratorFree( class_itr );
1846 jsonObjectFree( param );
1849 const char* context_org = NULL;
1850 const char* perm = NULL;
1853 // For every combination of permission and context org unit: call a stored procedure
1854 // to determine if the user has this permission in the context of this org unit.
1855 // If the answer is yes at any point, then we're done, and the user has permission.
1856 // In other words permissions are additive.
1858 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1861 osrfStringArray* pcache = NULL;
1862 if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
1863 pcache = getPermLocationCache(ctx, perm);
1866 pcache = osrfNewStringArray(0);
1868 result = dbi_conn_queryf(
1870 "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
1878 "Received a result for permission [%s] for user %d",
1883 if( dbi_result_first_row( result )) {
1885 jsonObject* return_val = oilsMakeJSONFromResult( result );
1886 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
1887 jsonObjectFree( return_val );
1888 } while( dbi_result_next_row( result ));
1890 setPermLocationCache(ctx, perm, pcache);
1893 dbi_result_free( result );
1899 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1901 if (rs_size > perm_at_threshold) {
1902 if (osrfStringArrayContains( pcache, context_org )) {
1911 "Checking object permission [%s] for user %d "
1912 "on object %s (class %s) at org %d",
1916 osrfHashGet( class, "classname" ),
1920 result = dbi_conn_queryf(
1922 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1925 osrfHashGet( class, "classname" ),
1933 "Received a result for object permission [%s] "
1934 "for user %d on object %s (class %s) at org %d",
1938 osrfHashGet( class, "classname" ),
1942 if( dbi_result_first_row( result )) {
1943 jsonObject* return_val = oilsMakeJSONFromResult( result );
1944 const char* has_perm = jsonObjectGetString(
1945 jsonObjectGetKeyConst( return_val, "has_perm" ));
1949 "Status of object permission [%s] for user %d "
1950 "on object %s (class %s) at org %d is %s",
1954 osrfHashGet(class, "classname"),
1959 if( *has_perm == 't' )
1961 jsonObjectFree( return_val );
1964 dbi_result_free( result );
1969 int errnum = dbi_conn_error( writehandle, &msg );
1970 osrfLogWarning( OSRF_LOG_MARK,
1971 "Unable to call check object permissions: %d, %s",
1972 errnum, msg ? msg : "(No description available)" );
1973 if( !oilsIsDBConnected( writehandle ))
1974 osrfAppSessionPanic( ctx->session );
1978 if (rs_size > perm_at_threshold) break;
1980 osrfLogDebug( OSRF_LOG_MARK,
1981 "Checking non-object permission [%s] for user %d at org %d",
1982 perm, userid, atoi(context_org) );
1983 result = dbi_conn_queryf(
1985 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1992 osrfLogDebug( OSRF_LOG_MARK,
1993 "Received a result for permission [%s] for user %d at org %d",
1994 perm, userid, atoi( context_org ));
1995 if( dbi_result_first_row( result )) {
1996 jsonObject* return_val = oilsMakeJSONFromResult( result );
1997 const char* has_perm = jsonObjectGetString(
1998 jsonObjectGetKeyConst( return_val, "has_perm" ));
1999 osrfLogDebug( OSRF_LOG_MARK,
2000 "Status of permission [%s] for user %d at org %d is [%s]",
2001 perm, userid, atoi( context_org ), has_perm );
2002 if( *has_perm == 't' )
2004 jsonObjectFree( return_val );
2007 dbi_result_free( result );
2012 int errnum = dbi_conn_error( writehandle, &msg );
2013 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
2014 errnum, msg ? msg : "(No description available)" );
2015 if( !oilsIsDBConnected( writehandle ))
2016 osrfAppSessionPanic( ctx->session );
2025 osrfStringArrayFree( context_org_array );
2031 @brief Look up the root of the org_unit tree.
2032 @param ctx Pointer to the method context.
2033 @return The id of the root org unit, as a character string.
2035 Query actor.org_unit where parent_ou is null, and return the id as a string.
2037 This function assumes that there is only one root org unit, i.e. that we
2038 have a single tree, not a forest.
2040 The calling code is responsible for freeing the returned string.
2042 static const char* org_tree_root( osrfMethodContext* ctx ) {
2044 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
2045 static time_t last_lookup_time = 0;
2046 time_t current_time = time( NULL );
2048 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2049 // We successfully looked this up less than an hour ago.
2050 // It's not likely to have changed since then.
2051 return strdup( cached_root_id );
2053 last_lookup_time = current_time;
2056 jsonObject* where_clause = single_hash( "parent_ou", NULL );
2057 jsonObject* result = doFieldmapperSearch(
2058 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2059 jsonObjectFree( where_clause );
2061 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2064 jsonObjectFree( result );
2066 growing_buffer* msg = buffer_init( 128 );
2067 OSRF_BUFFER_ADD( msg, modulename );
2068 OSRF_BUFFER_ADD( msg,
2069 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2071 char* m = buffer_release( msg );
2072 osrfAppSessionStatus( ctx->session,
2073 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2076 cached_root_id[ 0 ] = '\0';
2080 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2081 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2083 strcpy( cached_root_id, root_org_unit_id );
2084 jsonObjectFree( result );
2085 return cached_root_id;
2089 @brief Create a JSON_HASH with a single key/value pair.
2090 @param key The key of the key/value pair.
2091 @param value the value of the key/value pair.
2092 @return Pointer to a newly created jsonObject of type JSON_HASH.
2094 The value of the key/value is either a string or (if @a value is NULL) a null.
2096 static jsonObject* single_hash( const char* key, const char* value ) {
2098 if( ! key ) key = "";
2100 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2101 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2106 int doCreate( osrfMethodContext* ctx ) {
2107 if(osrfMethodVerifyContext( ctx )) {
2108 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2113 timeout_needs_resetting = 1;
2115 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2116 jsonObject* target = NULL;
2117 jsonObject* options = NULL;
2119 if( enforce_pcrud ) {
2120 target = jsonObjectGetIndex( ctx->params, 1 );
2121 options = jsonObjectGetIndex( ctx->params, 2 );
2123 target = jsonObjectGetIndex( ctx->params, 0 );
2124 options = jsonObjectGetIndex( ctx->params, 1 );
2127 if( !verifyObjectClass( ctx, target )) {
2128 osrfAppRespondComplete( ctx, NULL );
2132 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2134 const char* trans_id = getXactId( ctx );
2136 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2138 osrfAppSessionStatus(
2140 OSRF_STATUS_BADREQUEST,
2141 "osrfMethodException",
2143 "No active transaction -- required for CREATE"
2145 osrfAppRespondComplete( ctx, NULL );
2149 // The following test is harmless but redundant. If a class is
2150 // readonly, we don't register a create method for it.
2151 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2152 osrfAppSessionStatus(
2154 OSRF_STATUS_BADREQUEST,
2155 "osrfMethodException",
2157 "Cannot INSERT readonly class"
2159 osrfAppRespondComplete( ctx, NULL );
2163 // Set the last_xact_id
2164 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2166 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2167 trans_id, target->classname, index);
2168 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2171 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2173 dbhandle = writehandle;
2175 osrfHash* fields = osrfHashGet( meta, "fields" );
2176 char* pkey = osrfHashGet( meta, "primarykey" );
2177 char* seq = osrfHashGet( meta, "sequence" );
2179 growing_buffer* table_buf = buffer_init( 128 );
2180 growing_buffer* col_buf = buffer_init( 128 );
2181 growing_buffer* val_buf = buffer_init( 128 );
2183 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2184 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2185 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2186 buffer_add( val_buf,"VALUES (" );
2190 osrfHash* field = NULL;
2191 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2192 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2194 const char* field_name = osrfHashIteratorKey( field_itr );
2196 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2199 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2202 if( field_object && field_object->classname ) {
2203 value = oilsFMGetString(
2205 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2207 } else if( field_object && JSON_BOOL == field_object->type ) {
2208 if( jsonBoolIsTrue( field_object ) )
2209 value = strdup( "t" );
2211 value = strdup( "f" );
2213 value = jsonObjectToSimpleString( field_object );
2219 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2220 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2223 buffer_add( col_buf, field_name );
2225 if( !field_object || field_object->type == JSON_NULL ) {
2226 buffer_add( val_buf, "DEFAULT" );
2228 } else if( !strcmp( get_primitive( field ), "number" )) {
2229 const char* numtype = get_datatype( field );
2230 if( !strcmp( numtype, "INT8" )) {
2231 buffer_fadd( val_buf, "%lld", atoll( value ));
2233 } else if( !strcmp( numtype, "INT" )) {
2234 buffer_fadd( val_buf, "%d", atoi( value ));
2236 } else if( !strcmp( numtype, "NUMERIC" )) {
2237 buffer_fadd( val_buf, "%f", atof( value ));
2240 if( dbi_conn_quote_string( writehandle, &value )) {
2241 OSRF_BUFFER_ADD( val_buf, value );
2244 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2245 osrfAppSessionStatus(
2247 OSRF_STATUS_INTERNALSERVERERROR,
2248 "osrfMethodException",
2250 "Error quoting string -- please see the error log for more details"
2253 buffer_free( table_buf );
2254 buffer_free( col_buf );
2255 buffer_free( val_buf );
2256 osrfAppRespondComplete( ctx, NULL );
2264 osrfHashIteratorFree( field_itr );
2266 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2267 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2269 char* table_str = buffer_release( table_buf );
2270 char* col_str = buffer_release( col_buf );
2271 char* val_str = buffer_release( val_buf );
2272 growing_buffer* sql = buffer_init( 128 );
2273 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2278 char* query = buffer_release( sql );
2280 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2282 jsonObject* obj = NULL;
2285 dbi_result result = dbi_conn_query( writehandle, query );
2287 obj = jsonNewObject( NULL );
2289 int errnum = dbi_conn_error( writehandle, &msg );
2292 "%s ERROR inserting %s object using query [%s]: %d %s",
2294 osrfHashGet(meta, "fieldmapper"),
2297 msg ? msg : "(No description available)"
2299 osrfAppSessionStatus(
2301 OSRF_STATUS_INTERNALSERVERERROR,
2302 "osrfMethodException",
2304 "INSERT error -- please see the error log for more details"
2306 if( !oilsIsDBConnected( writehandle ))
2307 osrfAppSessionPanic( ctx->session );
2310 dbi_result_free( result );
2312 char* id = oilsFMGetString( target, pkey );
2314 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2315 growing_buffer* _id = buffer_init( 10 );
2316 buffer_fadd( _id, "%lld", new_id );
2317 id = buffer_release( _id );
2320 // Find quietness specification, if present
2321 const char* quiet_str = NULL;
2323 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2325 quiet_str = jsonObjectGetString( quiet_obj );
2328 if( str_is_true( quiet_str )) { // if quietness is specified
2329 obj = jsonNewObject( id );
2333 // Fetch the row that we just inserted, so that we can return it to the client
2334 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2335 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2338 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2342 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2344 jsonObjectFree( list );
2345 jsonObjectFree( where_clause );
2352 osrfAppRespondComplete( ctx, obj );
2353 jsonObjectFree( obj );
2358 @brief Implement the retrieve method.
2359 @param ctx Pointer to the method context.
2360 @param err Pointer through which to return an error code.
2361 @return If successful, a pointer to the result to be returned to the client;
2364 From the method's class, fetch a row with a specified value in the primary key. This
2365 method relies on the database design convention that a primary key consists of a single
2369 - authkey (PCRUD only)
2370 - value of the primary key for the desired row, for building the WHERE clause
2371 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2373 Return to client: One row from the query.
2375 int doRetrieve( osrfMethodContext* ctx ) {
2376 if(osrfMethodVerifyContext( ctx )) {
2377 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2382 timeout_needs_resetting = 1;
2387 if( enforce_pcrud ) {
2392 // Get the class metadata
2393 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2395 // Get the value of the primary key, from a method parameter
2396 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2400 "%s retrieving %s object with primary key value of %s",
2402 osrfHashGet( class_def, "fieldmapper" ),
2403 jsonObjectGetString( id_obj )
2406 // Build a WHERE clause based on the key value
2407 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2410 osrfHashGet( class_def, "primarykey" ), // name of key column
2411 jsonObjectClone( id_obj ) // value of key column
2414 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2418 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2420 jsonObjectFree( where_clause );
2422 osrfAppRespondComplete( ctx, NULL );
2426 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2427 jsonObjectFree( list );
2429 if( enforce_pcrud ) {
2430 // no result, skip this entirely
2431 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2432 jsonObjectFree( obj );
2434 growing_buffer* msg = buffer_init( 128 );
2435 OSRF_BUFFER_ADD( msg, modulename );
2436 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2438 char* m = buffer_release( msg );
2439 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2443 osrfAppRespondComplete( ctx, NULL );
2448 // doFieldmapperSearch() now does the responding for us
2449 //osrfAppRespondComplete( ctx, obj );
2450 osrfAppRespondComplete( ctx, NULL );
2452 jsonObjectFree( obj );
2457 @brief Translate a numeric value to a string representation for the database.
2458 @param field Pointer to the IDL field definition.
2459 @param value Pointer to a jsonObject holding the value of a field.
2460 @return Pointer to a newly allocated string.
2462 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2463 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2464 or (what is worse) valid SQL that is wrong.
2466 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2468 The calling code is responsible for freeing the resulting string by calling free().
2470 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2471 growing_buffer* val_buf = buffer_init( 32 );
2472 const char* numtype = get_datatype( field );
2474 // For historical reasons the following contains cruft that could be cleaned up.
2475 if( !strncmp( numtype, "INT", 3 ) ) {
2476 if( value->type == JSON_NUMBER )
2477 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2478 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2480 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2483 } else if( !strcmp( numtype, "NUMERIC" )) {
2484 if( value->type == JSON_NUMBER )
2485 buffer_fadd( val_buf, jsonObjectGetString( value ));
2487 buffer_fadd( val_buf, jsonObjectGetString( value ));
2491 // Presumably this was really intended to be a string, so quote it
2492 char* str = jsonObjectToSimpleString( value );
2493 if( dbi_conn_quote_string( dbhandle, &str )) {
2494 OSRF_BUFFER_ADD( val_buf, str );
2497 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2499 buffer_free( val_buf );
2504 return buffer_release( val_buf );
2507 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2508 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2509 growing_buffer* sql_buf = buffer_init( 32 );
2515 osrfHashGet( field, "name" )
2519 buffer_add( sql_buf, "IN (" );
2520 } else if( !strcasecmp( op,"not in" )) {
2521 buffer_add( sql_buf, "NOT IN (" );
2523 buffer_add( sql_buf, "IN (" );
2526 if( node->type == JSON_HASH ) {
2527 // subquery predicate
2528 char* subpred = buildQuery( ctx, node, SUBSELECT );
2530 buffer_free( sql_buf );
2534 buffer_add( sql_buf, subpred );
2537 } else if( node->type == JSON_ARRAY ) {
2538 // literal value list
2539 int in_item_index = 0;
2540 int in_item_first = 1;
2541 const jsonObject* in_item;
2542 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2547 buffer_add( sql_buf, ", " );
2550 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2551 osrfLogError( OSRF_LOG_MARK,
2552 "%s: Expected string or number within IN list; found %s",
2553 modulename, json_type( in_item->type ) );
2554 buffer_free( sql_buf );
2558 // Append the literal value -- quoted if not a number
2559 if( JSON_NUMBER == in_item->type ) {
2560 char* val = jsonNumberToDBString( field, in_item );
2561 OSRF_BUFFER_ADD( sql_buf, val );
2564 } else if( !strcmp( get_primitive( field ), "number" )) {
2565 char* val = jsonNumberToDBString( field, in_item );
2566 OSRF_BUFFER_ADD( sql_buf, val );
2570 char* key_string = jsonObjectToSimpleString( in_item );
2571 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2572 OSRF_BUFFER_ADD( sql_buf, key_string );
2575 osrfLogError( OSRF_LOG_MARK,
2576 "%s: Error quoting key string [%s]", modulename, key_string );
2578 buffer_free( sql_buf );
2584 if( in_item_first ) {
2585 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2586 buffer_free( sql_buf );
2590 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2591 modulename, json_type( node->type ));
2592 buffer_free( sql_buf );
2596 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2598 return buffer_release( sql_buf );
2601 // Receive a JSON_ARRAY representing a function call. The first
2602 // entry in the array is the function name. The rest are parameters.
2603 static char* searchValueTransform( const jsonObject* array ) {
2605 if( array->size < 1 ) {
2606 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2610 // Get the function name
2611 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2612 if( func_item->type != JSON_STRING ) {
2613 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2614 modulename, json_type( func_item->type ));
2618 growing_buffer* sql_buf = buffer_init( 32 );
2620 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2621 OSRF_BUFFER_ADD( sql_buf, "( " );
2623 // Get the parameters
2624 int func_item_index = 1; // We already grabbed the zeroth entry
2625 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2627 // Add a separator comma, if we need one
2628 if( func_item_index > 2 )
2629 buffer_add( sql_buf, ", " );
2631 // Add the current parameter
2632 if( func_item->type == JSON_NULL ) {
2633 buffer_add( sql_buf, "NULL" );
2635 if( func_item->type == JSON_BOOL ) {
2636 if( jsonBoolIsTrue(func_item) ) {
2637 buffer_add( sql_buf, "TRUE" );
2639 buffer_add( sql_buf, "FALSE" );
2642 char* val = jsonObjectToSimpleString( func_item );
2643 if( dbi_conn_quote_string( dbhandle, &val )) {
2644 OSRF_BUFFER_ADD( sql_buf, val );
2647 osrfLogError( OSRF_LOG_MARK,
2648 "%s: Error quoting key string [%s]", modulename, val );
2649 buffer_free( sql_buf );
2657 buffer_add( sql_buf, " )" );
2659 return buffer_release( sql_buf );
2662 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2663 const jsonObject* node, const char* op ) {
2665 if( ! is_good_operator( op ) ) {
2666 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2670 char* val = searchValueTransform( node );
2674 growing_buffer* sql_buf = buffer_init( 32 );
2679 osrfHashGet( field, "name" ),
2686 return buffer_release( sql_buf );
2689 // class_alias is a class name or other table alias
2690 // field is a field definition as stored in the IDL
2691 // node comes from the method parameter, and may represent an entry in the SELECT list
2692 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2693 const jsonObject* node ) {
2694 growing_buffer* sql_buf = buffer_init( 32 );
2696 const char* field_transform = jsonObjectGetString(
2697 jsonObjectGetKeyConst( node, "transform" ) );
2698 const char* transform_subcolumn = jsonObjectGetString(
2699 jsonObjectGetKeyConst( node, "result_field" ) );
2701 if( transform_subcolumn ) {
2702 if( ! is_identifier( transform_subcolumn ) ) {
2703 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2704 modulename, transform_subcolumn );
2705 buffer_free( sql_buf );
2708 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2711 if( field_transform ) {
2713 if( ! is_identifier( field_transform ) ) {
2714 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2715 modulename, field_transform );
2716 buffer_free( sql_buf );
2720 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2721 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2722 field_transform, class_alias, osrfHashGet( field, "name" ));
2724 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2725 field_transform, class_alias, osrfHashGet( field, "name" ));
2728 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2731 if( array->type != JSON_ARRAY ) {
2732 osrfLogError( OSRF_LOG_MARK,
2733 "%s: Expected JSON_ARRAY for function params; found %s",
2734 modulename, json_type( array->type ) );
2735 buffer_free( sql_buf );
2738 int func_item_index = 0;
2739 jsonObject* func_item;
2740 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2742 char* val = jsonObjectToSimpleString( func_item );
2745 buffer_add( sql_buf, ",NULL" );
2746 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2747 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2748 OSRF_BUFFER_ADD( sql_buf, val );
2750 osrfLogError( OSRF_LOG_MARK,
2751 "%s: Error quoting key string [%s]", modulename, val );
2753 buffer_free( sql_buf );
2760 buffer_add( sql_buf, " )" );
2763 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2766 if( transform_subcolumn )
2767 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2769 return buffer_release( sql_buf );
2772 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2773 const jsonObject* node, const char* op ) {
2775 if( ! is_good_operator( op ) ) {
2776 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2780 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2781 if( ! field_transform )
2784 int extra_parens = 0; // boolean
2786 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2788 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2790 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2792 free( field_transform );
2796 } else if( value_obj->type == JSON_ARRAY ) {
2797 value = searchValueTransform( value_obj );
2799 osrfLogError( OSRF_LOG_MARK,
2800 "%s: Error building value transform for field transform", modulename );
2801 free( field_transform );
2804 } else if( value_obj->type == JSON_HASH ) {
2805 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2807 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2809 free( field_transform );
2813 } else if( value_obj->type == JSON_NUMBER ) {
2814 value = jsonNumberToDBString( field, value_obj );
2815 } else if( value_obj->type == JSON_NULL ) {
2816 osrfLogError( OSRF_LOG_MARK,
2817 "%s: Error building predicate for field transform: null value", modulename );
2818 free( field_transform );
2820 } else if( value_obj->type == JSON_BOOL ) {
2821 osrfLogError( OSRF_LOG_MARK,
2822 "%s: Error building predicate for field transform: boolean value", modulename );
2823 free( field_transform );
2826 if( !strcmp( get_primitive( field ), "number") ) {
2827 value = jsonNumberToDBString( field, value_obj );
2829 value = jsonObjectToSimpleString( value_obj );
2830 if( !dbi_conn_quote_string( dbhandle, &value )) {
2831 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2832 modulename, value );
2834 free( field_transform );
2840 const char* left_parens = "";
2841 const char* right_parens = "";
2843 if( extra_parens ) {
2848 const char* right_percent = "";
2849 const char* real_op = op;
2851 if( !strcasecmp( op, "startwith") ) {
2853 right_percent = "|| '%'";
2856 growing_buffer* sql_buf = buffer_init( 32 );
2860 "%s%s %s %s %s%s %s%s",
2872 free( field_transform );
2874 return buffer_release( sql_buf );
2877 static char* searchSimplePredicate( const char* op, const char* class_alias,
2878 osrfHash* field, const jsonObject* node ) {
2880 if( ! is_good_operator( op ) ) {
2881 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2887 // Get the value to which we are comparing the specified column
2888 if( node->type != JSON_NULL ) {
2889 if( node->type == JSON_NUMBER ) {
2890 val = jsonNumberToDBString( field, node );
2891 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2892 val = jsonNumberToDBString( field, node );
2894 val = jsonObjectToSimpleString( node );
2899 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2900 // Value is not numeric; enclose it in quotes
2901 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2902 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2909 // Compare to a null value
2910 val = strdup( "NULL" );
2911 if( strcmp( op, "=" ))
2917 growing_buffer* sql_buf = buffer_init( 32 );
2918 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2919 char* pred = buffer_release( sql_buf );
2926 static char* searchBETWEENPredicate( const char* class_alias,
2927 osrfHash* field, const jsonObject* node ) {
2929 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2930 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2932 if( NULL == y_node ) {
2933 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2936 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2937 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2944 if( !strcmp( get_primitive( field ), "number") ) {
2945 x_string = jsonNumberToDBString( field, x_node );
2946 y_string = jsonNumberToDBString( field, y_node );
2949 x_string = jsonObjectToSimpleString( x_node );
2950 y_string = jsonObjectToSimpleString( y_node );
2951 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2952 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2953 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2954 modulename, x_string, y_string );
2961 growing_buffer* sql_buf = buffer_init( 32 );
2962 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2963 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2967 return buffer_release( sql_buf );
2970 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2971 jsonObject* node, osrfMethodContext* ctx ) {
2974 if( node->type == JSON_ARRAY ) { // equality IN search
2975 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2976 } else if( node->type == JSON_HASH ) { // other search
2977 jsonIterator* pred_itr = jsonNewIterator( node );
2978 if( !jsonIteratorHasNext( pred_itr ) ) {
2979 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2980 modulename, osrfHashGet(field, "name" ));
2982 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2984 // Verify that there are no additional predicates
2985 if( jsonIteratorHasNext( pred_itr ) ) {
2986 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2987 modulename, osrfHashGet(field, "name" ));
2988 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2989 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2990 else if( !(strcasecmp( pred_itr->key,"in" ))
2991 || !(strcasecmp( pred_itr->key,"not in" )) )
2992 pred = searchINPredicate(
2993 class_info->alias, field, pred_node, pred_itr->key, ctx );
2994 else if( pred_node->type == JSON_ARRAY )
2995 pred = searchFunctionPredicate(
2996 class_info->alias, field, pred_node, pred_itr->key );
2997 else if( pred_node->type == JSON_HASH )
2998 pred = searchFieldTransformPredicate(
2999 class_info, field, pred_node, pred_itr->key );
3001 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
3003 jsonIteratorFree( pred_itr );
3005 } else if( node->type == JSON_NULL ) { // IS NULL search
3006 growing_buffer* _p = buffer_init( 64 );
3009 "\"%s\".%s IS NULL",
3011 osrfHashGet( field, "name" )
3013 pred = buffer_release( _p );
3014 } else { // equality search
3015 pred = searchSimplePredicate( "=", class_info->alias, field, node );
3034 field : call_number,
3050 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
3052 const jsonObject* working_hash;
3053 jsonObject* freeable_hash = NULL;
3055 if( join_hash->type == JSON_HASH ) {
3056 working_hash = join_hash;
3057 } else if( join_hash->type == JSON_STRING ) {
3058 // turn it into a JSON_HASH by creating a wrapper
3059 // around a copy of the original
3060 const char* _tmp = jsonObjectGetString( join_hash );
3061 freeable_hash = jsonNewObjectType( JSON_HASH );
3062 jsonObjectSetKey( freeable_hash, _tmp, NULL );
3063 working_hash = freeable_hash;
3067 "%s: JOIN failed; expected JSON object type not found",
3073 growing_buffer* join_buf = buffer_init( 128 );
3074 const char* leftclass = left_info->class_name;
3076 jsonObject* snode = NULL;
3077 jsonIterator* search_itr = jsonNewIterator( working_hash );
3079 while ( (snode = jsonIteratorNext( search_itr )) ) {
3080 const char* right_alias = search_itr->key;
3082 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
3084 class = right_alias;
3086 const ClassInfo* right_info = add_joined_class( right_alias, class );
3090 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
3094 jsonIteratorFree( search_itr );
3095 buffer_free( join_buf );
3097 jsonObjectFree( freeable_hash );
3100 osrfHash* links = right_info->links;
3101 const char* table = right_info->source_def;
3103 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
3104 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
3106 if( field && !fkey ) {
3107 // Look up the corresponding join column in the IDL.
3108 // The link must be defined in the child table,
3109 // and point to the right parent table.
3110 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
3111 const char* reltype = NULL;
3112 const char* other_class = NULL;
3113 reltype = osrfHashGet( idl_link, "reltype" );
3114 if( reltype && strcmp( reltype, "has_many" ) )
3115 other_class = osrfHashGet( idl_link, "class" );
3116 if( other_class && !strcmp( other_class, leftclass ) )
3117 fkey = osrfHashGet( idl_link, "key" );
3121 "%s: JOIN failed. No link defined from %s.%s to %s",
3127 buffer_free( join_buf );
3129 jsonObjectFree( freeable_hash );
3130 jsonIteratorFree( search_itr );
3134 } else if( !field && fkey ) {
3135 // Look up the corresponding join column in the IDL.
3136 // The link must be defined in the child table,
3137 // and point to the right parent table.
3138 osrfHash* left_links = left_info->links;
3139 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3140 const char* reltype = NULL;
3141 const char* other_class = NULL;
3142 reltype = osrfHashGet( idl_link, "reltype" );
3143 if( reltype && strcmp( reltype, "has_many" ) )
3144 other_class = osrfHashGet( idl_link, "class" );
3145 if( other_class && !strcmp( other_class, class ) )
3146 field = osrfHashGet( idl_link, "key" );
3150 "%s: JOIN failed. No link defined from %s.%s to %s",
3156 buffer_free( join_buf );
3158 jsonObjectFree( freeable_hash );
3159 jsonIteratorFree( search_itr );
3163 } else if( !field && !fkey ) {
3164 osrfHash* left_links = left_info->links;
3166 // For each link defined for the left class:
3167 // see if the link references the joined class
3168 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3169 osrfHash* curr_link = NULL;
3170 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3171 const char* other_class = osrfHashGet( curr_link, "class" );
3172 if( other_class && !strcmp( other_class, class ) ) {
3174 // In the IDL, the parent class doesn't always know then names of the child
3175 // columns that are pointing to it, so don't use that end of the link
3176 const char* reltype = osrfHashGet( curr_link, "reltype" );
3177 if( reltype && strcmp( reltype, "has_many" ) ) {
3178 // Found a link between the classes
3179 fkey = osrfHashIteratorKey( itr );
3180 field = osrfHashGet( curr_link, "key" );
3185 osrfHashIteratorFree( itr );
3187 if( !field || !fkey ) {
3188 // Do another such search, with the classes reversed
3190 // For each link defined for the joined class:
3191 // see if the link references the left class
3192 osrfHashIterator* itr = osrfNewHashIterator( links );
3193 osrfHash* curr_link = NULL;
3194 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3195 const char* other_class = osrfHashGet( curr_link, "class" );
3196 if( other_class && !strcmp( other_class, leftclass ) ) {
3198 // In the IDL, the parent class doesn't know then names of the child
3199 // columns that are pointing to it, so don't use that end of the link
3200 const char* reltype = osrfHashGet( curr_link, "reltype" );
3201 if( reltype && strcmp( reltype, "has_many" ) ) {
3202 // Found a link between the classes
3203 field = osrfHashIteratorKey( itr );
3204 fkey = osrfHashGet( curr_link, "key" );
3209 osrfHashIteratorFree( itr );
3212 if( !field || !fkey ) {
3215 "%s: JOIN failed. No link defined between %s and %s",
3220 buffer_free( join_buf );
3222 jsonObjectFree( freeable_hash );
3223 jsonIteratorFree( search_itr );
3228 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3230 if( !strcasecmp( type,"left" )) {
3231 buffer_add( join_buf, " LEFT JOIN" );
3232 } else if( !strcasecmp( type,"right" )) {
3233 buffer_add( join_buf, " RIGHT JOIN" );
3234 } else if( !strcasecmp( type,"full" )) {
3235 buffer_add( join_buf, " FULL JOIN" );
3237 buffer_add( join_buf, " INNER JOIN" );
3240 buffer_add( join_buf, " INNER JOIN" );
3243 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3244 table, right_alias, right_alias, field, left_info->alias, fkey );
3246 // Add any other join conditions as specified by "filter"
3247 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3249 const char* filter_op = jsonObjectGetString(
3250 jsonObjectGetKeyConst( snode, "filter_op" ) );
3251 if( filter_op && !strcasecmp( "or",filter_op )) {
3252 buffer_add( join_buf, " OR " );
3254 buffer_add( join_buf, " AND " );
3257 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3259 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3260 OSRF_BUFFER_ADD( join_buf, jpred );
3265 "%s: JOIN failed. Invalid conditional expression.",
3268 jsonIteratorFree( search_itr );
3269 buffer_free( join_buf );
3271 jsonObjectFree( freeable_hash );
3276 buffer_add( join_buf, " ) " );
3278 // Recursively add a nested join, if one is present
3279 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3281 char* jpred = searchJOIN( join_filter, right_info );
3283 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3284 OSRF_BUFFER_ADD( join_buf, jpred );
3287 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3288 jsonIteratorFree( search_itr );
3289 buffer_free( join_buf );
3291 jsonObjectFree( freeable_hash );
3298 jsonObjectFree( freeable_hash );
3299 jsonIteratorFree( search_itr );
3301 return buffer_release( join_buf );
3306 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3307 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3308 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3310 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3312 search_hash is the JSON expression of the conditions.
3313 meta is the class definition from the IDL, for the relevant table.
3314 opjoin_type indicates whether multiple conditions, if present, should be
3315 connected by AND or OR.
3316 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3317 to pass it to other functions -- and all they do with it is to use the session
3318 and request members to send error messages back to the client.
3322 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3323 int opjoin_type, osrfMethodContext* ctx ) {
3327 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3328 "opjoin_type = %d, ctx addr = %p",
3331 class_info->class_def,
3336 growing_buffer* sql_buf = buffer_init( 128 );
3338 jsonObject* node = NULL;
3341 if( search_hash->type == JSON_ARRAY ) {
3342 if( 0 == search_hash->size ) {
3345 "%s: Invalid predicate structure: empty JSON array",
3348 buffer_free( sql_buf );
3352 unsigned long i = 0;
3353 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3357 if( opjoin_type == OR_OP_JOIN )
3358 buffer_add( sql_buf, " OR " );
3360 buffer_add( sql_buf, " AND " );
3363 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3365 buffer_free( sql_buf );
3369 buffer_fadd( sql_buf, "( %s )", subpred );
3373 } else if( search_hash->type == JSON_HASH ) {
3374 osrfLogDebug( OSRF_LOG_MARK,
3375 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3376 jsonIterator* search_itr = jsonNewIterator( search_hash );
3377 if( !jsonIteratorHasNext( search_itr ) ) {
3380 "%s: Invalid predicate structure: empty JSON object",
3383 jsonIteratorFree( search_itr );
3384 buffer_free( sql_buf );
3388 while( (node = jsonIteratorNext( search_itr )) ) {
3393 if( opjoin_type == OR_OP_JOIN )
3394 buffer_add( sql_buf, " OR " );
3396 buffer_add( sql_buf, " AND " );
3399 if( '+' == search_itr->key[ 0 ] ) {
3401 // This plus sign prefixes a class name or other table alias;
3402 // make sure the table alias is in scope
3403 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3404 if( ! alias_info ) {
3407 "%s: Invalid table alias \"%s\" in WHERE clause",
3411 jsonIteratorFree( search_itr );
3412 buffer_free( sql_buf );
3416 if( node->type == JSON_STRING ) {
3417 // It's the name of a column; make sure it belongs to the class
3418 const char* fieldname = jsonObjectGetString( node );
3419 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3422 "%s: Invalid column name \"%s\" in WHERE clause "
3423 "for table alias \"%s\"",
3428 jsonIteratorFree( search_itr );
3429 buffer_free( sql_buf );
3433 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3435 // It's something more complicated
3436 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3438 jsonIteratorFree( search_itr );
3439 buffer_free( sql_buf );
3443 buffer_fadd( sql_buf, "( %s )", subpred );
3446 } else if( '-' == search_itr->key[ 0 ] ) {
3447 if( !strcasecmp( "-or", search_itr->key )) {
3448 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3450 jsonIteratorFree( search_itr );
3451 buffer_free( sql_buf );
3455 buffer_fadd( sql_buf, "( %s )", subpred );
3457 } else if( !strcasecmp( "-and", search_itr->key )) {
3458 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3460 jsonIteratorFree( search_itr );
3461 buffer_free( sql_buf );
3465 buffer_fadd( sql_buf, "( %s )", subpred );
3467 } else if( !strcasecmp("-not",search_itr->key) ) {
3468 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3470 jsonIteratorFree( search_itr );
3471 buffer_free( sql_buf );
3475 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3477 } else if( !strcasecmp( "-exists", search_itr->key )) {
3478 char* subpred = buildQuery( ctx, node, SUBSELECT );
3480 jsonIteratorFree( search_itr );
3481 buffer_free( sql_buf );
3485 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3487 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3488 char* subpred = buildQuery( ctx, node, SUBSELECT );
3490 jsonIteratorFree( search_itr );
3491 buffer_free( sql_buf );
3495 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3497 } else { // Invalid "minus" operator
3500 "%s: Invalid operator \"%s\" in WHERE clause",
3504 jsonIteratorFree( search_itr );
3505 buffer_free( sql_buf );
3511 const char* class = class_info->class_name;
3512 osrfHash* fields = class_info->fields;
3513 osrfHash* field = osrfHashGet( fields, search_itr->key );
3516 const char* table = class_info->source_def;
3519 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3522 table ? table : "?",
3525 jsonIteratorFree( search_itr );
3526 buffer_free( sql_buf );
3530 char* subpred = searchPredicate( class_info, field, node, ctx );
3532 buffer_free( sql_buf );
3533 jsonIteratorFree( search_itr );
3537 buffer_add( sql_buf, subpred );
3541 jsonIteratorFree( search_itr );
3544 // ERROR ... only hash and array allowed at this level
3545 char* predicate_string = jsonObjectToJSON( search_hash );
3548 "%s: Invalid predicate structure: %s",
3552 buffer_free( sql_buf );
3553 free( predicate_string );
3557 return buffer_release( sql_buf );
3560 /* Build a JSON_ARRAY of field names for a given table alias
3562 static jsonObject* defaultSelectList( const char* table_alias ) {
3567 ClassInfo* class_info = search_all_alias( table_alias );
3568 if( ! class_info ) {
3571 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3578 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3579 osrfHash* field_def = NULL;
3580 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3581 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3582 const char* field_name = osrfHashIteratorKey( field_itr );
3583 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3584 jsonObjectPush( array, jsonNewObject( field_name ) );
3587 osrfHashIteratorFree( field_itr );
3592 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3593 // The jsonObject must be a JSON_HASH with an single entry for "union",
3594 // "intersect", or "except". The data associated with this key must be an
3595 // array of hashes, each hash being a query.
3596 // Also allowed but currently ignored: entries for "order_by" and "alias".
3597 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3599 if( ! combo || combo->type != JSON_HASH )
3600 return NULL; // should be impossible; validated by caller
3602 const jsonObject* query_array = NULL; // array of subordinate queries
3603 const char* op = NULL; // name of operator, e.g. UNION
3604 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3605 int op_count = 0; // for detecting conflicting operators
3606 int excepting = 0; // boolean
3607 int all = 0; // boolean
3608 jsonObject* order_obj = NULL;
3610 // Identify the elements in the hash
3611 jsonIterator* query_itr = jsonNewIterator( combo );
3612 jsonObject* curr_obj = NULL;
3613 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3614 if( ! strcmp( "union", query_itr->key ) ) {
3617 query_array = curr_obj;
3618 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3621 query_array = curr_obj;
3622 } else if( ! strcmp( "except", query_itr->key ) ) {
3626 query_array = curr_obj;
3627 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3630 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3633 order_obj = curr_obj;
3634 } else if( ! strcmp( "alias", query_itr->key ) ) {
3635 if( curr_obj->type != JSON_STRING ) {
3636 jsonIteratorFree( query_itr );
3639 alias = jsonObjectGetString( curr_obj );
3640 } else if( ! strcmp( "all", query_itr->key ) ) {
3641 if( obj_is_true( curr_obj ) )
3645 osrfAppSessionStatus(
3647 OSRF_STATUS_INTERNALSERVERERROR,
3648 "osrfMethodException",
3650 "Malformed query; unexpected entry in query object"
3654 "%s: Unexpected entry for \"%s\" in%squery",
3659 jsonIteratorFree( query_itr );
3663 jsonIteratorFree( query_itr );
3665 // More sanity checks
3666 if( ! query_array ) {
3668 osrfAppSessionStatus(
3670 OSRF_STATUS_INTERNALSERVERERROR,
3671 "osrfMethodException",
3673 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3677 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3680 return NULL; // should be impossible...
3681 } else if( op_count > 1 ) {
3683 osrfAppSessionStatus(
3685 OSRF_STATUS_INTERNALSERVERERROR,
3686 "osrfMethodException",
3688 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3692 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3696 } if( query_array->type != JSON_ARRAY ) {
3698 osrfAppSessionStatus(
3700 OSRF_STATUS_INTERNALSERVERERROR,
3701 "osrfMethodException",
3703 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3707 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3710 json_type( query_array->type )
3713 } if( query_array->size < 2 ) {
3715 osrfAppSessionStatus(
3717 OSRF_STATUS_INTERNALSERVERERROR,
3718 "osrfMethodException",
3720 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3724 "%s:%srequires multiple queries as operands",
3729 } else if( excepting && query_array->size > 2 ) {
3731 osrfAppSessionStatus(
3733 OSRF_STATUS_INTERNALSERVERERROR,
3734 "osrfMethodException",
3736 "EXCEPT operator has too many queries as operands"
3740 "%s:EXCEPT operator has too many queries as operands",
3744 } else if( order_obj && ! alias ) {
3746 osrfAppSessionStatus(
3748 OSRF_STATUS_INTERNALSERVERERROR,
3749 "osrfMethodException",
3751 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3755 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3761 // So far so good. Now build the SQL.
3762 growing_buffer* sql = buffer_init( 256 );
3764 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3765 // Add a layer of parentheses
3766 if( flags & SUBCOMBO )
3767 OSRF_BUFFER_ADD( sql, "( " );
3769 // Traverse the query array. Each entry should be a hash.
3770 int first = 1; // boolean
3772 jsonObject* query = NULL;
3773 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3774 if( query->type != JSON_HASH ) {
3776 osrfAppSessionStatus(
3778 OSRF_STATUS_INTERNALSERVERERROR,
3779 "osrfMethodException",
3781 "Malformed query under UNION, INTERSECT or EXCEPT"
3785 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3788 json_type( query->type )
3797 OSRF_BUFFER_ADD( sql, op );
3799 OSRF_BUFFER_ADD( sql, "ALL " );
3802 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3806 "%s: Error building query under%s",
3814 OSRF_BUFFER_ADD( sql, query_str );
3817 if( flags & SUBCOMBO )
3818 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3820 if( !(flags & SUBSELECT) )
3821 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3823 return buffer_release( sql );
3826 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3827 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3828 // or "except" to indicate the type of query.
3829 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3833 osrfAppSessionStatus(
3835 OSRF_STATUS_INTERNALSERVERERROR,
3836 "osrfMethodException",
3838 "Malformed query; no query object"
3840 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3842 } else if( query->type != JSON_HASH ) {
3844 osrfAppSessionStatus(
3846 OSRF_STATUS_INTERNALSERVERERROR,
3847 "osrfMethodException",
3849 "Malformed query object"
3853 "%s: Query object is %s instead of JSON_HASH",
3855 json_type( query->type )
3860 // Determine what kind of query it purports to be, and dispatch accordingly.
3861 if( jsonObjectGetKeyConst( query, "union" ) ||
3862 jsonObjectGetKeyConst( query, "intersect" ) ||
3863 jsonObjectGetKeyConst( query, "except" )) {
3864 return doCombo( ctx, query, flags );
3866 // It is presumably a SELECT query
3868 // Push a node onto the stack for the current query. Every level of
3869 // subquery gets its own QueryFrame on the Stack.
3872 // Build an SQL SELECT statement
3875 jsonObjectGetKey( query, "select" ),
3876 jsonObjectGetKeyConst( query, "from" ),
3877 jsonObjectGetKeyConst( query, "where" ),
3878 jsonObjectGetKeyConst( query, "having" ),
3879 jsonObjectGetKeyConst( query, "order_by" ),
3880 jsonObjectGetKeyConst( query, "limit" ),
3881 jsonObjectGetKeyConst( query, "offset" ),
3890 /* method context */ osrfMethodContext* ctx,
3892 /* SELECT */ jsonObject* selhash,
3893 /* FROM */ const jsonObject* join_hash,
3894 /* WHERE */ const jsonObject* search_hash,
3895 /* HAVING */ const jsonObject* having_hash,
3896 /* ORDER BY */ const jsonObject* order_hash,
3897 /* LIMIT */ const jsonObject* limit,
3898 /* OFFSET */ const jsonObject* offset,
3899 /* flags */ int flags
3901 const char* locale = osrf_message_get_last_locale();
3903 // general tmp objects
3904 const jsonObject* tmp_const;
3905 jsonObject* selclass = NULL;
3906 jsonObject* snode = NULL;
3907 jsonObject* onode = NULL;
3909 char* string = NULL;
3910 int from_function = 0;
3915 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3917 // punt if there's no FROM clause
3918 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3921 "%s: FROM clause is missing or empty",
3925 osrfAppSessionStatus(
3927 OSRF_STATUS_INTERNALSERVERERROR,
3928 "osrfMethodException",
3930 "FROM clause is missing or empty in JSON query"
3935 // the core search class
3936 const char* core_class = NULL;
3938 // get the core class -- the only key of the top level FROM clause, or a string
3939 if( join_hash->type == JSON_HASH ) {
3940 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3941 snode = jsonIteratorNext( tmp_itr );
3943 // Populate the current QueryFrame with information
3944 // about the core class
3945 if( add_query_core( NULL, tmp_itr->key ) ) {
3947 osrfAppSessionStatus(
3949 OSRF_STATUS_INTERNALSERVERERROR,
3950 "osrfMethodException",
3952 "Unable to look up core class"
3956 core_class = curr_query->core.class_name;
3959 jsonObject* extra = jsonIteratorNext( tmp_itr );
3961 jsonIteratorFree( tmp_itr );
3964 // There shouldn't be more than one entry in join_hash
3968 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3972 osrfAppSessionStatus(
3974 OSRF_STATUS_INTERNALSERVERERROR,
3975 "osrfMethodException",
3977 "Malformed FROM clause in JSON query"
3979 return NULL; // Malformed join_hash; extra entry
3981 } else if( join_hash->type == JSON_ARRAY ) {
3982 // We're selecting from a function, not from a table
3984 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3987 } else if( join_hash->type == JSON_STRING ) {
3988 // Populate the current QueryFrame with information
3989 // about the core class
3990 core_class = jsonObjectGetString( join_hash );
3992 if( add_query_core( NULL, core_class ) ) {
3994 osrfAppSessionStatus(
3996 OSRF_STATUS_INTERNALSERVERERROR,
3997 "osrfMethodException",
3999 "Unable to look up core class"
4007 "%s: FROM clause is unexpected JSON type: %s",
4009 json_type( join_hash->type )
4012 osrfAppSessionStatus(
4014 OSRF_STATUS_INTERNALSERVERERROR,
4015 "osrfMethodException",
4017 "Ill-formed FROM clause in JSON query"
4022 // Build the join clause, if any, while filling out the list
4023 // of joined classes in the current QueryFrame.
4024 char* join_clause = NULL;
4025 if( join_hash && ! from_function ) {
4027 join_clause = searchJOIN( join_hash, &curr_query->core );
4028 if( ! join_clause ) {
4030 osrfAppSessionStatus(
4032 OSRF_STATUS_INTERNALSERVERERROR,
4033 "osrfMethodException",
4035 "Unable to construct JOIN clause(s)"
4041 // For in case we don't get a select list
4042 jsonObject* defaultselhash = NULL;
4044 // if there is no select list, build a default select list ...
4045 if( !selhash && !from_function ) {
4046 jsonObject* default_list = defaultSelectList( core_class );
4047 if( ! default_list ) {
4049 osrfAppSessionStatus(
4051 OSRF_STATUS_INTERNALSERVERERROR,
4052 "osrfMethodException",
4054 "Unable to build default SELECT clause in JSON query"
4056 free( join_clause );
4061 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
4062 jsonObjectSetKey( selhash, core_class, default_list );
4065 // The SELECT clause can be encoded only by a hash
4066 if( !from_function && selhash->type != JSON_HASH ) {
4069 "%s: Expected JSON_HASH for SELECT clause; found %s",
4071 json_type( selhash->type )
4075 osrfAppSessionStatus(
4077 OSRF_STATUS_INTERNALSERVERERROR,
4078 "osrfMethodException",
4080 "Malformed SELECT clause in JSON query"
4082 free( join_clause );
4086 // If you see a null or wild card specifier for the core class, or an
4087 // empty array, replace it with a default SELECT list
4088 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
4090 int default_needed = 0; // boolean
4091 if( JSON_STRING == tmp_const->type
4092 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
4094 else if( JSON_NULL == tmp_const->type )
4097 if( default_needed ) {
4098 // Build a default SELECT list
4099 jsonObject* default_list = defaultSelectList( core_class );
4100 if( ! default_list ) {
4102 osrfAppSessionStatus(
4104 OSRF_STATUS_INTERNALSERVERERROR,
4105 "osrfMethodException",
4107 "Can't build default SELECT clause in JSON query"
4109 free( join_clause );
4114 jsonObjectSetKey( selhash, core_class, default_list );
4118 // temp buffers for the SELECT list and GROUP BY clause
4119 growing_buffer* select_buf = buffer_init( 128 );
4120 growing_buffer* group_buf = buffer_init( 128 );
4122 int aggregate_found = 0; // boolean
4124 // Build a select list
4125 if( from_function ) // From a function we select everything
4126 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4129 // Build the SELECT list as SQL
4133 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4134 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4136 const char* cname = selclass_itr->key;
4138 // Make sure the target relation is in the FROM clause.
4140 // At this point join_hash is a step down from the join_hash we
4141 // received as a parameter. If the original was a JSON_STRING,
4142 // then json_hash is now NULL. If the original was a JSON_HASH,
4143 // then json_hash is now the first (and only) entry in it,
4144 // denoting the core class. We've already excluded the
4145 // possibility that the original was a JSON_ARRAY, because in
4146 // that case from_function would be non-NULL, and we wouldn't
4149 // If the current table alias isn't in scope, bail out
4150 ClassInfo* class_info = search_alias( cname );
4151 if( ! class_info ) {
4154 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4159 osrfAppSessionStatus(
4161 OSRF_STATUS_INTERNALSERVERERROR,
4162 "osrfMethodException",
4164 "Selected class not in FROM clause in JSON query"
4166 jsonIteratorFree( selclass_itr );
4167 buffer_free( select_buf );
4168 buffer_free( group_buf );
4169 if( defaultselhash )
4170 jsonObjectFree( defaultselhash );
4171 free( join_clause );
4175 if( selclass->type != JSON_ARRAY ) {
4178 "%s: Malformed SELECT list for class \"%s\"; not an array",
4183 osrfAppSessionStatus(
4185 OSRF_STATUS_INTERNALSERVERERROR,
4186 "osrfMethodException",
4188 "Selected class not in FROM clause in JSON query"
4191 jsonIteratorFree( selclass_itr );
4192 buffer_free( select_buf );
4193 buffer_free( group_buf );
4194 if( defaultselhash )
4195 jsonObjectFree( defaultselhash );
4196 free( join_clause );
4200 // Look up some attributes of the current class
4201 osrfHash* idlClass = class_info->class_def;
4202 osrfHash* class_field_set = class_info->fields;
4203 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4204 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4206 if( 0 == selclass->size ) {
4209 "%s: No columns selected from \"%s\"",
4215 // stitch together the column list for the current table alias...
4216 unsigned long field_idx = 0;
4217 jsonObject* selfield = NULL;
4218 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4220 // If we need a separator comma, add one
4224 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4227 // if the field specification is a string, add it to the list
4228 if( selfield->type == JSON_STRING ) {
4230 // Look up the field in the IDL
4231 const char* col_name = jsonObjectGetString( selfield );
4232 osrfHash* field_def;
4234 if (!osrfStringArrayContains(
4236 osrfHashGet( class_field_set, col_name ),
4237 "suppress_controller"),
4240 field_def = osrfHashGet( class_field_set, col_name );
4243 // No such field in current class
4246 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4252 osrfAppSessionStatus(
4254 OSRF_STATUS_INTERNALSERVERERROR,
4255 "osrfMethodException",
4257 "Selected column not defined in JSON query"
4259 jsonIteratorFree( selclass_itr );
4260 buffer_free( select_buf );
4261 buffer_free( group_buf );
4262 if( defaultselhash )
4263 jsonObjectFree( defaultselhash );
4264 free( join_clause );
4266 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4267 // Virtual field not allowed
4270 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4276 osrfAppSessionStatus(
4278 OSRF_STATUS_INTERNALSERVERERROR,
4279 "osrfMethodException",
4281 "Selected column may not be virtual in JSON query"
4283 jsonIteratorFree( selclass_itr );
4284 buffer_free( select_buf );
4285 buffer_free( group_buf );
4286 if( defaultselhash )
4287 jsonObjectFree( defaultselhash );
4288 free( join_clause );
4294 if( flags & DISABLE_I18N )
4297 i18n = osrfHashGet( field_def, "i18n" );
4299 if( str_is_true( i18n ) ) {
4300 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4301 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4302 class_tname, cname, col_name, class_pkey,
4303 cname, class_pkey, locale, col_name );
4305 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4306 cname, col_name, col_name );
4309 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4310 cname, col_name, col_name );
4313 // ... but it could be an object, in which case we check for a Field Transform
4314 } else if( selfield->type == JSON_HASH ) {
4316 const char* col_name = jsonObjectGetString(
4317 jsonObjectGetKeyConst( selfield, "column" ) );
4319 // Get the field definition from the IDL
4320 osrfHash* field_def;
4321 if (!osrfStringArrayContains(
4323 osrfHashGet( class_field_set, col_name ),
4324 "suppress_controller"),
4327 field_def = osrfHashGet( class_field_set, col_name );
4331 // No such field in current class
4334 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4340 osrfAppSessionStatus(
4342 OSRF_STATUS_INTERNALSERVERERROR,
4343 "osrfMethodException",
4345 "Selected column is not defined in JSON query"
4347 jsonIteratorFree( selclass_itr );
4348 buffer_free( select_buf );
4349 buffer_free( group_buf );
4350 if( defaultselhash )
4351 jsonObjectFree( defaultselhash );
4352 free( join_clause );
4354 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4355 // No such field in current class
4358 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4364 osrfAppSessionStatus(
4366 OSRF_STATUS_INTERNALSERVERERROR,
4367 "osrfMethodException",
4369 "Selected column is virtual in JSON query"
4371 jsonIteratorFree( selclass_itr );
4372 buffer_free( select_buf );
4373 buffer_free( group_buf );
4374 if( defaultselhash )
4375 jsonObjectFree( defaultselhash );
4376 free( join_clause );
4380 // Decide what to use as a column alias
4382 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4383 _alias = jsonObjectGetString( tmp_const );
4384 } else if((tmp_const = jsonObjectGetKeyConst( selfield, "result_field" ))) { // Use result_field name as the alias
4385 _alias = jsonObjectGetString( tmp_const );
4386 } else { // Use field name as the alias
4390 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4391 char* transform_str = searchFieldTransform(
4392 class_info->alias, field_def, selfield );
4393 if( transform_str ) {
4394 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4395 free( transform_str );
4398 osrfAppSessionStatus(
4400 OSRF_STATUS_INTERNALSERVERERROR,
4401 "osrfMethodException",
4403 "Unable to generate transform function in JSON query"
4405 jsonIteratorFree( selclass_itr );
4406 buffer_free( select_buf );
4407 buffer_free( group_buf );
4408 if( defaultselhash )
4409 jsonObjectFree( defaultselhash );
4410 free( join_clause );
4417 if( flags & DISABLE_I18N )
4420 i18n = osrfHashGet( field_def, "i18n" );
4422 if( str_is_true( i18n ) ) {
4423 buffer_fadd( select_buf,
4424 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4425 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4426 class_tname, cname, col_name, class_pkey, cname,
4427 class_pkey, locale, _alias );
4429 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4430 cname, col_name, _alias );
4433 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4434 cname, col_name, _alias );
4441 "%s: Selected item is unexpected JSON type: %s",
4443 json_type( selfield->type )
4446 osrfAppSessionStatus(
4448 OSRF_STATUS_INTERNALSERVERERROR,
4449 "osrfMethodException",
4451 "Ill-formed SELECT item in JSON query"
4453 jsonIteratorFree( selclass_itr );
4454 buffer_free( select_buf );
4455 buffer_free( group_buf );
4456 if( defaultselhash )
4457 jsonObjectFree( defaultselhash );
4458 free( join_clause );
4462 const jsonObject* agg_obj = jsonObjectGetKeyConst( selfield, "aggregate" );
4463 if( obj_is_true( agg_obj ) )
4464 aggregate_found = 1;
4466 // Append a comma (except for the first one)
4467 // and add the column to a GROUP BY clause
4471 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4473 buffer_fadd( group_buf, " %d", sel_pos );
4477 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4479 const jsonObject* aggregate_obj = jsonObjectGetKeyConst( elfield, "aggregate");
4480 if ( ! obj_is_true( aggregate_obj ) ) {
4484 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4487 buffer_fadd(group_buf, " %d", sel_pos);
4490 } else if (is_agg = jsonObjectGetKeyConst( selfield, "having" )) {
4494 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4497 _column = searchFieldTransform(class_info->alias, field, selfield);
4498 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4499 OSRF_BUFFER_ADD(group_buf, _column);
4500 _column = searchFieldTransform(class_info->alias, field, selfield);
4507 } // end while -- iterating across SELECT columns
4509 } // end while -- iterating across classes
4511 jsonIteratorFree( selclass_itr );
4514 char* col_list = buffer_release( select_buf );
4516 // Make sure the SELECT list isn't empty. This can happen, for example,
4517 // if we try to build a default SELECT clause from a non-core table.
4520 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4522 osrfAppSessionStatus(
4524 OSRF_STATUS_INTERNALSERVERERROR,
4525 "osrfMethodException",
4527 "SELECT list is empty"
4530 buffer_free( group_buf );
4531 if( defaultselhash )
4532 jsonObjectFree( defaultselhash );
4533 free( join_clause );
4539 table = searchValueTransform( join_hash );
4541 table = strdup( curr_query->core.source_def );
4545 osrfAppSessionStatus(
4547 OSRF_STATUS_INTERNALSERVERERROR,
4548 "osrfMethodException",
4550 "Unable to identify table for core class"
4553 buffer_free( group_buf );
4554 if( defaultselhash )
4555 jsonObjectFree( defaultselhash );
4556 free( join_clause );
4560 // Put it all together
4561 growing_buffer* sql_buf = buffer_init( 128 );
4562 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4566 // Append the join clause, if any
4568 buffer_add(sql_buf, join_clause );
4569 free( join_clause );
4572 char* order_by_list = NULL;
4573 char* having_buf = NULL;
4575 if( !from_function ) {
4577 // Build a WHERE clause, if there is one
4579 buffer_add( sql_buf, " WHERE " );
4581 // and it's on the WHERE clause
4582 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4585 osrfAppSessionStatus(
4587 OSRF_STATUS_INTERNALSERVERERROR,
4588 "osrfMethodException",
4590 "Severe query error in WHERE predicate -- see error log for more details"
4593 buffer_free( group_buf );
4594 buffer_free( sql_buf );
4595 if( defaultselhash )
4596 jsonObjectFree( defaultselhash );
4600 buffer_add( sql_buf, pred );
4604 // Build a HAVING clause, if there is one
4607 // and it's on the the WHERE clause
4608 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4610 if( ! having_buf ) {
4612 osrfAppSessionStatus(
4614 OSRF_STATUS_INTERNALSERVERERROR,
4615 "osrfMethodException",
4617 "Severe query error in HAVING predicate -- see error log for more details"
4620 buffer_free( group_buf );
4621 buffer_free( sql_buf );
4622 if( defaultselhash )
4623 jsonObjectFree( defaultselhash );
4628 // Build an ORDER BY clause, if there is one
4629 if( NULL == order_hash )
4630 ; // No ORDER BY? do nothing
4631 else if( JSON_ARRAY == order_hash->type ) {
4632 order_by_list = buildOrderByFromArray( ctx, order_hash );
4633 if( !order_by_list ) {
4635 buffer_free( group_buf );
4636 buffer_free( sql_buf );
4637 if( defaultselhash )
4638 jsonObjectFree( defaultselhash );
4641 } else if( JSON_HASH == order_hash->type ) {
4642 // This hash is keyed on class alias. Each class has either
4643 // an array of field names or a hash keyed on field name.
4644 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4645 jsonIterator* class_itr = jsonNewIterator( order_hash );
4646 while( (snode = jsonIteratorNext( class_itr )) ) {
4648 ClassInfo* order_class_info = search_alias( class_itr->key );
4649 if( ! order_class_info ) {
4650 osrfLogError( OSRF_LOG_MARK,
4651 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4652 modulename, class_itr->key );
4654 osrfAppSessionStatus(
4656 OSRF_STATUS_INTERNALSERVERERROR,
4657 "osrfMethodException",
4659 "Invalid class referenced in ORDER BY clause -- "
4660 "see error log for more details"
4662 jsonIteratorFree( class_itr );
4663 buffer_free( order_buf );
4665 buffer_free( group_buf );
4666 buffer_free( sql_buf );
4667 if( defaultselhash )
4668 jsonObjectFree( defaultselhash );
4672 osrfHash* field_list_def = order_class_info->fields;
4674 if( snode->type == JSON_HASH ) {
4676 // Hash is keyed on field names from the current class. For each field
4677 // there is another layer of hash to define the sorting details, if any,
4678 // or a string to indicate direction of sorting.
4679 jsonIterator* order_itr = jsonNewIterator( snode );
4680 while( (onode = jsonIteratorNext( order_itr )) ) {
4682 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4684 osrfLogError( OSRF_LOG_MARK,
4685 "%s: Invalid field \"%s\" in ORDER BY clause",
4686 modulename, order_itr->key );
4688 osrfAppSessionStatus(
4690 OSRF_STATUS_INTERNALSERVERERROR,
4691 "osrfMethodException",
4693 "Invalid field in ORDER BY clause -- "
4694 "see error log for more details"
4696 jsonIteratorFree( order_itr );
4697 jsonIteratorFree( class_itr );
4698 buffer_free( order_buf );
4700 buffer_free( group_buf );
4701 buffer_free( sql_buf );
4702 if( defaultselhash )
4703 jsonObjectFree( defaultselhash );
4705 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4706 osrfLogError( OSRF_LOG_MARK,
4707 "%s: Virtual field \"%s\" in ORDER BY clause",
4708 modulename, order_itr->key );
4710 osrfAppSessionStatus(
4712 OSRF_STATUS_INTERNALSERVERERROR,
4713 "osrfMethodException",
4715 "Virtual field in ORDER BY clause -- "
4716 "see error log for more details"
4718 jsonIteratorFree( order_itr );
4719 jsonIteratorFree( class_itr );
4720 buffer_free( order_buf );
4722 buffer_free( group_buf );
4723 buffer_free( sql_buf );
4724 if( defaultselhash )
4725 jsonObjectFree( defaultselhash );
4729 const char* direction = NULL;
4730 if( onode->type == JSON_HASH ) {
4731 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4732 string = searchFieldTransform(
4734 osrfHashGet( field_list_def, order_itr->key ),
4738 if( ctx ) osrfAppSessionStatus(
4740 OSRF_STATUS_INTERNALSERVERERROR,
4741 "osrfMethodException",
4743 "Severe query error in ORDER BY clause -- "
4744 "see error log for more details"
4746 jsonIteratorFree( order_itr );
4747 jsonIteratorFree( class_itr );
4749 buffer_free( group_buf );
4750 buffer_free( order_buf);
4751 buffer_free( sql_buf );
4752 if( defaultselhash )
4753 jsonObjectFree( defaultselhash );
4757 growing_buffer* field_buf = buffer_init( 16 );
4758 buffer_fadd( field_buf, "\"%s\".%s",
4759 class_itr->key, order_itr->key );
4760 string = buffer_release( field_buf );
4763 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4764 const char* dir = jsonObjectGetString( tmp_const );
4765 if(!strncasecmp( dir, "d", 1 )) {
4766 direction = " DESC";
4772 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4773 osrfLogError( OSRF_LOG_MARK,
4774 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4775 modulename, json_type( onode->type ) );
4777 osrfAppSessionStatus(
4779 OSRF_STATUS_INTERNALSERVERERROR,
4780 "osrfMethodException",
4782 "Malformed ORDER BY clause -- see error log for more details"
4784 jsonIteratorFree( order_itr );
4785 jsonIteratorFree( class_itr );
4787 buffer_free( group_buf );
4788 buffer_free( order_buf );
4789 buffer_free( sql_buf );
4790 if( defaultselhash )
4791 jsonObjectFree( defaultselhash );
4795 string = strdup( order_itr->key );
4796 const char* dir = jsonObjectGetString( onode );
4797 if( !strncasecmp( dir, "d", 1 )) {
4798 direction = " DESC";
4805 OSRF_BUFFER_ADD( order_buf, ", " );
4807 order_buf = buffer_init( 128 );
4809 OSRF_BUFFER_ADD( order_buf, string );
4813 OSRF_BUFFER_ADD( order_buf, direction );
4817 jsonIteratorFree( order_itr );
4819 } else if( snode->type == JSON_ARRAY ) {
4821 // Array is a list of fields from the current class
4822 unsigned long order_idx = 0;
4823 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4825 const char* _f = jsonObjectGetString( onode );
4827 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4829 osrfLogError( OSRF_LOG_MARK,
4830 "%s: Invalid field \"%s\" in ORDER BY clause",
4833 osrfAppSessionStatus(
4835 OSRF_STATUS_INTERNALSERVERERROR,
4836 "osrfMethodException",
4838 "Invalid field in ORDER BY clause -- "
4839 "see error log for more details"
4841 jsonIteratorFree( class_itr );
4842 buffer_free( order_buf );
4844 buffer_free( group_buf );
4845 buffer_free( sql_buf );
4846 if( defaultselhash )
4847 jsonObjectFree( defaultselhash );
4849 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4850 osrfLogError( OSRF_LOG_MARK,
4851 "%s: Virtual field \"%s\" in ORDER BY clause",
4854 osrfAppSessionStatus(
4856 OSRF_STATUS_INTERNALSERVERERROR,
4857 "osrfMethodException",
4859 "Virtual field in ORDER BY clause -- "
4860 "see error log for more details"
4862 jsonIteratorFree( class_itr );
4863 buffer_free( order_buf );
4865 buffer_free( group_buf );
4866 buffer_free( sql_buf );
4867 if( defaultselhash )
4868 jsonObjectFree( defaultselhash );
4873 OSRF_BUFFER_ADD( order_buf, ", " );
4875 order_buf = buffer_init( 128 );
4877 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4881 // IT'S THE OOOOOOOOOOOLD STYLE!
4883 osrfLogError( OSRF_LOG_MARK,
4884 "%s: Possible SQL injection attempt; direct order by is not allowed",
4887 osrfAppSessionStatus(
4889 OSRF_STATUS_INTERNALSERVERERROR,
4890 "osrfMethodException",
4892 "Severe query error -- see error log for more details"
4897 buffer_free( group_buf );
4898 buffer_free( order_buf );
4899 buffer_free( sql_buf );
4900 if( defaultselhash )
4901 jsonObjectFree( defaultselhash );
4902 jsonIteratorFree( class_itr );
4906 jsonIteratorFree( class_itr );
4908 order_by_list = buffer_release( order_buf );
4910 osrfLogError( OSRF_LOG_MARK,
4911 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4912 modulename, json_type( order_hash->type ) );
4914 osrfAppSessionStatus(
4916 OSRF_STATUS_INTERNALSERVERERROR,
4917 "osrfMethodException",
4919 "Malformed ORDER BY clause -- see error log for more details"
4922 buffer_free( group_buf );
4923 buffer_free( sql_buf );
4924 if( defaultselhash )
4925 jsonObjectFree( defaultselhash );
4930 string = buffer_release( group_buf );
4932 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4933 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4934 OSRF_BUFFER_ADD( sql_buf, string );
4939 if( having_buf && *having_buf ) {
4940 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4941 OSRF_BUFFER_ADD( sql_buf, having_buf );
4945 if( order_by_list ) {
4947 if( *order_by_list ) {
4948 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4949 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4952 free( order_by_list );
4956 const char* str = jsonObjectGetString( limit );
4957 if (str) { // limit could be JSON_NULL, etc.
4958 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4963 const char* str = jsonObjectGetString( offset );
4965 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4969 if( !(flags & SUBSELECT) )
4970 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4972 if( defaultselhash )
4973 jsonObjectFree( defaultselhash );
4975 return buffer_release( sql_buf );
4977 } // end of SELECT()
4980 @brief Build a list of ORDER BY expressions.
4981 @param ctx Pointer to the method context.
4982 @param order_array Pointer to a JSON_ARRAY of field specifications.
4983 @return Pointer to a string containing a comma-separated list of ORDER BY expressions.
4984 Each expression may be either a column reference or a function call whose first parameter
4985 is a column reference.
4987 Each entry in @a order_array must be a JSON_HASH with values for "class" and "field".
4988 It may optionally include entries for "direction" and/or "transform".
4990 The calling code is responsible for freeing the returned string.
4992 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array ) {
4993 if( ! order_array ) {
4994 osrfLogError( OSRF_LOG_MARK, "%s: Logic error: NULL pointer for ORDER BY clause",
4997 osrfAppSessionStatus(
4999 OSRF_STATUS_INTERNALSERVERERROR,
5000 "osrfMethodException",
5002 "Logic error: ORDER BY clause expected, not found; "
5003 "see error log for more details"
5006 } else if( order_array->type != JSON_ARRAY ) {
5007 osrfLogError( OSRF_LOG_MARK,
5008 "%s: Logic error: Expected JSON_ARRAY for ORDER BY clause, not found", modulename );
5010 osrfAppSessionStatus(
5012 OSRF_STATUS_INTERNALSERVERERROR,
5013 "osrfMethodException",
5015 "Logic error: Unexpected format for ORDER BY clause; see error log for more details" );
5019 growing_buffer* order_buf = buffer_init( 128 );
5020 int first = 1; // boolean
5022 jsonObject* order_spec;
5023 while( (order_spec = jsonObjectGetIndex( order_array, order_idx++ ))) {
5025 if( JSON_HASH != order_spec->type ) {
5026 osrfLogError( OSRF_LOG_MARK,
5027 "%s: Malformed field specification in ORDER BY clause; "
5028 "expected JSON_HASH, found %s",
5029 modulename, json_type( order_spec->type ) );
5031 osrfAppSessionStatus(
5033 OSRF_STATUS_INTERNALSERVERERROR,
5034 "osrfMethodException",
5036 "Malformed ORDER BY clause -- see error log for more details"
5038 buffer_free( order_buf );
5042 const char* class_alias =
5043 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ));
5045 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ));
5047 jsonObject* compare_to = jsonObjectGetKeyConst( order_spec, "compare" );
5049 if( !field || !class_alias ) {
5050 osrfLogError( OSRF_LOG_MARK,
5051 "%s: Missing class or field name in field specification of ORDER BY clause",
5054 osrfAppSessionStatus(
5056 OSRF_STATUS_INTERNALSERVERERROR,
5057 "osrfMethodException",
5059 "Malformed ORDER BY clause -- see error log for more details"
5061 buffer_free( order_buf );
5065 const ClassInfo* order_class_info = search_alias( class_alias );
5066 if( ! order_class_info ) {
5067 osrfLogInternal( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
5068 "not in FROM clause, skipping it", modulename, class_alias );
5072 // Add a separating comma, except at the beginning
5076 OSRF_BUFFER_ADD( order_buf, ", " );
5078 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
5080 osrfLogError( OSRF_LOG_MARK,
5081 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
5082 modulename, class_alias, field );
5084 osrfAppSessionStatus(
5086 OSRF_STATUS_INTERNALSERVERERROR,
5087 "osrfMethodException",
5089 "Invalid field referenced in ORDER BY clause -- "
5090 "see error log for more details"
5094 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5095 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
5096 modulename, field );
5098 osrfAppSessionStatus(
5100 OSRF_STATUS_INTERNALSERVERERROR,
5101 "osrfMethodException",
5103 "Virtual field in ORDER BY clause -- see error log for more details"
5105 buffer_free( order_buf );
5109 if( jsonObjectGetKeyConst( order_spec, "transform" )) {
5110 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
5111 if( ! transform_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 OSRF_BUFFER_ADD( order_buf, transform_str );
5126 free( transform_str );
5127 } else if( compare_to ) {
5128 char* compare_str = searchPredicate( order_class_info, field_def, compare_to, ctx );
5129 if( ! compare_str ) {
5131 osrfAppSessionStatus(
5133 OSRF_STATUS_INTERNALSERVERERROR,
5134 "osrfMethodException",
5136 "Severe query error in ORDER BY clause -- "
5137 "see error log for more details"
5139 buffer_free( order_buf );
5143 buffer_fadd( order_buf, "(%s)", compare_str );
5144 free( compare_str );
5147 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
5149 const char* direction =
5150 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
5152 if( direction[ 0 ] && ( 'd' == direction[ 0 ] || 'D' == direction[ 0 ] ) )
5153 OSRF_BUFFER_ADD( order_buf, " DESC" );
5155 OSRF_BUFFER_ADD( order_buf, " ASC" );
5159 return buffer_release( order_buf );
5163 @brief Build a SELECT statement.
5164 @param search_hash Pointer to a JSON_HASH or JSON_ARRAY encoding the WHERE clause.
5165 @param rest_of_query Pointer to a JSON_HASH containing any other SQL clauses.
5166 @param meta Pointer to the class metadata for the core class.
5167 @param ctx Pointer to the method context.
5168 @return Pointer to a character string containing the WHERE clause; or NULL upon error.
5170 Within the rest_of_query hash, the meaningful keys are "join", "select", "no_i18n",
5171 "order_by", "limit", and "offset".
5173 The SELECT statements built here are distinct from those built for the json_query method.
5175 static char* buildSELECT ( const jsonObject* search_hash, jsonObject* rest_of_query,
5176 osrfHash* meta, osrfMethodContext* ctx ) {
5178 const char* locale = osrf_message_get_last_locale();
5180 osrfHash* fields = osrfHashGet( meta, "fields" );
5181 const char* core_class = osrfHashGet( meta, "classname" );
5183 const jsonObject* join_hash = jsonObjectGetKeyConst( rest_of_query, "join" );
5185 jsonObject* selhash = NULL;
5186 jsonObject* defaultselhash = NULL;
5188 growing_buffer* sql_buf = buffer_init( 128 );
5189 growing_buffer* select_buf = buffer_init( 128 );
5191 if( !(selhash = jsonObjectGetKey( rest_of_query, "select" )) ) {
5192 defaultselhash = jsonNewObjectType( JSON_HASH );
5193 selhash = defaultselhash;
5196 // If there's no SELECT list for the core class, build one
5197 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
5198 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5200 // Add every non-virtual field to the field list
5201 osrfHash* field_def = NULL;
5202 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5203 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5204 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5205 const char* field = osrfHashIteratorKey( field_itr );
5206 jsonObjectPush( field_list, jsonNewObject( field ) );
5209 osrfHashIteratorFree( field_itr );
5210 jsonObjectSetKey( selhash, core_class, field_list );
5213 // Build a list of columns for the SELECT clause
5215 const jsonObject* snode = NULL;
5216 jsonIterator* class_itr = jsonNewIterator( selhash );
5217 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class
5219 // If the class isn't in the IDL, ignore it
5220 const char* cname = class_itr->key;
5221 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5225 // If the class isn't the core class, and isn't in the JOIN clause, ignore it
5226 if( strcmp( core_class, class_itr->key )) {
5230 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
5231 if( !found->size ) {
5232 jsonObjectFree( found );
5236 jsonObjectFree( found );
5239 const jsonObject* node = NULL;
5240 jsonIterator* select_itr = jsonNewIterator( snode );
5241 while( (node = jsonIteratorNext( select_itr )) ) {
5242 const char* item_str = jsonObjectGetString( node );
5243 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5244 char* fname = osrfHashGet( field, "name" );
5249 if (osrfStringArrayContains( osrfHashGet(field, "suppress_controller"), modulename ))
5255 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
5260 const jsonObject* no_i18n_obj = jsonObjectGetKeyConst( rest_of_query, "no_i18n" );
5261 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5264 i18n = osrfHashGet( field, "i18n" );
5266 if( str_is_true( i18n ) ) {
5267 char* pkey = osrfHashGet( idlClass, "primarykey" );
5268 char* tname = osrfHashGet( idlClass, "tablename" );
5270 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5271 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5272 tname, cname, fname, pkey, cname, pkey, locale, fname );
5274 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5277 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
5281 jsonIteratorFree( select_itr );
5284 jsonIteratorFree( class_itr );
5286 char* col_list = buffer_release( select_buf );
5287 char* table = oilsGetRelation( meta );
5289 table = strdup( "(null)" );
5291 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5295 // Clear the query stack (as a fail-safe precaution against possible
5296 // leftover garbage); then push the first query frame onto the stack.
5297 clear_query_stack();
5299 if( add_query_core( NULL, core_class ) ) {
5301 osrfAppSessionStatus(
5303 OSRF_STATUS_INTERNALSERVERERROR,
5304 "osrfMethodException",
5306 "Unable to build query frame for core class"
5308 buffer_free( sql_buf );
5309 if( defaultselhash )
5310 jsonObjectFree( defaultselhash );
5314 // Add the JOIN clauses, if any
5316 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5317 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
5318 OSRF_BUFFER_ADD( sql_buf, join_clause );
5319 free( join_clause );
5322 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5323 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5325 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
5327 // Add the conditions in the WHERE clause
5328 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5330 osrfAppSessionStatus(
5332 OSRF_STATUS_INTERNALSERVERERROR,
5333 "osrfMethodException",
5335 "Severe query error -- see error log for more details"
5337 buffer_free( sql_buf );
5338 if( defaultselhash )
5339 jsonObjectFree( defaultselhash );
5340 clear_query_stack();
5343 buffer_add( sql_buf, pred );
5347 // Add the ORDER BY, LIMIT, and/or OFFSET clauses, if present
5348 if( rest_of_query ) {
5349 const jsonObject* order_by = NULL;
5350 if( ( order_by = jsonObjectGetKeyConst( rest_of_query, "order_by" )) ){
5352 char* order_by_list = NULL;
5354 if( JSON_ARRAY == order_by->type ) {
5355 order_by_list = buildOrderByFromArray( ctx, order_by );
5356 if( !order_by_list ) {
5357 buffer_free( sql_buf );
5358 if( defaultselhash )
5359 jsonObjectFree( defaultselhash );
5360 clear_query_stack();
5363 } else if( JSON_HASH == order_by->type ) {
5364 // We expect order_by to be a JSON_HASH keyed on class names. Traverse it
5365 // and build a list of ORDER BY expressions.
5366 growing_buffer* order_buf = buffer_init( 128 );
5368 jsonIterator* class_itr = jsonNewIterator( order_by );
5369 while( (snode = jsonIteratorNext( class_itr )) ) { // For each class:
5371 ClassInfo* order_class_info = search_alias( class_itr->key );
5372 if( ! order_class_info )
5373 continue; // class not referenced by FROM clause? Ignore it.
5375 if( JSON_HASH == snode->type ) {
5377 // If the data for the current class is a JSON_HASH, then it is
5378 // keyed on field name.
5380 const jsonObject* onode = NULL;
5381 jsonIterator* order_itr = jsonNewIterator( snode );
5382 while( (onode = jsonIteratorNext( order_itr )) ) { // For each field
5384 osrfHash* field_def = osrfHashGet(
5385 order_class_info->fields, order_itr->key );
5387 continue; // Field not defined in IDL? Ignore it.
5388 if( str_is_true( osrfHashGet( field_def, "virtual")))
5389 continue; // Field is virtual? Ignore it.
5391 char* field_str = NULL;
5392 char* direction = NULL;
5393 if( onode->type == JSON_HASH ) {
5394 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5395 field_str = searchFieldTransform(
5396 class_itr->key, field_def, onode );
5398 osrfAppSessionStatus(
5400 OSRF_STATUS_INTERNALSERVERERROR,
5401 "osrfMethodException",
5403 "Severe query error in ORDER BY clause -- "
5404 "see error log for more details"
5406 jsonIteratorFree( order_itr );
5407 jsonIteratorFree( class_itr );
5408 buffer_free( order_buf );
5409 buffer_free( sql_buf );
5410 if( defaultselhash )
5411 jsonObjectFree( defaultselhash );
5412 clear_query_stack();
5416 growing_buffer* field_buf = buffer_init( 16 );
5417 buffer_fadd( field_buf, "\"%s\".%s",
5418 class_itr->key, order_itr->key );
5419 field_str = buffer_release( field_buf );
5422 if( ( order_by = jsonObjectGetKeyConst( onode, "direction" )) ) {
5423 const char* dir = jsonObjectGetString( order_by );
5424 if(!strncasecmp( dir, "d", 1 )) {
5425 direction = " DESC";
5429 field_str = strdup( order_itr->key );
5430 const char* dir = jsonObjectGetString( onode );
5431 if( !strncasecmp( dir, "d", 1 )) {
5432 direction = " DESC";
5441 buffer_add( order_buf, ", " );
5444 buffer_add( order_buf, field_str );
5448 buffer_add( order_buf, direction );
5450 } // end while; looping over ORDER BY expressions
5452 jsonIteratorFree( order_itr );
5454 } else if( JSON_STRING == snode->type ) {
5455 // We expect a comma-separated list of sort fields.
5456 const char* str = jsonObjectGetString( snode );
5457 if( strchr( str, ';' )) {
5458 // No semicolons allowed. It is theoretically possible for a
5459 // legitimate semicolon to occur within quotes, but it's not likely
5460 // to occur in practice in the context of an ORDER BY list.
5461 osrfLogError( OSRF_LOG_MARK, "%s: Possible attempt at SOL injection -- "
5462 "semicolon found in ORDER BY list: \"%s\"", modulename, str );
5464 osrfAppSessionStatus(
5466 OSRF_STATUS_INTERNALSERVERERROR,
5467 "osrfMethodException",
5469 "Possible attempt at SOL injection -- "
5470 "semicolon found in ORDER BY list"
5473 jsonIteratorFree( class_itr );
5474 buffer_free( order_buf );
5475 buffer_free( sql_buf );
5476 if( defaultselhash )
5477 jsonObjectFree( defaultselhash );
5478 clear_query_stack();
5481 buffer_add( order_buf, str );
5485 } // end while; looping over order_by classes
5487 jsonIteratorFree( class_itr );
5488 order_by_list = buffer_release( order_buf );
5491 osrfLogWarning( OSRF_LOG_MARK,
5492 "\"order_by\" object in a query is not a JSON_HASH or JSON_ARRAY;"
5493 "no ORDER BY generated" );
5496 if( order_by_list && *order_by_list ) {
5497 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5498 OSRF_BUFFER_ADD( sql_buf, order_by_list );
5501 free( order_by_list );
5504 const jsonObject* limit = jsonObjectGetKeyConst( rest_of_query, "limit" );
5506 const char* str = jsonObjectGetString( limit );
5516 const jsonObject* offset = jsonObjectGetKeyConst( rest_of_query, "offset" );
5518 const char* str = jsonObjectGetString( offset );
5529 if( defaultselhash )
5530 jsonObjectFree( defaultselhash );
5531 clear_query_stack();
5533 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5534 return buffer_release( sql_buf );
5537 int doJSONSearch ( osrfMethodContext* ctx ) {
5538 if(osrfMethodVerifyContext( ctx )) {
5539 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5543 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5547 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5551 if( obj_is_true( jsonObjectGetKeyConst( hash, "distinct" )))
5552 flags |= SELECT_DISTINCT;
5554 if( obj_is_true( jsonObjectGetKeyConst( hash, "no_i18n" )))
5555 flags |= DISABLE_I18N;
5557 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5558 clear_query_stack(); // a possibly needless precaution
5559 char* sql = buildQuery( ctx, hash, flags );
5560 clear_query_stack();
5567 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5570 dbhandle = writehandle;
5572 dbi_result result = dbi_conn_query( dbhandle, sql );
5575 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5577 if( dbi_result_first_row( result )) {
5578 /* JSONify the result */
5579 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5582 jsonObject* return_val = oilsMakeJSONFromResult( result );
5583 osrfAppRespond( ctx, return_val );
5584 jsonObjectFree( return_val );
5585 } while( dbi_result_next_row( result ));
5588 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5591 osrfAppRespondComplete( ctx, NULL );
5593 /* clean up the query */
5594 dbi_result_free( result );
5599 int errnum = dbi_conn_error( dbhandle, &msg );
5600 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5601 modulename, sql, errnum, msg ? msg : "(No description available)" );
5602 osrfAppSessionStatus(
5604 OSRF_STATUS_INTERNALSERVERERROR,
5605 "osrfMethodException",
5607 "Severe query error -- see error log for more details"
5609 if( !oilsIsDBConnected( dbhandle ))
5610 osrfAppSessionPanic( ctx->session );
5617 // The last parameter, err, is used to report an error condition by updating an int owned by
5618 // the calling code.
5620 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5621 // It is the responsibility of the calling code to initialize *err before the
5622 // call, so that it will be able to make sense of the result.
5624 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5625 // redundant anyway.
5626 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5627 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5630 dbhandle = writehandle;
5632 char* core_class = osrfHashGet( class_meta, "classname" );
5633 osrfLogDebug( OSRF_LOG_MARK, "entering doFieldmapperSearch() with core_class %s", core_class );
5635 char* pkey = osrfHashGet( class_meta, "primarykey" );
5637 if (!ctx->session->userData)
5638 (void) initSessionCache( ctx );
5640 char *methodtype = osrfHashGet( (osrfHash *) ctx->method->userData, "methodtype" );
5641 char *inside_verify = osrfHashGet( (osrfHash*) ctx->session->userData, "inside_verify" );
5642 int need_to_verify = (inside_verify ? !atoi(inside_verify) : 1);
5644 int i_respond_directly = 0;
5645 int flesh_depth = 0;
5647 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5649 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5654 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5656 dbi_result result = dbi_conn_query( dbhandle, sql );
5657 if( NULL == result ) {
5659 int errnum = dbi_conn_error( dbhandle, &msg );
5660 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5661 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5662 msg ? msg : "(No description available)" );
5663 if( !oilsIsDBConnected( dbhandle ))
5664 osrfAppSessionPanic( ctx->session );
5665 osrfAppSessionStatus(
5667 OSRF_STATUS_INTERNALSERVERERROR,
5668 "osrfMethodException",
5670 "Severe query error -- see error log for more details"
5677 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5680 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5681 jsonObject* row_obj = NULL;
5683 // The following two steps are for verifyObjectPCRUD()'s benefit.
5684 // 1. get the flesh depth
5685 const jsonObject* _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5687 flesh_depth = (int) jsonObjectGetNumber( _tmp );
5688 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5689 flesh_depth = max_flesh_depth;
5692 // 2. figure out one consistent rs_size for verifyObjectPCRUD to use
5693 // over the whole life of this request. This means if we've already set
5694 // up a rs_size_req_%d, do nothing.
5695 // a. Incidentally, we can also use this opportunity to set i_respond_directly
5696 int *rs_size = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
5697 if( !rs_size ) { // pointer null, so value not set in hash
5698 // i_respond_directly can only be true at the /top/ of a recursive search, if even that.
5699 i_respond_directly = ( *methodtype == 'r' || *methodtype == 'i' || *methodtype == 's' );
5701 rs_size = (int *) safe_malloc( sizeof(int) ); // will be freed by sessionDataFree()
5702 unsigned long long result_count = dbi_result_get_numrows( result );
5703 *rs_size = (int) result_count * (flesh_depth + 1); // yes, we could lose some bits, but come on
5704 osrfHashSet( (osrfHash *) ctx->session->userData, rs_size, "rs_size_req_%d", ctx->request );
5707 if( dbi_result_first_row( result )) {
5709 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5710 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5711 // eliminate the duplicates.
5712 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5713 osrfHash* dedup = osrfNewHash();
5715 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5716 char* pkey_val = oilsFMGetString( row_obj, pkey );
5717 if( osrfHashGet( dedup, pkey_val ) ) {
5718 jsonObjectFree( row_obj );
5721 if( !enforce_pcrud || !need_to_verify ||
5722 verifyObjectPCRUD( ctx, class_meta, row_obj, 0 /* means check user data for rs_size */ )) {
5723 osrfHashSet( dedup, pkey_val, pkey_val );
5724 jsonObjectPush( res_list, row_obj );
5727 } while( dbi_result_next_row( result ));
5728 osrfHashFree( dedup );
5731 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5735 /* clean up the query */
5736 dbi_result_free( result );
5739 // If we're asked to flesh, and there's anything to flesh, then flesh it
5740 // (formerly we would skip fleshing if in pcrud mode, but now we support
5741 // fleshing even in PCRUD).
5742 if( res_list->size ) {
5743 jsonObject* temp_blob; // We need a non-zero flesh depth, and a list of fields to flesh
5744 jsonObject* flesh_fields;
5745 jsonObject* flesh_blob = NULL;
5746 osrfStringArray* link_fields = NULL;
5747 osrfHash* links = NULL;
5751 temp_blob = jsonObjectGetKey( query_hash, "flesh_fields" );
5752 if( temp_blob && flesh_depth > 0 ) {
5754 flesh_blob = jsonObjectClone( temp_blob );
5755 flesh_fields = jsonObjectGetKey( flesh_blob, core_class );
5757 links = osrfHashGet( class_meta, "links" );
5759 // Make an osrfStringArray of the names of fields to be fleshed
5760 if( flesh_fields ) {
5761 if( flesh_fields->size == 1 ) {
5762 const char* _t = jsonObjectGetString(
5763 jsonObjectGetIndex( flesh_fields, 0 ) );
5764 if( !strcmp( _t, "*" ))
5765 link_fields = osrfHashKeys( links );
5768 if( !link_fields ) {
5770 link_fields = osrfNewStringArray( 1 );
5771 jsonIterator* _i = jsonNewIterator( flesh_fields );
5772 while ((_f = jsonIteratorNext( _i ))) {
5773 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5775 jsonIteratorFree( _i );
5778 want_flesh = link_fields ? 1 : 0;
5782 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5784 // Iterate over the JSON_ARRAY of rows
5786 unsigned long res_idx = 0;
5787 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5790 const char* link_field;
5792 // Iterate over the list of fleshable fields
5794 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5796 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5798 osrfHash* kid_link = osrfHashGet( links, link_field );
5800 continue; // Not a link field; skip it
5802 osrfHash* field = osrfHashGet( fields, link_field );
5804 continue; // Not a field at all; skip it (IDL is ill-formed)
5806 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5807 osrfHashGet( kid_link, "class" ));
5809 continue; // The class it links to doesn't exist; skip it
5811 const char* reltype = osrfHashGet( kid_link, "reltype" );
5813 continue; // No reltype; skip it (IDL is ill-formed)
5815 osrfHash* value_field = field;
5817 if( !strcmp( reltype, "has_many" )
5818 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5819 value_field = osrfHashGet(
5820 fields, osrfHashGet( class_meta, "primarykey" ) );
5823 int kid_has_controller = osrfStringArrayContains( osrfHashGet(kid_idl, "controller"), modulename );
5824 // fleshing pcrud case: we require the controller in need_to_verify mode
5825 if ( !kid_has_controller && enforce_pcrud && need_to_verify ) {
5826 osrfLogInfo( OSRF_LOG_MARK, "%s is not listed as a controller for %s; moving on", modulename, core_class );
5830 (unsigned long) atoi( osrfHashGet(field, "array_position") ),
5832 !strcmp( reltype, "has_many" ) ? JSON_ARRAY : JSON_NULL
5838 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5840 if( link_map->size > 0 ) {
5841 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5844 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5849 osrfHashGet( kid_link, "class" ),
5856 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5857 osrfHashGet( kid_link, "field" ),
5858 osrfHashGet( kid_link, "class" ),
5859 osrfHashGet( kid_link, "key" ),
5860 osrfHashGet( kid_link, "reltype" )
5863 const char* search_key = jsonObjectGetString(
5864 jsonObjectGetIndex( cur,
5865 atoi( osrfHashGet( value_field, "array_position" ) )
5870 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5874 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5876 // construct WHERE clause
5877 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5880 osrfHashGet( kid_link, "key" ),
5881 jsonNewObject( search_key )
5884 // construct the rest of the query, mostly
5885 // by copying pieces of the previous level of query
5886 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5887 jsonObjectSetKey( rest_of_query, "flesh",
5888 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5892 jsonObjectSetKey( rest_of_query, "flesh_fields",
5893 jsonObjectClone( flesh_blob ));
5895 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5896 jsonObjectSetKey( rest_of_query, "order_by",
5897 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5901 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5902 jsonObjectSetKey( rest_of_query, "select",
5903 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5907 // do the query, recursively, to expand the fleshable field
5908 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5909 where_clause, rest_of_query, err );
5911 jsonObjectFree( where_clause );
5912 jsonObjectFree( rest_of_query );
5915 osrfStringArrayFree( link_fields );
5916 jsonObjectFree( res_list );
5917 jsonObjectFree( flesh_blob );
5921 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5922 osrfHashGet( kid_link, "class" ), kids->size );
5924 // Traverse the result set
5925 jsonObject* X = NULL;
5926 if( link_map->size > 0 && kids->size > 0 ) {
5928 kids = jsonNewObjectType( JSON_ARRAY );
5930 jsonObject* _k_node;
5931 unsigned long res_idx = 0;
5932 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5938 (unsigned long) atoi(
5944 osrfHashGet( kid_link, "class" )
5948 osrfStringArrayGetString( link_map, 0 )
5956 } // end while loop traversing X
5959 if (kids->size > 0) {
5961 if(( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5962 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" ))
5964 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5965 osrfHashGet( kid_link, "field" ));
5968 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5969 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5974 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5976 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5977 osrfHashGet( kid_link, "field" ) );
5980 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5981 jsonObjectClone( kids )
5986 jsonObjectFree( kids );
5990 jsonObjectFree( kids );
5992 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5993 osrfHashGet( kid_link, "field" ) );
5994 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5996 } // end while loop traversing list of fleshable fields
5999 if( i_respond_directly ) {
6000 if ( *methodtype == 'i' ) {
6001 osrfAppRespond( ctx,
6002 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
6004 osrfAppRespond( ctx, cur );
6007 } // end while loop traversing res_list
6008 jsonObjectFree( flesh_blob );
6009 osrfStringArrayFree( link_fields );
6012 if( i_respond_directly ) {
6013 jsonObjectFree( res_list );
6014 return jsonNewObjectType( JSON_ARRAY );
6021 int doUpdate( osrfMethodContext* ctx ) {
6022 if( osrfMethodVerifyContext( ctx )) {
6023 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6028 timeout_needs_resetting = 1;
6030 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6032 jsonObject* target = NULL;
6034 target = jsonObjectGetIndex( ctx->params, 1 );
6036 target = jsonObjectGetIndex( ctx->params, 0 );
6038 if(!verifyObjectClass( ctx, target )) {
6039 osrfAppRespondComplete( ctx, NULL );
6043 if( getXactId( ctx ) == NULL ) {
6044 osrfAppSessionStatus(
6046 OSRF_STATUS_BADREQUEST,
6047 "osrfMethodException",
6049 "No active transaction -- required for UPDATE"
6051 osrfAppRespondComplete( ctx, NULL );
6055 // The following test is harmless but redundant. If a class is
6056 // readonly, we don't register an update method for it.
6057 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6058 osrfAppSessionStatus(
6060 OSRF_STATUS_BADREQUEST,
6061 "osrfMethodException",
6063 "Cannot UPDATE readonly class"
6065 osrfAppRespondComplete( ctx, NULL );
6069 const char* trans_id = getXactId( ctx );
6071 // Set the last_xact_id
6072 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
6074 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
6075 trans_id, target->classname, index );
6076 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
6079 char* pkey = osrfHashGet( meta, "primarykey" );
6080 osrfHash* fields = osrfHashGet( meta, "fields" );
6082 char* id = oilsFMGetString( target, pkey );
6086 "%s updating %s object with %s = %s",
6088 osrfHashGet( meta, "fieldmapper" ),
6093 dbhandle = writehandle;
6094 growing_buffer* sql = buffer_init( 128 );
6095 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
6098 osrfHash* field_def = NULL;
6099 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
6100 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
6102 // Skip virtual fields, and the primary key
6103 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
6106 if (osrfStringArrayContains( osrfHashGet(field_def, "suppress_controller"), modulename ))
6110 const char* field_name = osrfHashIteratorKey( field_itr );
6111 if( ! strcmp( field_name, pkey ) )
6114 const jsonObject* field_object = oilsFMGetObject( target, field_name );
6116 int value_is_numeric = 0; // boolean
6118 if( field_object && field_object->classname ) {
6119 value = oilsFMGetString(
6121 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
6123 } else if( field_object && JSON_BOOL == field_object->type ) {
6124 if( jsonBoolIsTrue( field_object ) )
6125 value = strdup( "t" );
6127 value = strdup( "f" );
6129 value = jsonObjectToSimpleString( field_object );
6130 if( field_object && JSON_NUMBER == field_object->type )
6131 value_is_numeric = 1;
6134 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
6135 osrfHashGet( meta, "fieldmapper" ), field_name, value);
6137 if( !field_object || field_object->type == JSON_NULL ) {
6138 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
6139 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
6143 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6144 buffer_fadd( sql, " %s = NULL", field_name );
6147 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
6151 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6153 const char* numtype = get_datatype( field_def );
6154 if( !strncmp( numtype, "INT", 3 ) ) {
6155 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
6156 } else if( !strcmp( numtype, "NUMERIC" ) ) {
6157 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
6159 // Must really be intended as a string, so quote it
6160 if( dbi_conn_quote_string( dbhandle, &value )) {
6161 buffer_fadd( sql, " %s = %s", field_name, value );
6163 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
6164 modulename, value );
6165 osrfAppSessionStatus(
6167 OSRF_STATUS_INTERNALSERVERERROR,
6168 "osrfMethodException",
6170 "Error quoting string -- please see the error log for more details"
6174 osrfHashIteratorFree( field_itr );
6176 osrfAppRespondComplete( ctx, NULL );
6181 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
6184 if( dbi_conn_quote_string( dbhandle, &value ) ) {
6188 OSRF_BUFFER_ADD_CHAR( sql, ',' );
6189 buffer_fadd( sql, " %s = %s", field_name, value );
6191 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
6192 osrfAppSessionStatus(
6194 OSRF_STATUS_INTERNALSERVERERROR,
6195 "osrfMethodException",
6197 "Error quoting string -- please see the error log for more details"
6201 osrfHashIteratorFree( field_itr );
6203 osrfAppRespondComplete( ctx, NULL );
6212 osrfHashIteratorFree( field_itr );
6214 jsonObject* obj = jsonNewObject( id );
6216 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
6217 dbi_conn_quote_string( dbhandle, &id );
6219 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
6221 char* query = buffer_release( sql );
6222 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
6224 dbi_result result = dbi_conn_query( dbhandle, query );
6229 jsonObjectFree( obj );
6230 obj = jsonNewObject( NULL );
6232 int errnum = dbi_conn_error( dbhandle, &msg );
6235 "%s ERROR updating %s object with %s = %s: %d %s",
6237 osrfHashGet( meta, "fieldmapper" ),
6241 msg ? msg : "(No description available)"
6243 osrfAppSessionStatus(
6245 OSRF_STATUS_INTERNALSERVERERROR,
6246 "osrfMethodException",
6248 "Error in updating a row -- please see the error log for more details"
6250 if( !oilsIsDBConnected( dbhandle ))
6251 osrfAppSessionPanic( ctx->session );
6254 dbi_result_free( result );
6257 osrfAppRespondComplete( ctx, obj );
6258 jsonObjectFree( obj );
6262 int doDelete( osrfMethodContext* ctx ) {
6263 if( osrfMethodVerifyContext( ctx )) {
6264 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
6269 timeout_needs_resetting = 1;
6271 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
6273 if( getXactId( ctx ) == NULL ) {
6274 osrfAppSessionStatus(
6276 OSRF_STATUS_BADREQUEST,
6277 "osrfMethodException",
6279 "No active transaction -- required for DELETE"
6281 osrfAppRespondComplete( ctx, NULL );
6285 // The following test is harmless but redundant. If a class is
6286 // readonly, we don't register a delete method for it.
6287 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
6288 osrfAppSessionStatus(
6290 OSRF_STATUS_BADREQUEST,
6291 "osrfMethodException",
6293 "Cannot DELETE readonly class"
6295 osrfAppRespondComplete( ctx, NULL );
6299 dbhandle = writehandle;
6301 char* pkey = osrfHashGet( meta, "primarykey" );
6308 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
6309 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
6310 osrfAppRespondComplete( ctx, NULL );
6314 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
6316 if( enforce_pcrud && !verifyObjectPCRUD( ctx, meta, NULL, 1 )) {
6317 osrfAppRespondComplete( ctx, NULL );
6320 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
6325 "%s deleting %s object with %s = %s",
6327 osrfHashGet( meta, "fieldmapper" ),
6332 jsonObject* obj = jsonNewObject( id );
6334 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
6335 dbi_conn_quote_string( writehandle, &id );
6337 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
6338 osrfHashGet( meta, "tablename" ), pkey, id );
6343 jsonObjectFree( obj );
6344 obj = jsonNewObject( NULL );
6346 int errnum = dbi_conn_error( writehandle, &msg );
6349 "%s ERROR deleting %s object with %s = %s: %d %s",
6351 osrfHashGet( meta, "fieldmapper" ),
6355 msg ? msg : "(No description available)"
6357 osrfAppSessionStatus(
6359 OSRF_STATUS_INTERNALSERVERERROR,
6360 "osrfMethodException",
6362 "Error in deleting a row -- please see the error log for more details"
6364 if( !oilsIsDBConnected( writehandle ))
6365 osrfAppSessionPanic( ctx->session );
6367 dbi_result_free( result );
6371 osrfAppRespondComplete( ctx, obj );
6372 jsonObjectFree( obj );
6377 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6378 @param result An iterator for a result set; we only look at the current row.
6379 @param @meta Pointer to the class metadata for the core class.
6380 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6382 If a column is not defined in the IDL, or if it has no array_position defined for it in
6383 the IDL, or if it is defined as virtual, ignore it.
6385 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6386 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6387 array_position in the IDL.
6389 A field defined in the IDL but not represented in the returned row will leave a hole
6390 in the JSON_ARRAY. In effect it will be treated as a null value.
6392 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6393 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6394 classname corresponding to the @a meta argument.
6396 The calling code is responsible for freeing the the resulting jsonObject by calling
6399 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6400 if( !( result && meta )) return NULL;
6402 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6403 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
6404 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
6406 osrfHash* fields = osrfHashGet( meta, "fields" );
6408 int columnIndex = 1;
6409 const char* columnName;
6411 /* cycle through the columns in the row returned from the database */
6412 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
6414 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6416 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6418 /* determine the field type and storage attributes */
6419 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
6420 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6422 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6423 // or if it has no sequence number there, or if it's virtual, skip it.
6424 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6427 if( str_is_true( osrfHashGet( _f, "virtual" )))
6428 continue; // skip this column: IDL says it's virtual
6430 const char* pos = (char*) osrfHashGet( _f, "array_position" );
6431 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6432 continue; // since we assign sequence numbers dynamically as we load the IDL.
6434 fmIndex = atoi( pos );
6435 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
6437 continue; // This field is not defined in the IDL
6440 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6441 // sequence number from the IDL (which is likely to be different from the sequence
6442 // of columns in the SELECT clause).
6443 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6444 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
6449 case DBI_TYPE_INTEGER :
6451 if( attr & DBI_INTEGER_SIZE8 )
6452 jsonObjectSetIndex( object, fmIndex,
6453 jsonNewNumberObject(
6454 dbi_result_get_longlong_idx( result, columnIndex )));
6456 jsonObjectSetIndex( object, fmIndex,
6457 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
6461 case DBI_TYPE_DECIMAL :
6462 jsonObjectSetIndex( object, fmIndex,
6463 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
6466 case DBI_TYPE_STRING :
6471 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
6476 case DBI_TYPE_DATETIME : {
6478 char dt_string[ 256 ] = "";
6481 // Fetch the date column as a time_t
6482 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6484 // Translate the time_t to a human-readable string
6485 if( !( attr & DBI_DATETIME_DATE )) {
6486 gmtime_r( &_tmp_dt, &gmdt );
6487 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6488 } else if( !( attr & DBI_DATETIME_TIME )) {
6489 localtime_r( &_tmp_dt, &gmdt );
6490 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6492 localtime_r( &_tmp_dt, &gmdt );
6493 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6496 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
6500 case DBI_TYPE_BINARY :
6501 osrfLogError( OSRF_LOG_MARK,
6502 "Can't do binary at column %s : index %d", columnName, columnIndex );
6511 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6512 if( !result ) return NULL;
6514 jsonObject* object = jsonNewObject( NULL );
6517 char dt_string[ 256 ];
6521 int columnIndex = 1;
6523 unsigned short type;
6524 const char* columnName;
6526 /* cycle through the column list */
6527 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6529 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6531 fmIndex = -1; // reset the position
6533 /* determine the field type and storage attributes */
6534 type = dbi_result_get_field_type_idx( result, columnIndex );
6535 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6537 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6538 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6543 case DBI_TYPE_INTEGER :
6545 if( attr & DBI_INTEGER_SIZE8 )
6546 jsonObjectSetKey( object, columnName,
6547 jsonNewNumberObject( dbi_result_get_longlong_idx(
6548 result, columnIndex )) );
6550 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6551 dbi_result_get_int_idx( result, columnIndex )) );
6554 case DBI_TYPE_DECIMAL :
6555 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6556 dbi_result_get_double_idx( result, columnIndex )) );
6559 case DBI_TYPE_STRING :
6560 jsonObjectSetKey( object, columnName,
6561 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6564 case DBI_TYPE_DATETIME :
6566 memset( dt_string, '\0', sizeof( dt_string ));
6567 memset( &gmdt, '\0', sizeof( gmdt ));
6569 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6571 if( !( attr & DBI_DATETIME_DATE )) {
6572 gmtime_r( &_tmp_dt, &gmdt );
6573 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6574 } else if( !( attr & DBI_DATETIME_TIME )) {
6575 localtime_r( &_tmp_dt, &gmdt );
6576 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%d", &gmdt );
6578 localtime_r( &_tmp_dt, &gmdt );
6579 strftime( dt_string, sizeof( dt_string ), "%04Y-%m-%dT%T%z", &gmdt );
6582 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6585 case DBI_TYPE_BINARY :
6586 osrfLogError( OSRF_LOG_MARK,
6587 "Can't do binary at column %s : index %d", columnName, columnIndex );
6591 } // end while loop traversing result
6596 // Interpret a string as true or false
6597 int str_is_true( const char* str ) {
6598 if( NULL == str || strcasecmp( str, "true" ) )
6604 // Interpret a jsonObject as true or false
6605 static int obj_is_true( const jsonObject* obj ) {
6608 else switch( obj->type )
6616 if( strcasecmp( obj->value.s, "true" ) )
6620 case JSON_NUMBER : // Support 1/0 for perl's sake
6621 if( jsonObjectGetNumber( obj ) == 1.0 )
6630 // Translate a numeric code into a text string identifying a type of
6631 // jsonObject. To be used for building error messages.
6632 static const char* json_type( int code ) {
6638 return "JSON_ARRAY";
6640 return "JSON_STRING";
6642 return "JSON_NUMBER";
6648 return "(unrecognized)";
6652 // Extract the "primitive" attribute from an IDL field definition.
6653 // If we haven't initialized the app, then we must be running in
6654 // some kind of testbed. In that case, default to "string".
6655 static const char* get_primitive( osrfHash* field ) {
6656 const char* s = osrfHashGet( field, "primitive" );
6658 if( child_initialized )
6661 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6663 osrfHashGet( field, "name" )
6671 // Extract the "datatype" attribute from an IDL field definition.
6672 // If we haven't initialized the app, then we must be running in
6673 // some kind of testbed. In that case, default to to NUMERIC,
6674 // since we look at the datatype only for numbers.
6675 static const char* get_datatype( osrfHash* field ) {
6676 const char* s = osrfHashGet( field, "datatype" );
6678 if( child_initialized )
6681 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6683 osrfHashGet( field, "name" )
6692 @brief Determine whether a string is potentially a valid SQL identifier.
6693 @param s The identifier to be tested.
6694 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6696 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6697 need to follow all the rules exactly, such as requiring that the first character not
6700 We allow leading and trailing white space. In between, we do not allow punctuation
6701 (except for underscores and dollar signs), control characters, or embedded white space.
6703 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6704 for the foreseeable future such quoted identifiers are not likely to be an issue.
6706 int is_identifier( const char* s) {
6710 // Skip leading white space
6711 while( isspace( (unsigned char) *s ) )
6715 return 0; // Nothing but white space? Not okay.
6717 // Check each character until we reach white space or
6718 // end-of-string. Letters, digits, underscores, and
6719 // dollar signs are okay. With the exception of periods
6720 // (as in schema.identifier), control characters and other
6721 // punctuation characters are not okay. Anything else
6722 // is okay -- it could for example be part of a multibyte
6723 // UTF8 character such as a letter with diacritical marks,
6724 // and those are allowed.
6726 if( isalnum( (unsigned char) *s )
6730 ; // Fine; keep going
6731 else if( ispunct( (unsigned char) *s )
6732 || iscntrl( (unsigned char) *s ) )
6735 } while( *s && ! isspace( (unsigned char) *s ) );
6737 // If we found any white space in the above loop,
6738 // the rest had better be all white space.
6740 while( isspace( (unsigned char) *s ) )
6744 return 0; // White space was embedded within non-white space
6750 @brief Determine whether to accept a character string as a comparison operator.
6751 @param op The candidate comparison operator.
6752 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6754 We don't validate the operator for real. We just make sure that it doesn't contain
6755 any semicolons or white space (with special exceptions for a few specific operators).
6756 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6757 space but it's still not a valid operator, then the database will complain.
6759 Another approach would be to compare the string against a short list of approved operators.
6760 We don't do that because we want to allow custom operators like ">100*", which at this
6761 writing would be difficult or impossible to express otherwise in a JSON query.
6763 int is_good_operator( const char* op ) {
6764 if( !op ) return 0; // Sanity check
6768 if( isspace( (unsigned char) *s ) ) {
6769 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6770 // and IS NOT DISTINCT FROM.
6771 if( !strcasecmp( op, "similar to" ) )
6773 else if( !strcasecmp( op, "is distinct from" ) )
6775 else if( !strcasecmp( op, "is not distinct from" ) )
6780 else if( ';' == *s )
6788 @name Query Frame Management
6790 The following machinery supports a stack of query frames for use by SELECT().
6792 A query frame caches information about one level of a SELECT query. When we enter
6793 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6795 The query frame stores information about the core class, and about any joined classes
6798 The main purpose is to map table aliases to classes and tables, so that a query can
6799 join to the same table more than once. A secondary goal is to reduce the number of
6800 lookups in the IDL by caching the results.
6804 #define STATIC_CLASS_INFO_COUNT 3
6806 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6809 @brief Allocate a ClassInfo as raw memory.
6810 @return Pointer to the newly allocated ClassInfo.
6812 Except for the in_use flag, which is used only by the allocation and deallocation
6813 logic, we don't initialize the ClassInfo here.
6815 static ClassInfo* allocate_class_info( void ) {
6816 // In order to reduce the number of mallocs and frees, we return a static
6817 // instance of ClassInfo, if we can find one that we're not already using.
6818 // We rely on the fact that the compiler will implicitly initialize the
6819 // static instances so that in_use == 0.
6822 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6823 if( ! static_class_info[ i ].in_use ) {
6824 static_class_info[ i ].in_use = 1;
6825 return static_class_info + i;
6829 // The static ones are all in use. Malloc one.
6831 return safe_malloc( sizeof( ClassInfo ) );
6835 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6836 @param info Pointer to the ClassInfo to be cleared.
6838 static void clear_class_info( ClassInfo* info ) {
6843 // Free any malloc'd strings
6845 if( info->alias != info->alias_store )
6846 free( info->alias );
6848 if( info->class_name != info->class_name_store )
6849 free( info->class_name );
6851 free( info->source_def );
6853 info->alias = info->class_name = info->source_def = NULL;
6858 @brief Free a ClassInfo and everything it owns.
6859 @param info Pointer to the ClassInfo to be freed.
6861 static void free_class_info( ClassInfo* info ) {
6866 clear_class_info( info );
6868 // If it's one of the static instances, just mark it as not in use
6871 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6872 if( info == static_class_info + i ) {
6873 static_class_info[ i ].in_use = 0;
6878 // Otherwise it must have been malloc'd, so free it
6884 @brief Populate an already-allocated ClassInfo.
6885 @param info Pointer to the ClassInfo to be populated.
6886 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6888 @param class Name of the class.
6889 @return Zero if successful, or 1 if not.
6891 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6892 the relevant portions of the IDL for the specified class.
6894 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6897 osrfLogError( OSRF_LOG_MARK,
6898 "%s ERROR: No ClassInfo available to populate", modulename );
6899 info->alias = info->class_name = info->source_def = NULL;
6900 info->class_def = info->fields = info->links = NULL;
6905 osrfLogError( OSRF_LOG_MARK,
6906 "%s ERROR: No class name provided for lookup", modulename );
6907 info->alias = info->class_name = info->source_def = NULL;
6908 info->class_def = info->fields = info->links = NULL;
6912 // Alias defaults to class name if not supplied
6913 if( ! alias || ! alias[ 0 ] )
6916 // Look up class info in the IDL
6917 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6919 osrfLogError( OSRF_LOG_MARK,
6920 "%s ERROR: Class %s not defined in IDL", modulename, class );
6921 info->alias = info->class_name = info->source_def = NULL;
6922 info->class_def = info->fields = info->links = NULL;
6924 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6925 osrfLogError( OSRF_LOG_MARK,
6926 "%s ERROR: Class %s is defined as virtual", modulename, class );
6927 info->alias = info->class_name = info->source_def = NULL;
6928 info->class_def = info->fields = info->links = NULL;
6932 osrfHash* links = osrfHashGet( class_def, "links" );
6934 osrfLogError( OSRF_LOG_MARK,
6935 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6936 info->alias = info->class_name = info->source_def = NULL;
6937 info->class_def = info->fields = info->links = NULL;
6941 osrfHash* fields = osrfHashGet( class_def, "fields" );
6943 osrfLogError( OSRF_LOG_MARK,
6944 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6945 info->alias = info->class_name = info->source_def = NULL;
6946 info->class_def = info->fields = info->links = NULL;
6950 char* source_def = oilsGetRelation( class_def );
6954 // We got everything we need, so populate the ClassInfo
6955 if( strlen( alias ) > ALIAS_STORE_SIZE )
6956 info->alias = strdup( alias );
6958 strcpy( info->alias_store, alias );
6959 info->alias = info->alias_store;
6962 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6963 info->class_name = strdup( class );
6965 strcpy( info->class_name_store, class );
6966 info->class_name = info->class_name_store;
6969 info->source_def = source_def;
6971 info->class_def = class_def;
6972 info->links = links;
6973 info->fields = fields;
6978 #define STATIC_FRAME_COUNT 3
6980 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6983 @brief Allocate a QueryFrame as raw memory.
6984 @return Pointer to the newly allocated QueryFrame.
6986 Except for the in_use flag, which is used only by the allocation and deallocation
6987 logic, we don't initialize the QueryFrame here.
6989 static QueryFrame* allocate_frame( void ) {
6990 // In order to reduce the number of mallocs and frees, we return a static
6991 // instance of QueryFrame, if we can find one that we're not already using.
6992 // We rely on the fact that the compiler will implicitly initialize the
6993 // static instances so that in_use == 0.
6996 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6997 if( ! static_frame[ i ].in_use ) {
6998 static_frame[ i ].in_use = 1;
6999 return static_frame + i;
7003 // The static ones are all in use. Malloc one.
7005 return safe_malloc( sizeof( QueryFrame ) );
7009 @brief Free a QueryFrame, and all the memory it owns.
7010 @param frame Pointer to the QueryFrame to be freed.
7012 static void free_query_frame( QueryFrame* frame ) {
7017 clear_class_info( &frame->core );
7019 // Free the join list
7021 ClassInfo* info = frame->join_list;
7024 free_class_info( info );
7028 frame->join_list = NULL;
7031 // If the frame is a static instance, just mark it as unused
7033 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
7034 if( frame == static_frame + i ) {
7035 static_frame[ i ].in_use = 0;
7040 // Otherwise it must have been malloc'd, so free it
7046 @brief Search a given QueryFrame for a specified alias.
7047 @param frame Pointer to the QueryFrame to be searched.
7048 @param target The alias for which to search.
7049 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
7051 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
7052 if( ! frame || ! target ) {
7056 ClassInfo* found_class = NULL;
7058 if( !strcmp( target, frame->core.alias ) )
7059 return &(frame->core);
7061 ClassInfo* curr_class = frame->join_list;
7062 while( curr_class ) {
7063 if( strcmp( target, curr_class->alias ) )
7064 curr_class = curr_class->next;
7066 found_class = curr_class;
7076 @brief Push a new (blank) QueryFrame onto the stack.
7078 static void push_query_frame( void ) {
7079 QueryFrame* frame = allocate_frame();
7080 frame->join_list = NULL;
7081 frame->next = curr_query;
7083 // Initialize the ClassInfo for the core class
7084 ClassInfo* core = &frame->core;
7085 core->alias = core->class_name = core->source_def = NULL;
7086 core->class_def = core->fields = core->links = NULL;
7092 @brief Pop a QueryFrame off the stack and destroy it.
7094 static void pop_query_frame( void ) {
7099 QueryFrame* popped = curr_query;
7100 curr_query = popped->next;
7102 free_query_frame( popped );
7106 @brief Populate the ClassInfo for the core class.
7107 @param alias Alias for the core class. If it is NULL or an empty string, we use the
7108 class name as an alias.
7109 @param class_name Name of the core class.
7110 @return Zero if successful, or 1 if not.
7112 Populate the ClassInfo of the core class with copies of the alias and class name, and
7113 with pointers to the relevant portions of the IDL for the core class.
7115 static int add_query_core( const char* alias, const char* class_name ) {
7118 if( ! curr_query ) {
7119 osrfLogError( OSRF_LOG_MARK,
7120 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
7122 } else if( curr_query->core.alias ) {
7123 osrfLogError( OSRF_LOG_MARK,
7124 "%s ERROR: Core class %s already populated as %s",
7125 modulename, curr_query->core.class_name, curr_query->core.alias );
7129 build_class_info( &curr_query->core, alias, class_name );
7130 if( curr_query->core.alias )
7133 osrfLogError( OSRF_LOG_MARK,
7134 "%s ERROR: Unable to look up core class %s", modulename, class_name );
7140 @brief Search the current QueryFrame for a specified alias.
7141 @param target The alias for which to search.
7142 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7144 static inline ClassInfo* search_alias( const char* target ) {
7145 return search_alias_in_frame( curr_query, target );
7149 @brief Search all levels of query for a specified alias, starting with the current query.
7150 @param target The alias for which to search.
7151 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
7153 static ClassInfo* search_all_alias( const char* target ) {
7154 ClassInfo* found_class = NULL;
7155 QueryFrame* curr_frame = curr_query;
7157 while( curr_frame ) {
7158 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
7161 curr_frame = curr_frame->next;
7168 @brief Add a class to the list of classes joined to the current query.
7169 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
7170 the class name as an alias.
7171 @param classname The name of the class to be added.
7172 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
7174 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
7176 if( ! classname || ! *classname ) { // sanity check
7177 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
7184 const ClassInfo* conflict = search_alias( alias );
7186 osrfLogError( OSRF_LOG_MARK,
7187 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
7188 modulename, alias, conflict->class_name );
7192 ClassInfo* info = allocate_class_info();
7194 if( build_class_info( info, alias, classname ) ) {
7195 free_class_info( info );
7199 // Add the new ClassInfo to the join list of the current QueryFrame
7200 info->next = curr_query->join_list;
7201 curr_query->join_list = info;
7207 @brief Destroy all nodes on the query stack.
7209 static void clear_query_stack( void ) {
7215 @brief Implement the set_audit_info method.
7216 @param ctx Pointer to the method context.
7217 @return Zero if successful, or -1 if not.
7219 Issue a SAVEPOINT to the database server.
7224 - workstation id (int)
7226 If user id is not provided the authkey will be used.
7227 For PCRUD the authkey is always used, even if a user is provided.
7229 int setAuditInfo( osrfMethodContext* ctx ) {
7230 if(osrfMethodVerifyContext( ctx )) {
7231 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
7235 // Get the user id from the parameters
7236 const char* user_id = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 1) );
7238 if( enforce_pcrud || !user_id ) {
7239 timeout_needs_resetting = 1;
7240 const jsonObject* user = verifyUserPCRUD( ctx );
7243 osrfAppRespondComplete( ctx, NULL );
7247 // Not PCRUD and have a user_id?
7248 int result = writeAuditInfo( ctx, user_id, jsonObjectGetString( jsonObjectGetIndex(ctx->params, 2) ) );
7249 osrfAppRespondComplete( ctx, NULL );
7254 @brief Save a audit info
7255 @param ctx Pointer to the method context.
7256 @param user_id User ID to write as a string
7257 @param ws_id Workstation ID to write as a string
7259 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id) {
7260 if( ctx && ctx->session ) {
7261 osrfAppSession* session = ctx->session;
7263 osrfHash* cache = session->userData;
7265 // If the session doesn't already have a hash, create one. Make sure
7266 // that the application session frees the hash when it terminates.
7267 if( NULL == cache ) {
7268 session->userData = cache = osrfNewHash();
7269 osrfHashSetCallback( cache, &sessionDataFree );
7270 ctx->session->userDataFree = &userDataFree;
7273 dbi_result result = dbi_conn_queryf( writehandle, "SELECT auditor.set_audit_info( %s, %s );", user_id, ws_id ? ws_id : "NULL" );
7275 osrfLogWarning( OSRF_LOG_MARK, "BAD RESULT" );
7277 int errnum = dbi_conn_error( writehandle, &msg );
7280 "%s: Error setting auditor information: %d %s",
7283 msg ? msg : "(No description available)"
7285 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
7286 "osrfMethodException", ctx->request, "Error setting auditor info" );
7287 if( !oilsIsDBConnected( writehandle ))
7288 osrfAppSessionPanic( ctx->session );
7291 dbi_result_free( result );
7298 @brief Remove all but safe character from savepoint name
7299 @param sp User-supplied savepoint name
7300 @return sanitized savepoint name, or NULL
7302 The caller is expected to free the returned string. Note that
7303 this function exists only because we can't use PQescapeLiteral
7304 without either forking libdbi or abandoning it.
7306 static char* _sanitize_savepoint_name( const char* sp ) {
7308 const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_";
7310 // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
7311 // and the default value of NAMEDATALEN is 64; that should be long enough
7312 // for our purposes, and it's unlikely that anyone is going to recompile
7313 // PostgreSQL to have a smaller value, so cap the identifier name
7314 // accordingly to avoid the remote chance that someone manages to pass in a
7315 // 12GB savepoint name
7316 const int MAX_LITERAL_NAMELEN = 63;
7319 if (len > MAX_LITERAL_NAMELEN) {
7320 len = MAX_LITERAL_NAMELEN;
7323 char* safeSpName = safe_malloc( len + 1 );
7327 for (j = 0; j < len; j++) {
7328 found = strchr(safe_chars, sp[j]);
7330 safeSpName[ i++ ] = found[0];
7333 safeSpName[ i ] = '\0';