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*, const ClassInfo*, int, osrfMethodContext* );
94 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
95 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
97 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
99 void userDataFree( void* );
100 static void sessionDataFree( char*, void* );
101 static int obj_is_true( const jsonObject* obj );
102 static const char* json_type( int code );
103 static const char* get_primitive( osrfHash* field );
104 static const char* get_datatype( osrfHash* field );
105 static int is_identifier( const char* s );
106 static int is_good_operator( const char* op );
107 static void pop_query_frame( void );
108 static void push_query_frame( void );
109 static int add_query_core( const char* alias, const char* class_name );
110 static inline ClassInfo* search_alias( const char* target );
111 static ClassInfo* search_all_alias( const char* target );
112 static ClassInfo* add_joined_class( const char* alias, const char* classname );
113 static void clear_query_stack( void );
115 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
116 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
117 static char* org_tree_root( osrfMethodContext* ctx );
118 static jsonObject* single_hash( const char* key, const char* value );
120 static int child_initialized = 0; /* boolean */
122 static dbi_conn writehandle; /* our MASTER db connection */
123 static dbi_conn dbhandle; /* our CURRENT db connection */
124 //static osrfHash * readHandles;
126 // The following points to the top of a stack of QueryFrames. It's a little
127 // confusing because the top level of the query is at the bottom of the stack.
128 static QueryFrame* curr_query = NULL;
130 static dbi_conn writehandle; /* our MASTER db connection */
131 static dbi_conn dbhandle; /* our CURRENT db connection */
132 //static osrfHash * readHandles;
134 static int max_flesh_depth = 100;
136 static int enforce_pcrud = 0; // Boolean
137 static char* modulename = NULL;
140 @brief Connect to the database.
141 @return A database connection if successful, or NULL if not.
143 dbi_conn oilsConnectDB( const char* mod_name ) {
145 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
146 if( dbi_initialize( NULL ) == -1 ) {
147 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
150 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
152 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
153 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
154 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
155 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
156 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
157 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
159 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
160 dbi_conn handle = dbi_conn_new( driver );
163 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
166 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
168 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
169 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
171 if( host ) dbi_conn_set_option( handle, "host", host );
172 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
173 if( user ) dbi_conn_set_option( handle, "username", user );
174 if( pw ) dbi_conn_set_option( handle, "password", pw );
175 if( db ) dbi_conn_set_option( handle, "dbname", db );
183 if( dbi_conn_connect( handle ) < 0 ) {
185 if( dbi_conn_connect( handle ) < 0 ) {
187 dbi_conn_error( handle, &errmsg );
188 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", errmsg );
193 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
199 @brief Select some options.
200 @param module_name: Name of the server.
201 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
203 This source file is used (at this writing) to implement three different servers:
204 - open-ils.reporter-store
208 These servers behave mostly the same, but they implement different combinations of
209 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
211 Here we use the server name in messages to identify which kind of server issued them.
212 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
214 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
216 module_name = "open-ils.cstore"; // bulletproofing with a default
221 modulename = strdup( module_name );
222 enforce_pcrud = do_pcrud;
223 max_flesh_depth = flesh_depth;
227 @brief Install a database connection.
228 @param conn Pointer to a database connection.
230 In some contexts, @a conn may merely provide a driver so that we can process strings
231 properly, without providing an open database connection.
233 void oilsSetDBConnection( dbi_conn conn ) {
234 dbhandle = writehandle = conn;
238 @brief Get a table name, view name, or subquery for use in a FROM clause.
239 @param class Pointer to the IDL class entry.
240 @return A table name, a view name, or a subquery in parentheses.
242 In some cases the IDL defines a class, not with a table name or a view name, but with
243 a SELECT statement, which may be used as a subquery.
245 char* oilsGetRelation( osrfHash* classdef ) {
247 char* source_def = NULL;
248 const char* tabledef = osrfHashGet( classdef, "tablename" );
251 source_def = strdup( tabledef ); // Return the name of a table or view
253 tabledef = osrfHashGet( classdef, "source_definition" );
255 // Return a subquery, enclosed in parentheses
256 source_def = safe_malloc( strlen( tabledef ) + 3 );
257 source_def[ 0 ] = '(';
258 strcpy( source_def + 1, tabledef );
259 strcat( source_def, ")" );
261 // Not found: return an error
262 const char* classname = osrfHashGet( classdef, "classname" );
267 "%s ERROR No tablename or source_definition for class \"%s\"",
278 @brief Add datatypes from the database to the fields in the IDL.
279 @param handle Handle for a database connection
280 @return Zero if successful, or 1 upon error.
282 For each relevant class in the IDL: ask the database for the datatype of every field.
283 In particular, determine which fields are text fields and which fields are numeric
284 fields, so that we know whether to enclose their values in quotes.
286 int oilsExtendIDL( dbi_conn handle ) {
287 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
288 osrfHash* class = NULL;
289 growing_buffer* query_buf = buffer_init( 64 );
290 int results_found = 0; // boolean
292 // For each class in the IDL...
293 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
294 const char* classname = osrfHashIteratorKey( class_itr );
295 osrfHash* fields = osrfHashGet( class, "fields" );
297 // If the class is virtual, ignore it
298 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
299 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
303 char* tabledef = oilsGetRelation( class );
305 continue; // No such relation -- a query of it would be doomed to failure
307 buffer_reset( query_buf );
308 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
312 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
313 modulename, OSRF_BUFFER_C_STR( query_buf ) );
315 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
320 const char* columnName;
321 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
323 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
326 /* fetch the fieldmapper index */
327 osrfHash* _f = osrfHashGet(fields, columnName);
330 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
332 /* determine the field type and storage attributes */
334 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
336 case DBI_TYPE_INTEGER : {
338 if( !osrfHashGet(_f, "primitive") )
339 osrfHashSet(_f, "number", "primitive");
341 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
342 if( attr & DBI_INTEGER_SIZE8 )
343 osrfHashSet( _f, "INT8", "datatype" );
345 osrfHashSet( _f, "INT", "datatype" );
348 case DBI_TYPE_DECIMAL :
349 if( !osrfHashGet( _f, "primitive" ))
350 osrfHashSet( _f, "number", "primitive" );
352 osrfHashSet( _f, "NUMERIC", "datatype" );
355 case DBI_TYPE_STRING :
356 if( !osrfHashGet( _f, "primitive" ))
357 osrfHashSet( _f, "string", "primitive" );
359 osrfHashSet( _f,"TEXT", "datatype" );
362 case DBI_TYPE_DATETIME :
363 if( !osrfHashGet( _f, "primitive" ))
364 osrfHashSet( _f, "string", "primitive" );
366 osrfHashSet( _f, "TIMESTAMP", "datatype" );
369 case DBI_TYPE_BINARY :
370 if( !osrfHashGet( _f, "primitive" ))
371 osrfHashSet( _f, "string", "primitive" );
373 osrfHashSet( _f, "BYTEA", "datatype" );
378 "Setting [%s] to primitive [%s] and datatype [%s]...",
380 osrfHashGet( _f, "primitive" ),
381 osrfHashGet( _f, "datatype" )
385 } // end while loop for traversing columns of result
386 dbi_result_free( result );
388 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]...", classname );
390 } // end for each class in IDL
392 buffer_free( query_buf );
393 osrfHashIteratorFree( class_itr );
394 child_initialized = 1;
396 if( !results_found ) {
397 osrfLogError( OSRF_LOG_MARK,
398 "No results found for any class -- bad database connection?" );
406 @brief Free an osrfHash that stores a transaction ID.
407 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
409 This function is a callback, to be called by the application session when it ends.
410 The application session stores the osrfHash via an opaque pointer.
412 If the osrfHash contains an entry for the key "xact_id", it means that an
413 uncommitted transaction is pending. Roll it back.
415 void userDataFree( void* blob ) {
416 osrfHash* hash = (osrfHash*) blob;
417 if( osrfHashGet( hash, "xact_id" ) && writehandle )
418 dbi_conn_query( writehandle, "ROLLBACK;" );
420 osrfHashFree( hash );
424 @name Managing session data
425 @brief Maintain data stored via the userData pointer of the application session.
427 Currently, session-level data is stored in an osrfHash. Other arrangements are
428 possible, and some would be more efficient. The application session calls a
429 callback function to free userData before terminating.
431 Currently, the only data we store at the session level is the transaction id. By this
432 means we can ensure that any pending transactions are rolled back before the application
438 @brief Free an item in the application session's userData.
439 @param key The name of a key for an osrfHash.
440 @param item An opaque pointer to the item associated with the key.
442 We store an osrfHash as userData with the application session, and arrange (by
443 installing userDataFree() as a different callback) for the session to free that
444 osrfHash before terminating.
446 This function is a callback for freeing items in the osrfHash. Currently we store
448 - Transaction id of a pending transaction; a character string. Key: "xact_id".
449 - Authkey; a character string. Key: "authkey".
450 - User object from the authentication server; a jsonObject. Key: "user_login".
452 If we ever store anything else in userData, we will need to revisit this function so
453 that it will free whatever else needs freeing.
455 static void sessionDataFree( char* key, void* item ) {
456 if( !strcmp( key, "xact_id" )
457 || !strcmp( key, "authkey" ) ) {
459 } else if( !strcmp( key, "user_login" ) )
460 jsonObjectFree( (jsonObject*) item );
464 @brief Save a transaction id.
465 @param ctx Pointer to the method context.
467 Save the session_id of the current application session as a transaction id.
469 static void setXactId( osrfMethodContext* ctx ) {
470 if( ctx && ctx->session ) {
471 osrfAppSession* session = ctx->session;
473 osrfHash* cache = session->userData;
475 // If the session doesn't already have a hash, create one. Make sure
476 // that the application session frees the hash when it terminates.
477 if( NULL == cache ) {
478 session->userData = cache = osrfNewHash();
479 osrfHashSetCallback( cache, &sessionDataFree );
480 ctx->session->userDataFree = &userDataFree;
483 // Save the transaction id in the hash, with the key "xact_id"
484 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
489 @brief Get the transaction ID for the current transaction, if any.
490 @param ctx Pointer to the method context.
491 @return Pointer to the transaction ID.
493 The return value points to an internal buffer, and will become invalid upon issuing
494 a commit or rollback.
496 static inline const char* getXactId( osrfMethodContext* ctx ) {
497 if( ctx && ctx->session && ctx->session->userData )
498 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
504 @brief Clear the current transaction id.
505 @param ctx Pointer to the method context.
507 static inline void clearXactId( osrfMethodContext* ctx ) {
508 if( ctx && ctx->session && ctx->session->userData )
509 osrfHashRemove( ctx->session->userData, "xact_id" );
514 @brief Save the user's login in the userData for the current application session.
515 @param ctx Pointer to the method context.
516 @param user_login Pointer to the user login object to be cached (we cache the original,
519 If @a user_login is NULL, remove the user login if one is already cached.
521 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
522 if( ctx && ctx->session ) {
523 osrfAppSession* session = ctx->session;
525 osrfHash* cache = session->userData;
527 // If the session doesn't already have a hash, create one. Make sure
528 // that the application session frees the hash when it terminates.
529 if( NULL == cache ) {
530 session->userData = cache = osrfNewHash();
531 osrfHashSetCallback( cache, &sessionDataFree );
532 ctx->session->userDataFree = &userDataFree;
536 osrfHashSet( cache, user_login, "user_login" );
538 osrfHashRemove( cache, "user_login" );
543 @brief Get the user login object for the current application session, if any.
544 @param ctx Pointer to the method context.
545 @return Pointer to the user login object if found; otherwise NULL.
547 The user login object was returned from the authentication server, and then cached so
548 we don't have to call the authentication server again for the same user.
550 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
551 if( ctx && ctx->session && ctx->session->userData )
552 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
558 @brief Save a copy of an authkey in the userData of the current application session.
559 @param ctx Pointer to the method context.
560 @param authkey The authkey to be saved.
562 If @a authkey is NULL, remove the authkey if one is already cached.
564 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
565 if( ctx && ctx->session && authkey ) {
566 osrfAppSession* session = ctx->session;
567 osrfHash* cache = session->userData;
569 // If the session doesn't already have a hash, create one. Make sure
570 // that the application session frees the hash when it terminates.
571 if( NULL == cache ) {
572 session->userData = cache = osrfNewHash();
573 osrfHashSetCallback( cache, &sessionDataFree );
574 ctx->session->userDataFree = &userDataFree;
577 // Save the transaction id in the hash, with the key "xact_id"
578 if( authkey && *authkey )
579 osrfHashSet( cache, strdup( authkey ), "authkey" );
581 osrfHashRemove( cache, "authkey" );
586 @brief Reset the login timeout.
587 @param authkey The authentication key for the current login session.
588 @param now The current time.
589 @return Zero if successful, or 1 if not.
591 Tell the authentication server to reset the timeout so that the login session won't
592 expire for a while longer.
594 We could dispense with the @a now parameter by calling time(). But we just called
595 time() in order to decide whether to reset the timeout, so we might as well reuse
596 the result instead of calling time() again.
598 static int reset_timeout( const char* authkey, time_t now ) {
599 jsonObject* auth_object = jsonNewObject( authkey );
601 // Ask the authentication server to reset the timeout. It returns an event
602 // indicating success or failure.
603 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
604 "open-ils.auth.session.reset_timeout", auth_object );
605 jsonObjectFree( auth_object );
607 if( !result || result->type != JSON_HASH ) {
608 osrfLogError( OSRF_LOG_MARK,
609 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
610 jsonObjectFree( result );
611 return 1; // Not the right sort of object returned
614 const jsonObject* ilsevent = jsonObjectGetKey( result, "ilsevent" );
615 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
616 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
617 jsonObjectFree( result );
618 return 1; // Return code from method not available
621 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
622 const char* desc = jsonObjectGetString( jsonObjectGetKey( result, "desc" ) );
624 desc = "(No reason available)"; // failsafe; shouldn't happen
625 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
626 jsonObjectFree( result );
630 // Revise our local proxy for the timeout deadline
631 // by a smallish fraction of the timeout interval
632 const char* timeout = jsonObjectGetString( jsonObjectGetKey( result, "payload" ) );
634 timeout = "1"; // failsafe; shouldn't happen
635 time_next_reset = now + atoi( timeout ) / 15;
637 jsonObjectFree( result );
638 return 0; // Successfully reset timeout
642 @brief Get the authkey string for the current application session, if any.
643 @param ctx Pointer to the method context.
644 @return Pointer to the cached authkey if found; otherwise NULL.
646 If present, the authkey string was cached from a previous method call.
648 static const char* getAuthkey( osrfMethodContext* ctx ) {
649 if( ctx && ctx->session && ctx->session->userData ) {
650 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
652 // Possibly reset the authentication timeout to keep the login alive. We do so
653 // no more than once per method call, and not at all if it has been only a short
654 // time since the last reset.
656 // Here we reset explicitly, if at all. We also implicitly reset the timeout
657 // whenever we call the "open-ils.auth.session.retrieve" method.
658 if( timeout_needs_resetting ) {
659 time_t now = time( NULL );
660 if( now >= time_next_reset && reset_timeout( authkey, now ) )
661 authkey = NULL; // timeout has apparently expired already
664 timeout_needs_resetting = 0;
672 @brief Implement the transaction.begin method.
673 @param ctx Pointer to the method context.
674 @return Zero if successful, or -1 upon error.
676 Start a transaction. Save a transaction ID for future reference.
679 - authkey (PCRUD only)
681 Return to client: Transaction ID
683 int beginTransaction( osrfMethodContext* ctx ) {
684 if(osrfMethodVerifyContext( ctx )) {
685 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
689 if( enforce_pcrud ) {
690 timeout_needs_resetting = 1;
691 const jsonObject* user = verifyUserPCRUD( ctx );
696 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
698 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction", modulename );
699 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
700 "osrfMethodException", ctx->request, "Error starting transaction" );
704 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
705 osrfAppRespondComplete( ctx, ret );
706 jsonObjectFree( ret );
712 @brief Implement the savepoint.set method.
713 @param ctx Pointer to the method context.
714 @return Zero if successful, or -1 if not.
716 Issue a SAVEPOINT to the database server.
719 - authkey (PCRUD only)
722 Return to client: Savepoint name
724 int setSavepoint( osrfMethodContext* ctx ) {
725 if(osrfMethodVerifyContext( ctx )) {
726 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
731 if( enforce_pcrud ) {
733 timeout_needs_resetting = 1;
734 const jsonObject* user = verifyUserPCRUD( ctx );
739 // Verify that a transaction is pending
740 const char* trans_id = getXactId( ctx );
741 if( NULL == trans_id ) {
742 osrfAppSessionStatus(
744 OSRF_STATUS_INTERNALSERVERERROR,
745 "osrfMethodException",
747 "No active transaction -- required for savepoints"
752 // Get the savepoint name from the method params
753 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
755 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
759 "%s: Error creating savepoint %s in transaction %s",
764 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
765 "osrfMethodException", ctx->request, "Error creating savepoint" );
768 jsonObject* ret = jsonNewObject( spName );
769 osrfAppRespondComplete( ctx, ret );
770 jsonObjectFree( ret );
776 @brief Implement the savepoint.release method.
777 @param ctx Pointer to the method context.
778 @return Zero if successful, or -1 if not.
780 Issue a RELEASE SAVEPOINT to the database server.
783 - authkey (PCRUD only)
786 Return to client: Savepoint name
788 int releaseSavepoint( osrfMethodContext* ctx ) {
789 if(osrfMethodVerifyContext( ctx )) {
790 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
795 if( enforce_pcrud ) {
797 timeout_needs_resetting = 1;
798 const jsonObject* user = verifyUserPCRUD( ctx );
803 // Verify that a transaction is pending
804 const char* trans_id = getXactId( ctx );
805 if( NULL == trans_id ) {
806 osrfAppSessionStatus(
808 OSRF_STATUS_INTERNALSERVERERROR,
809 "osrfMethodException",
811 "No active transaction -- required for savepoints"
816 // Get the savepoint name from the method params
817 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
819 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
823 "%s: Error releasing savepoint %s in transaction %s",
828 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
829 "osrfMethodException", ctx->request, "Error releasing savepoint" );
832 jsonObject* ret = jsonNewObject( spName );
833 osrfAppRespondComplete( ctx, ret );
834 jsonObjectFree( ret );
840 @brief Implement the savepoint.rollback method.
841 @param ctx Pointer to the method context.
842 @return Zero if successful, or -1 if not.
844 Issue a ROLLBACK TO SAVEPOINT to the database server.
847 - authkey (PCRUD only)
850 Return to client: Savepoint name
852 int rollbackSavepoint( osrfMethodContext* ctx ) {
853 if(osrfMethodVerifyContext( ctx )) {
854 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
859 if( enforce_pcrud ) {
861 timeout_needs_resetting = 1;
862 const jsonObject* user = verifyUserPCRUD( ctx );
867 // Verify that a transaction is pending
868 const char* trans_id = getXactId( ctx );
869 if( NULL == trans_id ) {
870 osrfAppSessionStatus(
872 OSRF_STATUS_INTERNALSERVERERROR,
873 "osrfMethodException",
875 "No active transaction -- required for savepoints"
880 // Get the savepoint name from the method params
881 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
883 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
887 "%s: Error rolling back savepoint %s in transaction %s",
892 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
893 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
896 jsonObject* ret = jsonNewObject( spName );
897 osrfAppRespondComplete( ctx, ret );
898 jsonObjectFree( ret );
904 @brief Implement the transaction.commit method.
905 @param ctx Pointer to the method context.
906 @return Zero if successful, or -1 if not.
908 Issue a COMMIT to the database server.
911 - authkey (PCRUD only)
913 Return to client: Transaction ID.
915 int commitTransaction( osrfMethodContext* ctx ) {
916 if(osrfMethodVerifyContext( ctx )) {
917 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
921 if( enforce_pcrud ) {
922 timeout_needs_resetting = 1;
923 const jsonObject* user = verifyUserPCRUD( ctx );
928 // Verify that a transaction is pending
929 const char* trans_id = getXactId( ctx );
930 if( NULL == trans_id ) {
931 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
932 "osrfMethodException", ctx->request, "No active transaction to commit" );
936 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
938 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction", modulename );
939 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
940 "osrfMethodException", ctx->request, "Error committing transaction" );
943 jsonObject* ret = jsonNewObject( trans_id );
944 osrfAppRespondComplete( ctx, ret );
945 jsonObjectFree( ret );
952 @brief Implement the transaction.rollback method.
953 @param ctx Pointer to the method context.
954 @return Zero if successful, or -1 if not.
956 Issue a ROLLBACK to the database server.
959 - authkey (PCRUD only)
961 Return to client: Transaction ID
963 int rollbackTransaction( osrfMethodContext* ctx ) {
964 if( osrfMethodVerifyContext( ctx )) {
965 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
969 if( enforce_pcrud ) {
970 timeout_needs_resetting = 1;
971 const jsonObject* user = verifyUserPCRUD( ctx );
976 // Verify that a transaction is pending
977 const char* trans_id = getXactId( ctx );
978 if( NULL == trans_id ) {
979 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
980 "osrfMethodException", ctx->request, "No active transaction to roll back" );
984 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
986 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction", modulename );
987 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
988 "osrfMethodException", ctx->request, "Error rolling back transaction" );
991 jsonObject* ret = jsonNewObject( trans_id );
992 osrfAppRespondComplete( ctx, ret );
993 jsonObjectFree( ret );
1000 @brief Implement the "search" method.
1001 @param ctx Pointer to the method context.
1002 @return Zero if successful, or -1 if not.
1005 - authkey (PCRUD only)
1006 - WHERE clause, as jsonObject
1007 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1009 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1010 Optionally flesh linked fields.
1012 int doSearch( osrfMethodContext* ctx ) {
1013 if( osrfMethodVerifyContext( ctx )) {
1014 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1019 timeout_needs_resetting = 1;
1021 jsonObject* where_clause;
1022 jsonObject* rest_of_query;
1024 if( enforce_pcrud ) {
1025 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1026 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1028 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1029 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1032 // Get the class metadata
1033 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1034 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1038 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1040 osrfAppRespondComplete( ctx, NULL );
1044 // Return each row to the client (except that some may be suppressed by PCRUD)
1045 jsonObject* cur = 0;
1046 unsigned long res_idx = 0;
1047 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1048 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1050 osrfAppRespond( ctx, cur );
1052 jsonObjectFree( obj );
1054 osrfAppRespondComplete( ctx, NULL );
1059 @brief Implement the "id_list" method.
1060 @param ctx Pointer to the method context.
1061 @param err Pointer through which to return an error code.
1062 @return Zero if successful, or -1 if not.
1065 - authkey (PCRUD only)
1066 - WHERE clause, as jsonObject
1067 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1069 Return to client: The primary key values for all rows of the relevant class that
1070 satisfy a specified WHERE clause.
1072 This method relies on the assumption that every class has a primary key consisting of
1075 int doIdList( osrfMethodContext* ctx ) {
1076 if( osrfMethodVerifyContext( ctx )) {
1077 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1082 timeout_needs_resetting = 1;
1084 jsonObject* where_clause;
1085 jsonObject* rest_of_query;
1087 // We use the where clause without change. But we need to massage the rest of the
1088 // query, so we work with a copy of it instead of modifying the original.
1090 if( enforce_pcrud ) {
1091 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1092 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1094 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1095 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1098 // Eliminate certain SQL clauses, if present.
1099 if( rest_of_query ) {
1100 jsonObjectRemoveKey( rest_of_query, "select" );
1101 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1102 jsonObjectRemoveKey( rest_of_query, "flesh" );
1103 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
1105 rest_of_query = jsonNewObjectType( JSON_HASH );
1108 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1110 // Get the class metadata
1111 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1112 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1114 // Build a SELECT list containing just the primary key,
1115 // i.e. like { "classname":["keyname"] }
1116 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1118 // Load array with name of primary key
1119 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1120 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1121 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1123 jsonObjectSetKey( rest_of_query, "select", select_clause );
1128 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1130 jsonObjectFree( rest_of_query );
1132 osrfAppRespondComplete( ctx, NULL );
1136 // Return each primary key value to the client
1138 unsigned long res_idx = 0;
1139 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1140 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1141 continue; // Suppress due to lack of permission
1143 osrfAppRespond( ctx,
1144 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1147 jsonObjectFree( obj );
1148 osrfAppRespondComplete( ctx, NULL );
1153 @brief Verify that we have a valid class reference.
1154 @param ctx Pointer to the method context.
1155 @param param Pointer to the method parameters.
1156 @return 1 if the class reference is valid, or zero if it isn't.
1158 The class of the method params must match the class to which the method id devoted.
1159 For PCRUD there are additional restrictions.
1161 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1163 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1164 osrfHash* class = osrfHashGet( method_meta, "class" );
1166 // Compare the method's class to the parameters' class
1167 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1169 // Oops -- they don't match. Complain.
1170 growing_buffer* msg = buffer_init( 128 );
1173 "%s: %s method for type %s was passed a %s",
1175 osrfHashGet( method_meta, "methodtype" ),
1176 osrfHashGet( class, "classname" ),
1177 param->classname ? param->classname : "(null)"
1180 char* m = buffer_release( msg );
1181 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1189 return verifyObjectPCRUD( ctx, param );
1195 @brief (PCRUD only) Verify that the user is properly logged in.
1196 @param ctx Pointer to the method context.
1197 @return If the user is logged in, a pointer to the user object from the authentication
1198 server; otherwise NULL.
1200 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1202 // Get the authkey (the first method parameter)
1203 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1205 // See if we have the same authkey, and a user object,
1206 // locally cached from a previous call
1207 const char* cached_authkey = getAuthkey( ctx );
1208 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1209 const jsonObject* cached_user = getUserLogin( ctx );
1214 // We have no matching authentication data in the cache. Authenticate from scratch.
1215 jsonObject* auth_object = jsonNewObject( auth );
1217 // Fetch the user object from the authentication server
1218 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1220 jsonObjectFree( auth_object );
1222 if( !user->classname || strcmp(user->classname, "au" )) {
1224 growing_buffer* msg = buffer_init( 128 );
1227 "%s: permacrud received a bad auth token: %s",
1232 char* m = buffer_release( msg );
1233 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1237 jsonObjectFree( user );
1241 setUserLogin( ctx, user );
1242 setAuthkey( ctx, auth );
1244 // Allow ourselves up to a second before we have to reset the login timeout.
1245 // It would be nice to use some fraction of the timeout interval enforced by the
1246 // authentication server, but that value is not readily available at this point.
1247 // Instead, we use a conservative default interval.
1248 time_next_reset = time( NULL ) + 1;
1253 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1255 dbhandle = writehandle;
1257 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1258 osrfHash* class = osrfHashGet( method_metadata, "class" );
1259 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1262 if( *method_type == 's' || *method_type == 'i' ) {
1263 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1264 } else if( *method_type == 'u' || *method_type == 'd' ) {
1265 fetch = 1; // MUST go to the db for the object for update and delete
1268 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1271 // No permacrud for this method type on this class
1273 growing_buffer* msg = buffer_init( 128 );
1276 "%s: %s on class %s has no permacrud IDL entry",
1278 osrfHashGet( method_metadata, "methodtype" ),
1279 osrfHashGet( class, "classname" )
1282 char* m = buffer_release( msg );
1283 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1284 "osrfMethodException", ctx->request, m );
1291 const jsonObject* user = verifyUserPCRUD( ctx );
1295 int userid = atoi( oilsFMGetString( user, "id" ) );
1297 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1298 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1299 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1301 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1304 char* pkey_value = NULL;
1305 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1306 osrfLogDebug( OSRF_LOG_MARK,
1307 "global-level permissions required, fetching top of the org tree" );
1309 // check for perm at top of org tree
1310 char* org_tree_root_id = org_tree_root( ctx );
1311 if( org_tree_root_id ) {
1312 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1313 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1315 osrfStringArrayFree( context_org_array );
1320 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1321 "fetching context org ids" );
1322 const char* pkey = osrfHashGet( class, "primarykey" );
1323 jsonObject *param = NULL;
1325 if( obj->classname ) {
1326 pkey_value = oilsFMGetString( obj, pkey );
1328 param = jsonObjectClone( obj );
1329 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1332 pkey_value = jsonObjectToSimpleString( obj );
1334 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1335 "of %s and retrieving from the database", pkey_value );
1339 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1340 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1341 jsonObjectFree( _tmp_params );
1343 param = jsonObjectExtractIndex( _list, 0 );
1344 jsonObjectFree( _list );
1348 osrfLogDebug( OSRF_LOG_MARK,
1349 "Object not found in the database with primary key %s of %s",
1352 growing_buffer* msg = buffer_init( 128 );
1355 "%s: no object found with primary key %s of %s",
1361 char* m = buffer_release( msg );
1362 osrfAppSessionStatus(
1364 OSRF_STATUS_INTERNALSERVERERROR,
1365 "osrfMethodException",
1377 if( local_context->size > 0 ) {
1378 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1379 local_context->size );
1381 const char* lcontext = NULL;
1382 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1383 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1386 "adding class-local field %s (value: %s) to the context org list",
1388 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1393 if( foreign_context ) {
1394 unsigned long class_count = osrfHashGetCount( foreign_context );
1395 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1397 if( class_count > 0 ) {
1399 osrfHash* fcontext = NULL;
1400 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1401 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1402 const char* class_name = osrfHashIteratorKey( class_itr );
1403 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1407 "%d foreign context fields(s) specified for class %s",
1408 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1412 char* foreign_pkey = osrfHashGet( fcontext, "field" );
1413 char* foreign_pkey_value =
1414 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1416 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1418 jsonObject* _list = doFieldmapperSearch(
1419 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1421 jsonObject* _fparam = jsonObjectClone( jsonObjectGetIndex( _list, 0 ));
1422 jsonObjectFree( _tmp_params );
1423 jsonObjectFree( _list );
1425 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1427 if( _fparam && jump_list ) {
1428 const char* flink = NULL;
1430 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1431 free( foreign_pkey_value );
1433 osrfHash* foreign_link_hash =
1434 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1436 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1437 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1439 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1441 _list = doFieldmapperSearch(
1443 osrfHashGet( oilsIDL(),
1444 osrfHashGet( foreign_link_hash, "class" ) ),
1450 _fparam = jsonObjectClone( jsonObjectGetIndex( _list, 0 ));
1451 jsonObjectFree( _tmp_params );
1452 jsonObjectFree( _list );
1458 growing_buffer* msg = buffer_init( 128 );
1461 "%s: no object found with primary key %s of %s",
1467 char* m = buffer_release( msg );
1468 osrfAppSessionStatus(
1470 OSRF_STATUS_INTERNALSERVERERROR,
1471 "osrfMethodException",
1477 osrfHashIteratorFree( class_itr );
1478 free( foreign_pkey_value );
1479 jsonObjectFree( param );
1484 free( foreign_pkey_value );
1487 const char* foreign_field = NULL;
1488 while ( (foreign_field = osrfStringArrayGetString(
1489 osrfHashGet(fcontext,"context" ), j++ )) ) {
1490 osrfStringArrayAdd( context_org_array,
1491 oilsFMGetString( _fparam, foreign_field ) );
1494 "adding foreign class %s field %s (value: %s) to the context org list",
1497 osrfStringArrayGetString(
1498 context_org_array, context_org_array->size - 1 )
1502 jsonObjectFree( _fparam );
1505 osrfHashIteratorFree( class_itr );
1509 jsonObjectFree( param );
1512 const char* context_org = NULL;
1513 const char* perm = NULL;
1516 if( permission->size == 0 ) {
1517 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1522 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1524 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1530 "Checking object permission [%s] for user %d "
1531 "on object %s (class %s) at org %d",
1535 osrfHashGet( class, "classname" ),
1539 result = dbi_conn_queryf(
1541 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1544 osrfHashGet( class, "classname" ),
1552 "Received a result for object permission [%s] "
1553 "for user %d on object %s (class %s) at org %d",
1557 osrfHashGet( class, "classname" ),
1561 if( dbi_result_first_row( result )) {
1562 jsonObject* return_val = oilsMakeJSONFromResult( result );
1563 const char* has_perm = jsonObjectGetString(
1564 jsonObjectGetKeyConst( return_val, "has_perm" ));
1568 "Status of object permission [%s] for user %d "
1569 "on object %s (class %s) at org %d is %s",
1573 osrfHashGet(class, "classname"),
1578 if( *has_perm == 't' )
1580 jsonObjectFree( return_val );
1583 dbi_result_free( result );
1589 osrfLogDebug( OSRF_LOG_MARK,
1590 "Checking non-object permission [%s] for user %d at org %d",
1591 perm, userid, atoi(context_org) );
1592 result = dbi_conn_queryf(
1594 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1601 osrfLogDebug( OSRF_LOG_MARK,
1602 "Received a result for permission [%s] for user %d at org %d",
1603 perm, userid, atoi( context_org ));
1604 if( dbi_result_first_row( result )) {
1605 jsonObject* return_val = oilsMakeJSONFromResult( result );
1606 const char* has_perm = jsonObjectGetString(
1607 jsonObjectGetKeyConst( return_val, "has_perm" ));
1608 osrfLogDebug( OSRF_LOG_MARK,
1609 "Status of permission [%s] for user %d at org %d is [%s]",
1610 perm, userid, atoi( context_org ), has_perm );
1611 if( *has_perm == 't' )
1613 jsonObjectFree( return_val );
1616 dbi_result_free( result );
1628 osrfStringArrayFree( context_org_array );
1634 @brief Look up the root of the org_unit tree.
1635 @param ctx Pointer to the method context.
1636 @return The id of the root org unit, as a character string.
1638 Query actor.org_unit where parent_ou is null, and return the id as a string.
1640 This function assumes that there is only one root org unit, i.e. that we
1641 have a single tree, not a forest.
1643 The calling code is responsible for freeing the returned string.
1645 static char* org_tree_root( osrfMethodContext* ctx ) {
1647 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1648 static time_t last_lookup_time = 0;
1649 time_t current_time = time( NULL );
1651 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1652 // We successfully looked this up less than an hour ago.
1653 // It's not likely to have changed since then.
1654 return strdup( cached_root_id );
1656 last_lookup_time = current_time;
1659 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1660 jsonObject* result = doFieldmapperSearch(
1661 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1662 jsonObjectFree( where_clause );
1664 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1667 jsonObjectFree( result );
1669 growing_buffer* msg = buffer_init( 128 );
1670 OSRF_BUFFER_ADD( msg, modulename );
1671 OSRF_BUFFER_ADD( msg,
1672 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1674 char* m = buffer_release( msg );
1675 osrfAppSessionStatus( ctx->session,
1676 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1679 cached_root_id[ 0 ] = '\0';
1683 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1684 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1686 jsonObjectFree( result );
1688 strcpy( cached_root_id, root_org_unit_id );
1689 return root_org_unit_id;
1693 @brief Create a JSON_HASH with a single key/value pair.
1694 @param key The key of the key/value pair.
1695 @param value the value of the key/value pair.
1696 @return Pointer to a newly created jsonObject of type JSON_HASH.
1698 The value of the key/value is either a string or (if @a value is NULL) a null.
1700 static jsonObject* single_hash( const char* key, const char* value ) {
1702 if( ! key ) key = "";
1704 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1705 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1710 int doCreate( osrfMethodContext* ctx ) {
1711 if(osrfMethodVerifyContext( ctx )) {
1712 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1717 timeout_needs_resetting = 1;
1719 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1720 jsonObject* target = NULL;
1721 jsonObject* options = NULL;
1723 if( enforce_pcrud ) {
1724 target = jsonObjectGetIndex( ctx->params, 1 );
1725 options = jsonObjectGetIndex( ctx->params, 2 );
1727 target = jsonObjectGetIndex( ctx->params, 0 );
1728 options = jsonObjectGetIndex( ctx->params, 1 );
1731 if( !verifyObjectClass( ctx, target )) {
1732 osrfAppRespondComplete( ctx, NULL );
1736 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1738 const char* trans_id = getXactId( ctx );
1740 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1742 osrfAppSessionStatus(
1744 OSRF_STATUS_BADREQUEST,
1745 "osrfMethodException",
1747 "No active transaction -- required for CREATE"
1749 osrfAppRespondComplete( ctx, NULL );
1753 // The following test is harmless but redundant. If a class is
1754 // readonly, we don't register a create method for it.
1755 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1756 osrfAppSessionStatus(
1758 OSRF_STATUS_BADREQUEST,
1759 "osrfMethodException",
1761 "Cannot INSERT readonly class"
1763 osrfAppRespondComplete( ctx, NULL );
1767 // Set the last_xact_id
1768 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1770 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1771 trans_id, target->classname, index);
1772 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1775 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1777 dbhandle = writehandle;
1779 osrfHash* fields = osrfHashGet( meta, "fields" );
1780 char* pkey = osrfHashGet( meta, "primarykey" );
1781 char* seq = osrfHashGet( meta, "sequence" );
1783 growing_buffer* table_buf = buffer_init( 128 );
1784 growing_buffer* col_buf = buffer_init( 128 );
1785 growing_buffer* val_buf = buffer_init( 128 );
1787 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1788 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1789 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1790 buffer_add( val_buf,"VALUES (" );
1794 osrfHash* field = NULL;
1795 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1796 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1798 const char* field_name = osrfHashIteratorKey( field_itr );
1800 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1803 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1806 if( field_object && field_object->classname ) {
1807 value = oilsFMGetString(
1809 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
1811 } else if( field_object && JSON_BOOL == field_object->type ) {
1812 if( jsonBoolIsTrue( field_object ) )
1813 value = strdup( "t" );
1815 value = strdup( "f" );
1817 value = jsonObjectToSimpleString( field_object );
1823 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1824 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1827 buffer_add( col_buf, field_name );
1829 if( !field_object || field_object->type == JSON_NULL ) {
1830 buffer_add( val_buf, "DEFAULT" );
1832 } else if( !strcmp( get_primitive( field ), "number" )) {
1833 const char* numtype = get_datatype( field );
1834 if( !strcmp( numtype, "INT8" )) {
1835 buffer_fadd( val_buf, "%lld", atoll( value ));
1837 } else if( !strcmp( numtype, "INT" )) {
1838 buffer_fadd( val_buf, "%d", atoi( value ));
1840 } else if( !strcmp( numtype, "NUMERIC" )) {
1841 buffer_fadd( val_buf, "%f", atof( value ));
1844 if( dbi_conn_quote_string( writehandle, &value )) {
1845 OSRF_BUFFER_ADD( val_buf, value );
1848 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
1849 osrfAppSessionStatus(
1851 OSRF_STATUS_INTERNALSERVERERROR,
1852 "osrfMethodException",
1854 "Error quoting string -- please see the error log for more details"
1857 buffer_free( table_buf );
1858 buffer_free( col_buf );
1859 buffer_free( val_buf );
1860 osrfAppRespondComplete( ctx, NULL );
1868 osrfHashIteratorFree( field_itr );
1870 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1871 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1873 char* table_str = buffer_release( table_buf );
1874 char* col_str = buffer_release( col_buf );
1875 char* val_str = buffer_release( val_buf );
1876 growing_buffer* sql = buffer_init( 128 );
1877 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1882 char* query = buffer_release( sql );
1884 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
1886 jsonObject* obj = NULL;
1889 dbi_result result = dbi_conn_query( writehandle, query );
1891 obj = jsonNewObject( NULL );
1894 "%s ERROR inserting %s object using query [%s]",
1896 osrfHashGet(meta, "fieldmapper"),
1899 osrfAppSessionStatus(
1901 OSRF_STATUS_INTERNALSERVERERROR,
1902 "osrfMethodException",
1904 "INSERT error -- please see the error log for more details"
1909 char* id = oilsFMGetString( target, pkey );
1911 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
1912 growing_buffer* _id = buffer_init( 10 );
1913 buffer_fadd( _id, "%lld", new_id );
1914 id = buffer_release( _id );
1917 // Find quietness specification, if present
1918 const char* quiet_str = NULL;
1920 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1922 quiet_str = jsonObjectGetString( quiet_obj );
1925 if( str_is_true( quiet_str )) { // if quietness is specified
1926 obj = jsonNewObject( id );
1930 // Fetch the row that we just inserted, so that we can return it to the client
1931 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1932 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
1935 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
1939 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
1941 jsonObjectFree( list );
1942 jsonObjectFree( where_clause );
1949 osrfAppRespondComplete( ctx, obj );
1950 jsonObjectFree( obj );
1955 @brief Implement the retrieve method.
1956 @param ctx Pointer to the method context.
1957 @param err Pointer through which to return an error code.
1958 @return If successful, a pointer to the result to be returned to the client;
1961 From the method's class, fetch a row with a specified value in the primary key. This
1962 method relies on the database design convention that a primary key consists of a single
1966 - authkey (PCRUD only)
1967 - value of the primary key for the desired row, for building the WHERE clause
1968 - a JSON_HASH containing any other SQL clauses: select, join, etc.
1970 Return to client: One row from the query.
1972 int doRetrieve( osrfMethodContext* ctx ) {
1973 if(osrfMethodVerifyContext( ctx )) {
1974 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1979 timeout_needs_resetting = 1;
1984 if( enforce_pcrud ) {
1989 // Get the class metadata
1990 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1992 // Get the value of the primary key, from a method parameter
1993 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
1997 "%s retrieving %s object with primary key value of %s",
1999 osrfHashGet( class_def, "fieldmapper" ),
2000 jsonObjectGetString( id_obj )
2003 // Build a WHERE clause based on the key value
2004 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2007 osrfHashGet( class_def, "primarykey" ), // name of key column
2008 jsonObjectClone( id_obj ) // value of key column
2011 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2015 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2017 jsonObjectFree( where_clause );
2019 osrfAppRespondComplete( ctx, NULL );
2023 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2024 jsonObjectFree( list );
2026 if( enforce_pcrud ) {
2027 if(!verifyObjectPCRUD( ctx, obj )) {
2028 jsonObjectFree( obj );
2030 growing_buffer* msg = buffer_init( 128 );
2031 OSRF_BUFFER_ADD( msg, modulename );
2032 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2034 char* m = buffer_release( msg );
2035 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2039 osrfAppRespondComplete( ctx, NULL );
2044 osrfAppRespondComplete( ctx, obj );
2045 jsonObjectFree( obj );
2050 @brief Translate a numeric value to a string representation for the database.
2051 @param field Pointer to the IDL field definition.
2052 @param value Pointer to a jsonObject holding the value of a field.
2053 @return Pointer to a newly allocated string.
2055 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2056 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2057 or (what is worse) valid SQL that is wrong.
2059 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2061 The calling code is responsible for freeing the resulting string by calling free().
2063 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2064 growing_buffer* val_buf = buffer_init( 32 );
2065 const char* numtype = get_datatype( field );
2067 // For historical reasons the following contains cruft that could be cleaned up.
2068 if( !strncmp( numtype, "INT", 3 ) ) {
2069 if( value->type == JSON_NUMBER )
2070 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2071 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2073 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2076 } else if( !strcmp( numtype, "NUMERIC" )) {
2077 if( value->type == JSON_NUMBER )
2078 buffer_fadd( val_buf, jsonObjectGetString( value ));
2080 buffer_fadd( val_buf, jsonObjectGetString( value ));
2084 // Presumably this was really intended to be a string, so quote it
2085 char* str = jsonObjectToSimpleString( value );
2086 if( dbi_conn_quote_string( dbhandle, &str )) {
2087 OSRF_BUFFER_ADD( val_buf, str );
2090 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2092 buffer_free( val_buf );
2097 return buffer_release( val_buf );
2100 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2101 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2102 growing_buffer* sql_buf = buffer_init( 32 );
2108 osrfHashGet( field, "name" )
2112 buffer_add( sql_buf, "IN (" );
2113 } else if( !strcasecmp( op,"not in" )) {
2114 buffer_add( sql_buf, "NOT IN (" );
2116 buffer_add( sql_buf, "IN (" );
2119 if( node->type == JSON_HASH ) {
2120 // subquery predicate
2121 char* subpred = buildQuery( ctx, node, SUBSELECT );
2123 buffer_free( sql_buf );
2127 buffer_add( sql_buf, subpred );
2130 } else if( node->type == JSON_ARRAY ) {
2131 // literal value list
2132 int in_item_index = 0;
2133 int in_item_first = 1;
2134 const jsonObject* in_item;
2135 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2140 buffer_add( sql_buf, ", " );
2143 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2144 osrfLogError( OSRF_LOG_MARK,
2145 "%s: Expected string or number within IN list; found %s",
2146 modulename, json_type( in_item->type ) );
2147 buffer_free( sql_buf );
2151 // Append the literal value -- quoted if not a number
2152 if( JSON_NUMBER == in_item->type ) {
2153 char* val = jsonNumberToDBString( field, in_item );
2154 OSRF_BUFFER_ADD( sql_buf, val );
2157 } else if( !strcmp( get_primitive( field ), "number" )) {
2158 char* val = jsonNumberToDBString( field, in_item );
2159 OSRF_BUFFER_ADD( sql_buf, val );
2163 char* key_string = jsonObjectToSimpleString( in_item );
2164 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2165 OSRF_BUFFER_ADD( sql_buf, key_string );
2168 osrfLogError( OSRF_LOG_MARK,
2169 "%s: Error quoting key string [%s]", modulename, key_string );
2171 buffer_free( sql_buf );
2177 if( in_item_first ) {
2178 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2179 buffer_free( sql_buf );
2183 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2184 modulename, json_type( node->type ));
2185 buffer_free( sql_buf );
2189 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2191 return buffer_release( sql_buf );
2194 // Receive a JSON_ARRAY representing a function call. The first
2195 // entry in the array is the function name. The rest are parameters.
2196 static char* searchValueTransform( const jsonObject* array ) {
2198 if( array->size < 1 ) {
2199 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2203 // Get the function name
2204 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2205 if( func_item->type != JSON_STRING ) {
2206 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2207 modulename, json_type( func_item->type ));
2211 growing_buffer* sql_buf = buffer_init( 32 );
2213 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2214 OSRF_BUFFER_ADD( sql_buf, "( " );
2216 // Get the parameters
2217 int func_item_index = 1; // We already grabbed the zeroth entry
2218 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2220 // Add a separator comma, if we need one
2221 if( func_item_index > 2 )
2222 buffer_add( sql_buf, ", " );
2224 // Add the current parameter
2225 if( func_item->type == JSON_NULL ) {
2226 buffer_add( sql_buf, "NULL" );
2228 char* val = jsonObjectToSimpleString( func_item );
2229 if( dbi_conn_quote_string( dbhandle, &val )) {
2230 OSRF_BUFFER_ADD( sql_buf, val );
2233 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2235 buffer_free( sql_buf );
2242 buffer_add( sql_buf, " )" );
2244 return buffer_release( sql_buf );
2247 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2248 const jsonObject* node, const char* op ) {
2250 if( ! is_good_operator( op ) ) {
2251 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2255 char* val = searchValueTransform( node );
2259 growing_buffer* sql_buf = buffer_init( 32 );
2264 osrfHashGet( field, "name" ),
2271 return buffer_release( sql_buf );
2274 // class_alias is a class name or other table alias
2275 // field is a field definition as stored in the IDL
2276 // node comes from the method parameter, and may represent an entry in the SELECT list
2277 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2278 const jsonObject* node ) {
2279 growing_buffer* sql_buf = buffer_init( 32 );
2281 const char* field_transform = jsonObjectGetString(
2282 jsonObjectGetKeyConst( node, "transform" ) );
2283 const char* transform_subcolumn = jsonObjectGetString(
2284 jsonObjectGetKeyConst( node, "result_field" ) );
2286 if( transform_subcolumn ) {
2287 if( ! is_identifier( transform_subcolumn ) ) {
2288 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2289 modulename, transform_subcolumn );
2290 buffer_free( sql_buf );
2293 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2296 if( field_transform ) {
2298 if( ! is_identifier( field_transform ) ) {
2299 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2300 modulename, field_transform );
2301 buffer_free( sql_buf );
2305 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2306 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2307 field_transform, class_alias, osrfHashGet( field, "name" ));
2309 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2310 field_transform, class_alias, osrfHashGet( field, "name" ));
2313 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2316 if( array->type != JSON_ARRAY ) {
2317 osrfLogError( OSRF_LOG_MARK,
2318 "%s: Expected JSON_ARRAY for function params; found %s",
2319 modulename, json_type( array->type ) );
2320 buffer_free( sql_buf );
2323 int func_item_index = 0;
2324 jsonObject* func_item;
2325 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2327 char* val = jsonObjectToSimpleString( func_item );
2330 buffer_add( sql_buf, ",NULL" );
2331 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2332 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2333 OSRF_BUFFER_ADD( sql_buf, val );
2335 osrfLogError( OSRF_LOG_MARK,
2336 "%s: Error quoting key string [%s]", modulename, val );
2338 buffer_free( sql_buf );
2345 buffer_add( sql_buf, " )" );
2348 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2351 if( transform_subcolumn )
2352 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2354 return buffer_release( sql_buf );
2357 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2358 const jsonObject* node, const char* op ) {
2360 if( ! is_good_operator( op ) ) {
2361 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2365 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2366 if( ! field_transform )
2369 int extra_parens = 0; // boolean
2371 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2373 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2375 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2377 free( field_transform );
2381 } else if( value_obj->type == JSON_ARRAY ) {
2382 value = searchValueTransform( value_obj );
2384 osrfLogError( OSRF_LOG_MARK,
2385 "%s: Error building value transform for field transform", modulename );
2386 free( field_transform );
2389 } else if( value_obj->type == JSON_HASH ) {
2390 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2392 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2394 free( field_transform );
2398 } else if( value_obj->type == JSON_NUMBER ) {
2399 value = jsonNumberToDBString( field, value_obj );
2400 } else if( value_obj->type == JSON_NULL ) {
2401 osrfLogError( OSRF_LOG_MARK,
2402 "%s: Error building predicate for field transform: null value", modulename );
2403 free( field_transform );
2405 } else if( value_obj->type == JSON_BOOL ) {
2406 osrfLogError( OSRF_LOG_MARK,
2407 "%s: Error building predicate for field transform: boolean value", modulename );
2408 free( field_transform );
2411 if( !strcmp( get_primitive( field ), "number") ) {
2412 value = jsonNumberToDBString( field, value_obj );
2414 value = jsonObjectToSimpleString( value_obj );
2415 if( !dbi_conn_quote_string( dbhandle, &value )) {
2416 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2417 modulename, value );
2419 free( field_transform );
2425 const char* left_parens = "";
2426 const char* right_parens = "";
2428 if( extra_parens ) {
2433 growing_buffer* sql_buf = buffer_init( 32 );
2437 "%s%s %s %s %s %s%s",
2448 free( field_transform );
2450 return buffer_release( sql_buf );
2453 static char* searchSimplePredicate( const char* op, const char* class_alias,
2454 osrfHash* field, const jsonObject* node ) {
2456 if( ! is_good_operator( op ) ) {
2457 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2463 // Get the value to which we are comparing the specified column
2464 if( node->type != JSON_NULL ) {
2465 if( node->type == JSON_NUMBER ) {
2466 val = jsonNumberToDBString( field, node );
2467 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2468 val = jsonNumberToDBString( field, node );
2470 val = jsonObjectToSimpleString( node );
2475 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2476 // Value is not numeric; enclose it in quotes
2477 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2478 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2485 // Compare to a null value
2486 val = strdup( "NULL" );
2487 if( strcmp( op, "=" ))
2493 growing_buffer* sql_buf = buffer_init( 32 );
2494 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2495 char* pred = buffer_release( sql_buf );
2502 static char* searchBETWEENPredicate( const char* class_alias,
2503 osrfHash* field, const jsonObject* node ) {
2505 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2506 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2508 if( NULL == y_node ) {
2509 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2512 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2513 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2520 if( !strcmp( get_primitive( field ), "number") ) {
2521 x_string = jsonNumberToDBString( field, x_node );
2522 y_string = jsonNumberToDBString( field, y_node );
2525 x_string = jsonObjectToSimpleString( x_node );
2526 y_string = jsonObjectToSimpleString( y_node );
2527 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2528 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2529 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2530 modulename, x_string, y_string );
2537 growing_buffer* sql_buf = buffer_init( 32 );
2538 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2539 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2543 return buffer_release( sql_buf );
2546 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2547 jsonObject* node, osrfMethodContext* ctx ) {
2550 if( node->type == JSON_ARRAY ) { // equality IN search
2551 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2552 } else if( node->type == JSON_HASH ) { // other search
2553 jsonIterator* pred_itr = jsonNewIterator( node );
2554 if( !jsonIteratorHasNext( pred_itr ) ) {
2555 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2556 modulename, osrfHashGet(field, "name" ));
2558 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2560 // Verify that there are no additional predicates
2561 if( jsonIteratorHasNext( pred_itr ) ) {
2562 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2563 modulename, osrfHashGet(field, "name" ));
2564 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2565 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2566 else if( !(strcasecmp( pred_itr->key,"in" ))
2567 || !(strcasecmp( pred_itr->key,"not in" )) )
2568 pred = searchINPredicate(
2569 class_info->alias, field, pred_node, pred_itr->key, ctx );
2570 else if( pred_node->type == JSON_ARRAY )
2571 pred = searchFunctionPredicate(
2572 class_info->alias, field, pred_node, pred_itr->key );
2573 else if( pred_node->type == JSON_HASH )
2574 pred = searchFieldTransformPredicate(
2575 class_info, field, pred_node, pred_itr->key );
2577 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2579 jsonIteratorFree( pred_itr );
2581 } else if( node->type == JSON_NULL ) { // IS NULL search
2582 growing_buffer* _p = buffer_init( 64 );
2585 "\"%s\".%s IS NULL",
2586 class_info->class_name,
2587 osrfHashGet( field, "name" )
2589 pred = buffer_release( _p );
2590 } else { // equality search
2591 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2610 field : call_number,
2626 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2628 const jsonObject* working_hash;
2629 jsonObject* freeable_hash = NULL;
2631 if( join_hash->type == JSON_HASH ) {
2632 working_hash = join_hash;
2633 } else if( join_hash->type == JSON_STRING ) {
2634 // turn it into a JSON_HASH by creating a wrapper
2635 // around a copy of the original
2636 const char* _tmp = jsonObjectGetString( join_hash );
2637 freeable_hash = jsonNewObjectType( JSON_HASH );
2638 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2639 working_hash = freeable_hash;
2643 "%s: JOIN failed; expected JSON object type not found",
2649 growing_buffer* join_buf = buffer_init( 128 );
2650 const char* leftclass = left_info->class_name;
2652 jsonObject* snode = NULL;
2653 jsonIterator* search_itr = jsonNewIterator( working_hash );
2655 while ( (snode = jsonIteratorNext( search_itr )) ) {
2656 const char* right_alias = search_itr->key;
2658 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2660 class = right_alias;
2662 const ClassInfo* right_info = add_joined_class( right_alias, class );
2666 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2670 jsonIteratorFree( search_itr );
2671 buffer_free( join_buf );
2673 jsonObjectFree( freeable_hash );
2676 osrfHash* links = right_info->links;
2677 const char* table = right_info->source_def;
2679 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2680 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2682 if( field && !fkey ) {
2683 // Look up the corresponding join column in the IDL.
2684 // The link must be defined in the child table,
2685 // and point to the right parent table.
2686 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2687 const char* reltype = NULL;
2688 const char* other_class = NULL;
2689 reltype = osrfHashGet( idl_link, "reltype" );
2690 if( reltype && strcmp( reltype, "has_many" ) )
2691 other_class = osrfHashGet( idl_link, "class" );
2692 if( other_class && !strcmp( other_class, leftclass ) )
2693 fkey = osrfHashGet( idl_link, "key" );
2697 "%s: JOIN failed. No link defined from %s.%s to %s",
2703 buffer_free( join_buf );
2705 jsonObjectFree( freeable_hash );
2706 jsonIteratorFree( search_itr );
2710 } else if( !field && fkey ) {
2711 // Look up the corresponding join column in the IDL.
2712 // The link must be defined in the child table,
2713 // and point to the right parent table.
2714 osrfHash* left_links = left_info->links;
2715 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2716 const char* reltype = NULL;
2717 const char* other_class = NULL;
2718 reltype = osrfHashGet( idl_link, "reltype" );
2719 if( reltype && strcmp( reltype, "has_many" ) )
2720 other_class = osrfHashGet( idl_link, "class" );
2721 if( other_class && !strcmp( other_class, class ) )
2722 field = osrfHashGet( idl_link, "key" );
2726 "%s: JOIN failed. No link defined from %s.%s to %s",
2732 buffer_free( join_buf );
2734 jsonObjectFree( freeable_hash );
2735 jsonIteratorFree( search_itr );
2739 } else if( !field && !fkey ) {
2740 osrfHash* left_links = left_info->links;
2742 // For each link defined for the left class:
2743 // see if the link references the joined class
2744 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2745 osrfHash* curr_link = NULL;
2746 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2747 const char* other_class = osrfHashGet( curr_link, "class" );
2748 if( other_class && !strcmp( other_class, class ) ) {
2750 // In the IDL, the parent class doesn't always know then names of the child
2751 // columns that are pointing to it, so don't use that end of the link
2752 const char* reltype = osrfHashGet( curr_link, "reltype" );
2753 if( reltype && strcmp( reltype, "has_many" ) ) {
2754 // Found a link between the classes
2755 fkey = osrfHashIteratorKey( itr );
2756 field = osrfHashGet( curr_link, "key" );
2761 osrfHashIteratorFree( itr );
2763 if( !field || !fkey ) {
2764 // Do another such search, with the classes reversed
2766 // For each link defined for the joined class:
2767 // see if the link references the left class
2768 osrfHashIterator* itr = osrfNewHashIterator( links );
2769 osrfHash* curr_link = NULL;
2770 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2771 const char* other_class = osrfHashGet( curr_link, "class" );
2772 if( other_class && !strcmp( other_class, leftclass ) ) {
2774 // In the IDL, the parent class doesn't know then names of the child
2775 // columns that are pointing to it, so don't use that end of the link
2776 const char* reltype = osrfHashGet( curr_link, "reltype" );
2777 if( reltype && strcmp( reltype, "has_many" ) ) {
2778 // Found a link between the classes
2779 field = osrfHashIteratorKey( itr );
2780 fkey = osrfHashGet( curr_link, "key" );
2785 osrfHashIteratorFree( itr );
2788 if( !field || !fkey ) {
2791 "%s: JOIN failed. No link defined between %s and %s",
2796 buffer_free( join_buf );
2798 jsonObjectFree( freeable_hash );
2799 jsonIteratorFree( search_itr );
2804 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2806 if( !strcasecmp( type,"left" )) {
2807 buffer_add( join_buf, " LEFT JOIN" );
2808 } else if( !strcasecmp( type,"right" )) {
2809 buffer_add( join_buf, " RIGHT JOIN" );
2810 } else if( !strcasecmp( type,"full" )) {
2811 buffer_add( join_buf, " FULL JOIN" );
2813 buffer_add( join_buf, " INNER JOIN" );
2816 buffer_add( join_buf, " INNER JOIN" );
2819 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2820 table, right_alias, right_alias, field, left_info->alias, fkey );
2822 // Add any other join conditions as specified by "filter"
2823 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2825 const char* filter_op = jsonObjectGetString(
2826 jsonObjectGetKeyConst( snode, "filter_op" ) );
2827 if( filter_op && !strcasecmp( "or",filter_op )) {
2828 buffer_add( join_buf, " OR " );
2830 buffer_add( join_buf, " AND " );
2833 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2835 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2836 OSRF_BUFFER_ADD( join_buf, jpred );
2841 "%s: JOIN failed. Invalid conditional expression.",
2844 jsonIteratorFree( search_itr );
2845 buffer_free( join_buf );
2847 jsonObjectFree( freeable_hash );
2852 buffer_add( join_buf, " ) " );
2854 // Recursively add a nested join, if one is present
2855 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2857 char* jpred = searchJOIN( join_filter, right_info );
2859 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2860 OSRF_BUFFER_ADD( join_buf, jpred );
2863 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
2864 jsonIteratorFree( search_itr );
2865 buffer_free( join_buf );
2867 jsonObjectFree( freeable_hash );
2874 jsonObjectFree( freeable_hash );
2875 jsonIteratorFree( search_itr );
2877 return buffer_release( join_buf );
2882 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2883 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2884 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2886 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2888 search_hash is the JSON expression of the conditions.
2889 meta is the class definition from the IDL, for the relevant table.
2890 opjoin_type indicates whether multiple conditions, if present, should be
2891 connected by AND or OR.
2892 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2893 to pass it to other functions -- and all they do with it is to use the session
2894 and request members to send error messages back to the client.
2898 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
2899 int opjoin_type, osrfMethodContext* ctx ) {
2903 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
2904 "opjoin_type = %d, ctx addr = %p",
2907 class_info->class_def,
2912 growing_buffer* sql_buf = buffer_init( 128 );
2914 jsonObject* node = NULL;
2917 if( search_hash->type == JSON_ARRAY ) {
2918 osrfLogDebug( OSRF_LOG_MARK,
2919 "%s: In WHERE clause, condition type is JSON_ARRAY", modulename );
2920 if( 0 == search_hash->size ) {
2923 "%s: Invalid predicate structure: empty JSON array",
2926 buffer_free( sql_buf );
2930 unsigned long i = 0;
2931 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
2935 if( opjoin_type == OR_OP_JOIN )
2936 buffer_add( sql_buf, " OR " );
2938 buffer_add( sql_buf, " AND " );
2941 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2943 buffer_free( sql_buf );
2947 buffer_fadd( sql_buf, "( %s )", subpred );
2951 } else if( search_hash->type == JSON_HASH ) {
2952 osrfLogDebug( OSRF_LOG_MARK,
2953 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
2954 jsonIterator* search_itr = jsonNewIterator( search_hash );
2955 if( !jsonIteratorHasNext( search_itr ) ) {
2958 "%s: Invalid predicate structure: empty JSON object",
2961 jsonIteratorFree( search_itr );
2962 buffer_free( sql_buf );
2966 while( (node = jsonIteratorNext( search_itr )) ) {
2971 if( opjoin_type == OR_OP_JOIN )
2972 buffer_add( sql_buf, " OR " );
2974 buffer_add( sql_buf, " AND " );
2977 if( '+' == search_itr->key[ 0 ] ) {
2979 // This plus sign prefixes a class name or other table alias;
2980 // make sure the table alias is in scope
2981 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2982 if( ! alias_info ) {
2985 "%s: Invalid table alias \"%s\" in WHERE clause",
2989 jsonIteratorFree( search_itr );
2990 buffer_free( sql_buf );
2994 if( node->type == JSON_STRING ) {
2995 // It's the name of a column; make sure it belongs to the class
2996 const char* fieldname = jsonObjectGetString( node );
2997 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3000 "%s: Invalid column name \"%s\" in WHERE clause "
3001 "for table alias \"%s\"",
3006 jsonIteratorFree( search_itr );
3007 buffer_free( sql_buf );
3011 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3013 // It's something more complicated
3014 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3016 jsonIteratorFree( search_itr );
3017 buffer_free( sql_buf );
3021 buffer_fadd( sql_buf, "( %s )", subpred );
3024 } else if( '-' == search_itr->key[ 0 ] ) {
3025 if( !strcasecmp( "-or", search_itr->key )) {
3026 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3028 jsonIteratorFree( search_itr );
3029 buffer_free( sql_buf );
3033 buffer_fadd( sql_buf, "( %s )", subpred );
3035 } else if( !strcasecmp( "-and", search_itr->key )) {
3036 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3038 jsonIteratorFree( search_itr );
3039 buffer_free( sql_buf );
3043 buffer_fadd( sql_buf, "( %s )", subpred );
3045 } else if( !strcasecmp("-not",search_itr->key) ) {
3046 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3048 jsonIteratorFree( search_itr );
3049 buffer_free( sql_buf );
3053 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3055 } else if( !strcasecmp( "-exists", search_itr->key )) {
3056 char* subpred = buildQuery( ctx, node, SUBSELECT );
3058 jsonIteratorFree( search_itr );
3059 buffer_free( sql_buf );
3063 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3065 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3066 char* subpred = buildQuery( ctx, node, SUBSELECT );
3068 jsonIteratorFree( search_itr );
3069 buffer_free( sql_buf );
3073 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3075 } else { // Invalid "minus" operator
3078 "%s: Invalid operator \"%s\" in WHERE clause",
3082 jsonIteratorFree( search_itr );
3083 buffer_free( sql_buf );
3089 const char* class = class_info->class_name;
3090 osrfHash* fields = class_info->fields;
3091 osrfHash* field = osrfHashGet( fields, search_itr->key );
3094 const char* table = class_info->source_def;
3097 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3100 table ? table : "?",
3103 jsonIteratorFree( search_itr );
3104 buffer_free( sql_buf );
3108 char* subpred = searchPredicate( class_info, field, node, ctx );
3110 buffer_free( sql_buf );
3111 jsonIteratorFree( search_itr );
3115 buffer_add( sql_buf, subpred );
3119 jsonIteratorFree( search_itr );
3122 // ERROR ... only hash and array allowed at this level
3123 char* predicate_string = jsonObjectToJSON( search_hash );
3126 "%s: Invalid predicate structure: %s",
3130 buffer_free( sql_buf );
3131 free( predicate_string );
3135 return buffer_release( sql_buf );
3138 /* Build a JSON_ARRAY of field names for a given table alias
3140 static jsonObject* defaultSelectList( const char* table_alias ) {
3145 ClassInfo* class_info = search_all_alias( table_alias );
3146 if( ! class_info ) {
3149 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3156 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3157 osrfHash* field_def = NULL;
3158 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3159 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3160 const char* field_name = osrfHashIteratorKey( field_itr );
3161 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3162 jsonObjectPush( array, jsonNewObject( field_name ) );
3165 osrfHashIteratorFree( field_itr );
3170 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3171 // The jsonObject must be a JSON_HASH with an single entry for "union",
3172 // "intersect", or "except". The data associated with this key must be an
3173 // array of hashes, each hash being a query.
3174 // Also allowed but currently ignored: entries for "order_by" and "alias".
3175 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3177 if( ! combo || combo->type != JSON_HASH )
3178 return NULL; // should be impossible; validated by caller
3180 const jsonObject* query_array = NULL; // array of subordinate queries
3181 const char* op = NULL; // name of operator, e.g. UNION
3182 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3183 int op_count = 0; // for detecting conflicting operators
3184 int excepting = 0; // boolean
3185 int all = 0; // boolean
3186 jsonObject* order_obj = NULL;
3188 // Identify the elements in the hash
3189 jsonIterator* query_itr = jsonNewIterator( combo );
3190 jsonObject* curr_obj = NULL;
3191 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3192 if( ! strcmp( "union", query_itr->key ) ) {
3195 query_array = curr_obj;
3196 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3199 query_array = curr_obj;
3200 } else if( ! strcmp( "except", query_itr->key ) ) {
3204 query_array = curr_obj;
3205 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3208 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3211 order_obj = curr_obj;
3212 } else if( ! strcmp( "alias", query_itr->key ) ) {
3213 if( curr_obj->type != JSON_STRING ) {
3214 jsonIteratorFree( query_itr );
3217 alias = jsonObjectGetString( curr_obj );
3218 } else if( ! strcmp( "all", query_itr->key ) ) {
3219 if( obj_is_true( curr_obj ) )
3223 osrfAppSessionStatus(
3225 OSRF_STATUS_INTERNALSERVERERROR,
3226 "osrfMethodException",
3228 "Malformed query; unexpected entry in query object"
3232 "%s: Unexpected entry for \"%s\" in%squery",
3237 jsonIteratorFree( query_itr );
3241 jsonIteratorFree( query_itr );
3243 // More sanity checks
3244 if( ! query_array ) {
3246 osrfAppSessionStatus(
3248 OSRF_STATUS_INTERNALSERVERERROR,
3249 "osrfMethodException",
3251 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3255 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3258 return NULL; // should be impossible...
3259 } else if( op_count > 1 ) {
3261 osrfAppSessionStatus(
3263 OSRF_STATUS_INTERNALSERVERERROR,
3264 "osrfMethodException",
3266 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3270 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3274 } if( query_array->type != JSON_ARRAY ) {
3276 osrfAppSessionStatus(
3278 OSRF_STATUS_INTERNALSERVERERROR,
3279 "osrfMethodException",
3281 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3285 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3288 json_type( query_array->type )
3291 } if( query_array->size < 2 ) {
3293 osrfAppSessionStatus(
3295 OSRF_STATUS_INTERNALSERVERERROR,
3296 "osrfMethodException",
3298 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3302 "%s:%srequires multiple queries as operands",
3307 } else if( excepting && query_array->size > 2 ) {
3309 osrfAppSessionStatus(
3311 OSRF_STATUS_INTERNALSERVERERROR,
3312 "osrfMethodException",
3314 "EXCEPT operator has too many queries as operands"
3318 "%s:EXCEPT operator has too many queries as operands",
3322 } else if( order_obj && ! alias ) {
3324 osrfAppSessionStatus(
3326 OSRF_STATUS_INTERNALSERVERERROR,
3327 "osrfMethodException",
3329 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3333 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3339 // So far so good. Now build the SQL.
3340 growing_buffer* sql = buffer_init( 256 );
3342 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3343 // Add a layer of parentheses
3344 if( flags & SUBCOMBO )
3345 OSRF_BUFFER_ADD( sql, "( " );
3347 // Traverse the query array. Each entry should be a hash.
3348 int first = 1; // boolean
3350 jsonObject* query = NULL;
3351 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3352 if( query->type != JSON_HASH ) {
3354 osrfAppSessionStatus(
3356 OSRF_STATUS_INTERNALSERVERERROR,
3357 "osrfMethodException",
3359 "Malformed query under UNION, INTERSECT or EXCEPT"
3363 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3366 json_type( query->type )
3375 OSRF_BUFFER_ADD( sql, op );
3377 OSRF_BUFFER_ADD( sql, "ALL " );
3380 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3384 "%s: Error building query under%s",
3392 OSRF_BUFFER_ADD( sql, query_str );
3395 if( flags & SUBCOMBO )
3396 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3398 if( !(flags & SUBSELECT) )
3399 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3401 return buffer_release( sql );
3404 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3405 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3406 // or "except" to indicate the type of query.
3407 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3411 osrfAppSessionStatus(
3413 OSRF_STATUS_INTERNALSERVERERROR,
3414 "osrfMethodException",
3416 "Malformed query; no query object"
3418 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3420 } else if( query->type != JSON_HASH ) {
3422 osrfAppSessionStatus(
3424 OSRF_STATUS_INTERNALSERVERERROR,
3425 "osrfMethodException",
3427 "Malformed query object"
3431 "%s: Query object is %s instead of JSON_HASH",
3433 json_type( query->type )
3438 // Determine what kind of query it purports to be, and dispatch accordingly.
3439 if( jsonObjectGetKey( query, "union" ) ||
3440 jsonObjectGetKey( query, "intersect" ) ||
3441 jsonObjectGetKey( query, "except" ) ) {
3442 return doCombo( ctx, query, flags );
3444 // It is presumably a SELECT query
3446 // Push a node onto the stack for the current query. Every level of
3447 // subquery gets its own QueryFrame on the Stack.
3450 // Build an SQL SELECT statement
3453 jsonObjectGetKey( query, "select" ),
3454 jsonObjectGetKey( query, "from" ),
3455 jsonObjectGetKey( query, "where" ),
3456 jsonObjectGetKey( query, "having" ),
3457 jsonObjectGetKey( query, "order_by" ),
3458 jsonObjectGetKey( query, "limit" ),
3459 jsonObjectGetKey( query, "offset" ),
3468 /* method context */ osrfMethodContext* ctx,
3470 /* SELECT */ jsonObject* selhash,
3471 /* FROM */ jsonObject* join_hash,
3472 /* WHERE */ jsonObject* search_hash,
3473 /* HAVING */ jsonObject* having_hash,
3474 /* ORDER BY */ jsonObject* order_hash,
3475 /* LIMIT */ jsonObject* limit,
3476 /* OFFSET */ jsonObject* offset,
3477 /* flags */ int flags
3479 const char* locale = osrf_message_get_last_locale();
3481 // general tmp objects
3482 const jsonObject* tmp_const;
3483 jsonObject* selclass = NULL;
3484 jsonObject* snode = NULL;
3485 jsonObject* onode = NULL;
3487 char* string = NULL;
3488 int from_function = 0;
3493 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3495 // punt if there's no FROM clause
3496 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3499 "%s: FROM clause is missing or empty",
3503 osrfAppSessionStatus(
3505 OSRF_STATUS_INTERNALSERVERERROR,
3506 "osrfMethodException",
3508 "FROM clause is missing or empty in JSON query"
3513 // the core search class
3514 const char* core_class = NULL;
3516 // get the core class -- the only key of the top level FROM clause, or a string
3517 if( join_hash->type == JSON_HASH ) {
3518 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3519 snode = jsonIteratorNext( tmp_itr );
3521 // Populate the current QueryFrame with information
3522 // about the core class
3523 if( add_query_core( NULL, tmp_itr->key ) ) {
3525 osrfAppSessionStatus(
3527 OSRF_STATUS_INTERNALSERVERERROR,
3528 "osrfMethodException",
3530 "Unable to look up core class"
3534 core_class = curr_query->core.class_name;
3537 jsonObject* extra = jsonIteratorNext( tmp_itr );
3539 jsonIteratorFree( tmp_itr );
3542 // There shouldn't be more than one entry in join_hash
3546 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3550 osrfAppSessionStatus(
3552 OSRF_STATUS_INTERNALSERVERERROR,
3553 "osrfMethodException",
3555 "Malformed FROM clause in JSON query"
3557 return NULL; // Malformed join_hash; extra entry
3559 } else if( join_hash->type == JSON_ARRAY ) {
3560 // We're selecting from a function, not from a table
3562 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3565 } else if( join_hash->type == JSON_STRING ) {
3566 // Populate the current QueryFrame with information
3567 // about the core class
3568 core_class = jsonObjectGetString( join_hash );
3570 if( add_query_core( NULL, core_class ) ) {
3572 osrfAppSessionStatus(
3574 OSRF_STATUS_INTERNALSERVERERROR,
3575 "osrfMethodException",
3577 "Unable to look up core class"
3585 "%s: FROM clause is unexpected JSON type: %s",
3587 json_type( join_hash->type )
3590 osrfAppSessionStatus(
3592 OSRF_STATUS_INTERNALSERVERERROR,
3593 "osrfMethodException",
3595 "Ill-formed FROM clause in JSON query"
3600 // Build the join clause, if any, while filling out the list
3601 // of joined classes in the current QueryFrame.
3602 char* join_clause = NULL;
3603 if( join_hash && ! from_function ) {
3605 join_clause = searchJOIN( join_hash, &curr_query->core );
3606 if( ! join_clause ) {
3608 osrfAppSessionStatus(
3610 OSRF_STATUS_INTERNALSERVERERROR,
3611 "osrfMethodException",
3613 "Unable to construct JOIN clause(s)"
3619 // For in case we don't get a select list
3620 jsonObject* defaultselhash = NULL;
3622 // if there is no select list, build a default select list ...
3623 if( !selhash && !from_function ) {
3624 jsonObject* default_list = defaultSelectList( core_class );
3625 if( ! default_list ) {
3627 osrfAppSessionStatus(
3629 OSRF_STATUS_INTERNALSERVERERROR,
3630 "osrfMethodException",
3632 "Unable to build default SELECT clause in JSON query"
3634 free( join_clause );
3639 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3640 jsonObjectSetKey( selhash, core_class, default_list );
3643 // The SELECT clause can be encoded only by a hash
3644 if( !from_function && selhash->type != JSON_HASH ) {
3647 "%s: Expected JSON_HASH for SELECT clause; found %s",
3649 json_type( selhash->type )
3653 osrfAppSessionStatus(
3655 OSRF_STATUS_INTERNALSERVERERROR,
3656 "osrfMethodException",
3658 "Malformed SELECT clause in JSON query"
3660 free( join_clause );
3664 // If you see a null or wild card specifier for the core class, or an
3665 // empty array, replace it with a default SELECT list
3666 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3668 int default_needed = 0; // boolean
3669 if( JSON_STRING == tmp_const->type
3670 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3672 else if( JSON_NULL == tmp_const->type )
3675 if( default_needed ) {
3676 // Build a default SELECT list
3677 jsonObject* default_list = defaultSelectList( core_class );
3678 if( ! default_list ) {
3680 osrfAppSessionStatus(
3682 OSRF_STATUS_INTERNALSERVERERROR,
3683 "osrfMethodException",
3685 "Can't build default SELECT clause in JSON query"
3687 free( join_clause );
3692 jsonObjectSetKey( selhash, core_class, default_list );
3696 // temp buffers for the SELECT list and GROUP BY clause
3697 growing_buffer* select_buf = buffer_init( 128 );
3698 growing_buffer* group_buf = buffer_init( 128 );
3700 int aggregate_found = 0; // boolean
3702 // Build a select list
3703 if( from_function ) // From a function we select everything
3704 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3707 // Build the SELECT list as SQL
3711 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3712 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3714 const char* cname = selclass_itr->key;
3716 // Make sure the target relation is in the FROM clause.
3718 // At this point join_hash is a step down from the join_hash we
3719 // received as a parameter. If the original was a JSON_STRING,
3720 // then json_hash is now NULL. If the original was a JSON_HASH,
3721 // then json_hash is now the first (and only) entry in it,
3722 // denoting the core class. We've already excluded the
3723 // possibility that the original was a JSON_ARRAY, because in
3724 // that case from_function would be non-NULL, and we wouldn't
3727 // If the current table alias isn't in scope, bail out
3728 ClassInfo* class_info = search_alias( cname );
3729 if( ! class_info ) {
3732 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3737 osrfAppSessionStatus(
3739 OSRF_STATUS_INTERNALSERVERERROR,
3740 "osrfMethodException",
3742 "Selected class not in FROM clause in JSON query"
3744 jsonIteratorFree( selclass_itr );
3745 buffer_free( select_buf );
3746 buffer_free( group_buf );
3747 if( defaultselhash )
3748 jsonObjectFree( defaultselhash );
3749 free( join_clause );
3753 if( selclass->type != JSON_ARRAY ) {
3756 "%s: Malformed SELECT list for class \"%s\"; not an array",
3761 osrfAppSessionStatus(
3763 OSRF_STATUS_INTERNALSERVERERROR,
3764 "osrfMethodException",
3766 "Selected class not in FROM clause in JSON query"
3769 jsonIteratorFree( selclass_itr );
3770 buffer_free( select_buf );
3771 buffer_free( group_buf );
3772 if( defaultselhash )
3773 jsonObjectFree( defaultselhash );
3774 free( join_clause );
3778 // Look up some attributes of the current class
3779 osrfHash* idlClass = class_info->class_def;
3780 osrfHash* class_field_set = class_info->fields;
3781 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3782 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3784 if( 0 == selclass->size ) {
3787 "%s: No columns selected from \"%s\"",
3793 // stitch together the column list for the current table alias...
3794 unsigned long field_idx = 0;
3795 jsonObject* selfield = NULL;
3796 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3798 // If we need a separator comma, add one
3802 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3805 // if the field specification is a string, add it to the list
3806 if( selfield->type == JSON_STRING ) {
3808 // Look up the field in the IDL
3809 const char* col_name = jsonObjectGetString( selfield );
3810 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3812 // No such field in current class
3815 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3821 osrfAppSessionStatus(
3823 OSRF_STATUS_INTERNALSERVERERROR,
3824 "osrfMethodException",
3826 "Selected column not defined in JSON query"
3828 jsonIteratorFree( selclass_itr );
3829 buffer_free( select_buf );
3830 buffer_free( group_buf );
3831 if( defaultselhash )
3832 jsonObjectFree( defaultselhash );
3833 free( join_clause );
3835 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3836 // Virtual field not allowed
3839 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3845 osrfAppSessionStatus(
3847 OSRF_STATUS_INTERNALSERVERERROR,
3848 "osrfMethodException",
3850 "Selected column may not be virtual in JSON query"
3852 jsonIteratorFree( selclass_itr );
3853 buffer_free( select_buf );
3854 buffer_free( group_buf );
3855 if( defaultselhash )
3856 jsonObjectFree( defaultselhash );
3857 free( join_clause );
3863 if( flags & DISABLE_I18N )
3866 i18n = osrfHashGet( field_def, "i18n" );
3868 if( str_is_true( i18n ) ) {
3869 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
3870 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3871 class_tname, cname, col_name, class_pkey,
3872 cname, class_pkey, locale, col_name );
3874 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3875 cname, col_name, col_name );
3878 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3879 cname, col_name, col_name );
3882 // ... but it could be an object, in which case we check for a Field Transform
3883 } else if( selfield->type == JSON_HASH ) {
3885 const char* col_name = jsonObjectGetString(
3886 jsonObjectGetKeyConst( selfield, "column" ) );
3888 // Get the field definition from the IDL
3889 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3891 // No such field in current class
3894 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3900 osrfAppSessionStatus(
3902 OSRF_STATUS_INTERNALSERVERERROR,
3903 "osrfMethodException",
3905 "Selected column is not defined in JSON query"
3907 jsonIteratorFree( selclass_itr );
3908 buffer_free( select_buf );
3909 buffer_free( group_buf );
3910 if( defaultselhash )
3911 jsonObjectFree( defaultselhash );
3912 free( join_clause );
3914 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
3915 // No such field in current class
3918 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3924 osrfAppSessionStatus(
3926 OSRF_STATUS_INTERNALSERVERERROR,
3927 "osrfMethodException",
3929 "Selected column is virtual in JSON query"
3931 jsonIteratorFree( selclass_itr );
3932 buffer_free( select_buf );
3933 buffer_free( group_buf );
3934 if( defaultselhash )
3935 jsonObjectFree( defaultselhash );
3936 free( join_clause );
3940 // Decide what to use as a column alias
3942 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3943 _alias = jsonObjectGetString( tmp_const );
3944 } else { // Use field name as the alias
3948 if( jsonObjectGetKeyConst( selfield, "transform" )) {
3949 char* transform_str = searchFieldTransform(
3950 class_info->alias, field_def, selfield );
3951 if( transform_str ) {
3952 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
3953 free( transform_str );
3956 osrfAppSessionStatus(
3958 OSRF_STATUS_INTERNALSERVERERROR,
3959 "osrfMethodException",
3961 "Unable to generate transform function in JSON query"
3963 jsonIteratorFree( selclass_itr );
3964 buffer_free( select_buf );
3965 buffer_free( group_buf );
3966 if( defaultselhash )
3967 jsonObjectFree( defaultselhash );
3968 free( join_clause );
3975 if( flags & DISABLE_I18N )
3978 i18n = osrfHashGet( field_def, "i18n" );
3980 if( str_is_true( i18n ) ) {
3981 buffer_fadd( select_buf,
3982 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
3983 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
3984 class_tname, cname, col_name, class_pkey, cname,
3985 class_pkey, locale, _alias );
3987 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3988 cname, col_name, _alias );
3991 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3992 cname, col_name, _alias );
3999 "%s: Selected item is unexpected JSON type: %s",
4001 json_type( selfield->type )
4004 osrfAppSessionStatus(
4006 OSRF_STATUS_INTERNALSERVERERROR,
4007 "osrfMethodException",
4009 "Ill-formed SELECT item in JSON query"
4011 jsonIteratorFree( selclass_itr );
4012 buffer_free( select_buf );
4013 buffer_free( group_buf );
4014 if( defaultselhash )
4015 jsonObjectFree( defaultselhash );
4016 free( join_clause );
4020 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
4021 if( obj_is_true( agg_obj ) )
4022 aggregate_found = 1;
4024 // Append a comma (except for the first one)
4025 // and add the column to a GROUP BY clause
4029 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4031 buffer_fadd( group_buf, " %d", sel_pos );
4035 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4037 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4038 if ( ! obj_is_true( aggregate_obj ) ) {
4042 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4045 buffer_fadd(group_buf, " %d", sel_pos);
4048 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4052 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4055 _column = searchFieldTransform(class_info->alias, field, selfield);
4056 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4057 OSRF_BUFFER_ADD(group_buf, _column);
4058 _column = searchFieldTransform(class_info->alias, field, selfield);
4065 } // end while -- iterating across SELECT columns
4067 } // end while -- iterating across classes
4069 jsonIteratorFree( selclass_itr );
4073 char* col_list = buffer_release( select_buf );
4075 // Make sure the SELECT list isn't empty. This can happen, for example,
4076 // if we try to build a default SELECT clause from a non-core table.
4079 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4081 osrfAppSessionStatus(
4083 OSRF_STATUS_INTERNALSERVERERROR,
4084 "osrfMethodException",
4086 "SELECT list is empty"
4089 buffer_free( group_buf );
4090 if( defaultselhash )
4091 jsonObjectFree( defaultselhash );
4092 free( join_clause );
4098 table = searchValueTransform( join_hash );
4100 table = strdup( curr_query->core.source_def );
4104 osrfAppSessionStatus(
4106 OSRF_STATUS_INTERNALSERVERERROR,
4107 "osrfMethodException",
4109 "Unable to identify table for core class"
4112 buffer_free( group_buf );
4113 if( defaultselhash )
4114 jsonObjectFree( defaultselhash );
4115 free( join_clause );
4119 // Put it all together
4120 growing_buffer* sql_buf = buffer_init( 128 );
4121 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4125 // Append the join clause, if any
4127 buffer_add(sql_buf, join_clause );
4128 free( join_clause );
4131 char* order_by_list = NULL;
4132 char* having_buf = NULL;
4134 if( !from_function ) {
4136 // Build a WHERE clause, if there is one
4138 buffer_add( sql_buf, " WHERE " );
4140 // and it's on the WHERE clause
4141 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4144 osrfAppSessionStatus(
4146 OSRF_STATUS_INTERNALSERVERERROR,
4147 "osrfMethodException",
4149 "Severe query error in WHERE predicate -- see error log for more details"
4152 buffer_free( group_buf );
4153 buffer_free( sql_buf );
4154 if( defaultselhash )
4155 jsonObjectFree( defaultselhash );
4159 buffer_add( sql_buf, pred );
4163 // Build a HAVING clause, if there is one
4166 // and it's on the the WHERE clause
4167 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4169 if( ! having_buf ) {
4171 osrfAppSessionStatus(
4173 OSRF_STATUS_INTERNALSERVERERROR,
4174 "osrfMethodException",
4176 "Severe query error in HAVING predicate -- see error log for more details"
4179 buffer_free( group_buf );
4180 buffer_free( sql_buf );
4181 if( defaultselhash )
4182 jsonObjectFree( defaultselhash );
4187 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4189 // Build an ORDER BY clause, if there is one
4190 if( NULL == order_hash )
4191 ; // No ORDER BY? do nothing
4192 else if( JSON_ARRAY == order_hash->type ) {
4193 // Array of field specifications, each specification being a
4194 // hash to define the class, field, and other details
4196 jsonObject* order_spec;
4197 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4199 if( JSON_HASH != order_spec->type ) {
4200 osrfLogError( OSRF_LOG_MARK,
4201 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4202 modulename, json_type( order_spec->type ) );
4204 osrfAppSessionStatus(
4206 OSRF_STATUS_INTERNALSERVERERROR,
4207 "osrfMethodException",
4209 "Malformed ORDER BY clause -- see error log for more details"
4211 buffer_free( order_buf );
4213 buffer_free( group_buf );
4214 buffer_free( sql_buf );
4215 if( defaultselhash )
4216 jsonObjectFree( defaultselhash );
4220 const char* class_alias =
4221 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4223 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4226 OSRF_BUFFER_ADD( order_buf, ", " );
4228 order_buf = buffer_init( 128 );
4230 if( !field || !class_alias ) {
4231 osrfLogError( OSRF_LOG_MARK,
4232 "%s: Missing class or field name in field specification "
4233 "of ORDER BY clause",
4236 osrfAppSessionStatus(
4238 OSRF_STATUS_INTERNALSERVERERROR,
4239 "osrfMethodException",
4241 "Malformed ORDER BY clause -- see error log for more details"
4243 buffer_free( order_buf );
4245 buffer_free( group_buf );
4246 buffer_free( sql_buf );
4247 if( defaultselhash )
4248 jsonObjectFree( defaultselhash );
4252 ClassInfo* order_class_info = search_alias( class_alias );
4253 if( ! order_class_info ) {
4254 osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4255 "not in FROM clause", modulename, class_alias );
4257 osrfAppSessionStatus(
4259 OSRF_STATUS_INTERNALSERVERERROR,
4260 "osrfMethodException",
4262 "Invalid class referenced in ORDER BY clause -- "
4263 "see error log for more details"
4266 buffer_free( group_buf );
4267 buffer_free( sql_buf );
4268 if( defaultselhash )
4269 jsonObjectFree( defaultselhash );
4273 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4275 osrfLogError( OSRF_LOG_MARK,
4276 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4277 modulename, class_alias, field );
4279 osrfAppSessionStatus(
4281 OSRF_STATUS_INTERNALSERVERERROR,
4282 "osrfMethodException",
4284 "Invalid field referenced in ORDER BY clause -- "
4285 "see error log for more details"
4288 buffer_free( group_buf );
4289 buffer_free( sql_buf );
4290 if( defaultselhash )
4291 jsonObjectFree( defaultselhash );
4293 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4294 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4295 modulename, field );
4297 osrfAppSessionStatus(
4299 OSRF_STATUS_INTERNALSERVERERROR,
4300 "osrfMethodException",
4302 "Virtual field in ORDER BY clause -- see error log for more details"
4304 buffer_free( order_buf );
4306 buffer_free( group_buf );
4307 buffer_free( sql_buf );
4308 if( defaultselhash )
4309 jsonObjectFree( defaultselhash );
4313 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4314 char* transform_str = searchFieldTransform(
4315 class_alias, field_def, order_spec );
4316 if( ! transform_str ) {
4318 osrfAppSessionStatus(
4320 OSRF_STATUS_INTERNALSERVERERROR,
4321 "osrfMethodException",
4323 "Severe query error in ORDER BY clause -- "
4324 "see error log for more details"
4326 buffer_free( order_buf );
4328 buffer_free( group_buf );
4329 buffer_free( sql_buf );
4330 if( defaultselhash )
4331 jsonObjectFree( defaultselhash );
4335 OSRF_BUFFER_ADD( order_buf, transform_str );
4336 free( transform_str );
4339 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4341 const char* direction =
4342 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4344 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4345 OSRF_BUFFER_ADD( order_buf, " DESC" );
4347 OSRF_BUFFER_ADD( order_buf, " ASC" );
4350 } else if( JSON_HASH == order_hash->type ) {
4351 // This hash is keyed on class alias. Each class has either
4352 // an array of field names or a hash keyed on field name.
4353 jsonIterator* class_itr = jsonNewIterator( order_hash );
4354 while( (snode = jsonIteratorNext( class_itr )) ) {
4356 ClassInfo* order_class_info = search_alias( class_itr->key );
4357 if( ! order_class_info ) {
4358 osrfLogError( OSRF_LOG_MARK,
4359 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4360 modulename, class_itr->key );
4362 osrfAppSessionStatus(
4364 OSRF_STATUS_INTERNALSERVERERROR,
4365 "osrfMethodException",
4367 "Invalid class referenced in ORDER BY clause -- "
4368 "see error log for more details"
4370 jsonIteratorFree( class_itr );
4371 buffer_free( order_buf );
4373 buffer_free( group_buf );
4374 buffer_free( sql_buf );
4375 if( defaultselhash )
4376 jsonObjectFree( defaultselhash );
4380 osrfHash* field_list_def = order_class_info->fields;
4382 if( snode->type == JSON_HASH ) {
4384 // Hash is keyed on field names from the current class. For each field
4385 // there is another layer of hash to define the sorting details, if any,
4386 // or a string to indicate direction of sorting.
4387 jsonIterator* order_itr = jsonNewIterator( snode );
4388 while( (onode = jsonIteratorNext( order_itr )) ) {
4390 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4392 osrfLogError( OSRF_LOG_MARK,
4393 "%s: Invalid field \"%s\" in ORDER BY clause",
4394 modulename, order_itr->key );
4396 osrfAppSessionStatus(
4398 OSRF_STATUS_INTERNALSERVERERROR,
4399 "osrfMethodException",
4401 "Invalid field in ORDER BY clause -- "
4402 "see error log for more details"
4404 jsonIteratorFree( order_itr );
4405 jsonIteratorFree( class_itr );
4406 buffer_free( order_buf );
4408 buffer_free( group_buf );
4409 buffer_free( sql_buf );
4410 if( defaultselhash )
4411 jsonObjectFree( defaultselhash );
4413 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4414 osrfLogError( OSRF_LOG_MARK,
4415 "%s: Virtual field \"%s\" in ORDER BY clause",
4416 modulename, order_itr->key );
4418 osrfAppSessionStatus(
4420 OSRF_STATUS_INTERNALSERVERERROR,
4421 "osrfMethodException",
4423 "Virtual field in ORDER BY clause -- "
4424 "see error log for more details"
4426 jsonIteratorFree( order_itr );
4427 jsonIteratorFree( class_itr );
4428 buffer_free( order_buf );
4430 buffer_free( group_buf );
4431 buffer_free( sql_buf );
4432 if( defaultselhash )
4433 jsonObjectFree( defaultselhash );
4437 const char* direction = NULL;
4438 if( onode->type == JSON_HASH ) {
4439 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4440 string = searchFieldTransform(
4442 osrfHashGet( field_list_def, order_itr->key ),
4446 if( ctx ) osrfAppSessionStatus(
4448 OSRF_STATUS_INTERNALSERVERERROR,
4449 "osrfMethodException",
4451 "Severe query error in ORDER BY clause -- "
4452 "see error log for more details"
4454 jsonIteratorFree( order_itr );
4455 jsonIteratorFree( class_itr );
4457 buffer_free( group_buf );
4458 buffer_free( order_buf);
4459 buffer_free( sql_buf );
4460 if( defaultselhash )
4461 jsonObjectFree( defaultselhash );
4465 growing_buffer* field_buf = buffer_init( 16 );
4466 buffer_fadd( field_buf, "\"%s\".%s",
4467 class_itr->key, order_itr->key );
4468 string = buffer_release( field_buf );
4471 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4472 const char* dir = jsonObjectGetString( tmp_const );
4473 if(!strncasecmp( dir, "d", 1 )) {
4474 direction = " DESC";
4480 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4481 osrfLogError( OSRF_LOG_MARK,
4482 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4483 modulename, json_type( onode->type ) );
4485 osrfAppSessionStatus(
4487 OSRF_STATUS_INTERNALSERVERERROR,
4488 "osrfMethodException",
4490 "Malformed ORDER BY clause -- see error log for more details"
4492 jsonIteratorFree( order_itr );
4493 jsonIteratorFree( class_itr );
4495 buffer_free( group_buf );
4496 buffer_free( order_buf );
4497 buffer_free( sql_buf );
4498 if( defaultselhash )
4499 jsonObjectFree( defaultselhash );
4503 string = strdup( order_itr->key );
4504 const char* dir = jsonObjectGetString( onode );
4505 if( !strncasecmp( dir, "d", 1 )) {
4506 direction = " DESC";
4513 OSRF_BUFFER_ADD( order_buf, ", " );
4515 order_buf = buffer_init( 128 );
4517 OSRF_BUFFER_ADD( order_buf, string );
4521 OSRF_BUFFER_ADD( order_buf, direction );
4525 jsonIteratorFree( order_itr );
4527 } else if( snode->type == JSON_ARRAY ) {
4529 // Array is a list of fields from the current class
4530 unsigned long order_idx = 0;
4531 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4533 const char* _f = jsonObjectGetString( onode );
4535 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4537 osrfLogError( OSRF_LOG_MARK,
4538 "%s: Invalid field \"%s\" in ORDER BY clause",
4541 osrfAppSessionStatus(
4543 OSRF_STATUS_INTERNALSERVERERROR,
4544 "osrfMethodException",
4546 "Invalid field in ORDER BY clause -- "
4547 "see error log for more details"
4549 jsonIteratorFree( class_itr );
4550 buffer_free( order_buf );
4552 buffer_free( group_buf );
4553 buffer_free( sql_buf );
4554 if( defaultselhash )
4555 jsonObjectFree( defaultselhash );
4557 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4558 osrfLogError( OSRF_LOG_MARK,
4559 "%s: Virtual field \"%s\" in ORDER BY clause",
4562 osrfAppSessionStatus(
4564 OSRF_STATUS_INTERNALSERVERERROR,
4565 "osrfMethodException",
4567 "Virtual field in ORDER BY clause -- "
4568 "see error log for more details"
4570 jsonIteratorFree( class_itr );
4571 buffer_free( order_buf );
4573 buffer_free( group_buf );
4574 buffer_free( sql_buf );
4575 if( defaultselhash )
4576 jsonObjectFree( defaultselhash );
4581 OSRF_BUFFER_ADD( order_buf, ", " );
4583 order_buf = buffer_init( 128 );
4585 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4589 // IT'S THE OOOOOOOOOOOLD STYLE!
4591 osrfLogError( OSRF_LOG_MARK,
4592 "%s: Possible SQL injection attempt; direct order by is not allowed",
4595 osrfAppSessionStatus(
4597 OSRF_STATUS_INTERNALSERVERERROR,
4598 "osrfMethodException",
4600 "Severe query error -- see error log for more details"
4605 buffer_free( group_buf );
4606 buffer_free( order_buf );
4607 buffer_free( sql_buf );
4608 if( defaultselhash )
4609 jsonObjectFree( defaultselhash );
4610 jsonIteratorFree( class_itr );
4614 jsonIteratorFree( class_itr );
4616 osrfLogError( OSRF_LOG_MARK,
4617 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4618 modulename, json_type( order_hash->type ) );
4620 osrfAppSessionStatus(
4622 OSRF_STATUS_INTERNALSERVERERROR,
4623 "osrfMethodException",
4625 "Malformed ORDER BY clause -- see error log for more details"
4627 buffer_free( order_buf );
4629 buffer_free( group_buf );
4630 buffer_free( sql_buf );
4631 if( defaultselhash )
4632 jsonObjectFree( defaultselhash );
4637 order_by_list = buffer_release( order_buf );
4641 string = buffer_release( group_buf );
4643 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4644 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4645 OSRF_BUFFER_ADD( sql_buf, string );
4650 if( having_buf && *having_buf ) {
4651 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4652 OSRF_BUFFER_ADD( sql_buf, having_buf );
4656 if( order_by_list ) {
4658 if( *order_by_list ) {
4659 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4660 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4663 free( order_by_list );
4667 const char* str = jsonObjectGetString( limit );
4668 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4672 const char* str = jsonObjectGetString( offset );
4673 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4676 if( !(flags & SUBSELECT) )
4677 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4679 if( defaultselhash )
4680 jsonObjectFree( defaultselhash );
4682 return buffer_release( sql_buf );
4684 } // end of SELECT()
4686 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4688 const char* locale = osrf_message_get_last_locale();
4690 osrfHash* fields = osrfHashGet( meta, "fields" );
4691 char* core_class = osrfHashGet( meta, "classname" );
4693 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4695 jsonObject* node = NULL;
4696 jsonObject* snode = NULL;
4697 jsonObject* onode = NULL;
4698 const jsonObject* _tmp = NULL;
4699 jsonObject* selhash = NULL;
4700 jsonObject* defaultselhash = NULL;
4702 growing_buffer* sql_buf = buffer_init( 128 );
4703 growing_buffer* select_buf = buffer_init( 128 );
4705 if( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4706 defaultselhash = jsonNewObjectType( JSON_HASH );
4707 selhash = defaultselhash;
4710 // If there's no SELECT list for the core class, build one
4711 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4712 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4714 // Add every non-virtual field to the field list
4715 osrfHash* field_def = NULL;
4716 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4717 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4718 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4719 const char* field = osrfHashIteratorKey( field_itr );
4720 jsonObjectPush( field_list, jsonNewObject( field ) );
4723 osrfHashIteratorFree( field_itr );
4724 jsonObjectSetKey( selhash, core_class, field_list );
4728 jsonIterator* class_itr = jsonNewIterator( selhash );
4729 while( (snode = jsonIteratorNext( class_itr )) ) {
4731 const char* cname = class_itr->key;
4732 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4736 if( strcmp(core_class,class_itr->key )) {
4740 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4741 if( !found->size ) {
4742 jsonObjectFree( found );
4746 jsonObjectFree( found );
4749 jsonIterator* select_itr = jsonNewIterator( snode );
4750 while( (node = jsonIteratorNext( select_itr )) ) {
4751 const char* item_str = jsonObjectGetString( node );
4752 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4753 char* fname = osrfHashGet( field, "name" );
4761 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4766 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4767 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4770 i18n = osrfHashGet( field, "i18n" );
4772 if( str_is_true( i18n ) ) {
4773 char* pkey = osrfHashGet( idlClass, "primarykey" );
4774 char* tname = osrfHashGet( idlClass, "tablename" );
4776 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4777 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4778 tname, cname, fname, pkey, cname, pkey, locale, fname );
4780 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4783 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4787 jsonIteratorFree( select_itr );
4790 jsonIteratorFree( class_itr );
4792 char* col_list = buffer_release( select_buf );
4793 char* table = oilsGetRelation( meta );
4795 table = strdup( "(null)" );
4797 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4801 // Clear the query stack (as a fail-safe precaution against possible
4802 // leftover garbage); then push the first query frame onto the stack.
4803 clear_query_stack();
4805 if( add_query_core( NULL, core_class ) ) {
4807 osrfAppSessionStatus(
4809 OSRF_STATUS_INTERNALSERVERERROR,
4810 "osrfMethodException",
4812 "Unable to build query frame for core class"
4818 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4819 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
4820 OSRF_BUFFER_ADD( sql_buf, join_clause );
4821 free( join_clause );
4824 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4825 modulename, OSRF_BUFFER_C_STR( sql_buf ));
4827 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
4829 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4831 osrfAppSessionStatus(
4833 OSRF_STATUS_INTERNALSERVERERROR,
4834 "osrfMethodException",
4836 "Severe query error -- see error log for more details"
4838 buffer_free( sql_buf );
4839 if( defaultselhash )
4840 jsonObjectFree( defaultselhash );
4841 clear_query_stack();
4844 buffer_add( sql_buf, pred );
4849 char* string = NULL;
4850 if( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4852 growing_buffer* order_buf = buffer_init( 128 );
4855 jsonIterator* class_itr = jsonNewIterator( _tmp );
4856 while( (snode = jsonIteratorNext( class_itr )) ) {
4858 if( !jsonObjectGetKeyConst( selhash,class_itr->key ))
4861 if( snode->type == JSON_HASH ) {
4863 jsonIterator* order_itr = jsonNewIterator( snode );
4864 while( (onode = jsonIteratorNext( order_itr )) ) {
4866 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4867 class_itr->key, order_itr->key );
4871 char* direction = NULL;
4872 if( onode->type == JSON_HASH ) {
4873 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4874 string = searchFieldTransform( class_itr->key, field_def, onode );
4876 osrfAppSessionStatus(
4878 OSRF_STATUS_INTERNALSERVERERROR,
4879 "osrfMethodException",
4881 "Severe query error in ORDER BY clause -- "
4882 "see error log for more details"
4884 jsonIteratorFree( order_itr );
4885 jsonIteratorFree( class_itr );
4886 buffer_free( order_buf );
4887 buffer_free( sql_buf );
4888 if( defaultselhash )
4889 jsonObjectFree( defaultselhash );
4890 clear_query_stack();
4894 growing_buffer* field_buf = buffer_init( 16 );
4895 buffer_fadd( field_buf, "\"%s\".%s",
4896 class_itr->key, order_itr->key );
4897 string = buffer_release( field_buf );
4900 if( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4901 const char* dir = jsonObjectGetString( _tmp );
4902 if(!strncasecmp( dir, "d", 1 )) {
4903 direction = " DESC";
4907 string = strdup( order_itr->key );
4908 const char* dir = jsonObjectGetString( onode );
4909 if( !strncasecmp( dir, "d", 1 )) {
4910 direction = " DESC";
4919 buffer_add( order_buf, ", " );
4922 buffer_add( order_buf, string );
4926 buffer_add( order_buf, direction );
4930 jsonIteratorFree( order_itr );
4933 const char* str = jsonObjectGetString( snode );
4934 buffer_add( order_buf, str );
4940 jsonIteratorFree( class_itr );
4942 string = buffer_release( order_buf );
4945 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4946 OSRF_BUFFER_ADD( sql_buf, string );
4952 if( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ) {
4953 const char* str = jsonObjectGetString( _tmp );
4961 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4963 const char* str = jsonObjectGetString( _tmp );
4972 if( defaultselhash )
4973 jsonObjectFree( defaultselhash );
4974 clear_query_stack();
4976 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4977 return buffer_release( sql_buf );
4980 int doJSONSearch ( osrfMethodContext* ctx ) {
4981 if(osrfMethodVerifyContext( ctx )) {
4982 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4986 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
4991 dbhandle = writehandle;
4993 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
4997 if( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4998 flags |= SELECT_DISTINCT;
5000 if( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
5001 flags |= DISABLE_I18N;
5003 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5004 clear_query_stack(); // a possibly needless precaution
5005 char* sql = buildQuery( ctx, hash, flags );
5006 clear_query_stack();
5013 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5014 dbi_result result = dbi_conn_query( dbhandle, sql );
5017 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5019 if( dbi_result_first_row( result )) {
5020 /* JSONify the result */
5021 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5024 jsonObject* return_val = oilsMakeJSONFromResult( result );
5025 osrfAppRespond( ctx, return_val );
5026 jsonObjectFree( return_val );
5027 } while( dbi_result_next_row( result ));
5030 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5033 osrfAppRespondComplete( ctx, NULL );
5035 /* clean up the query */
5036 dbi_result_free( result );
5040 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]", modulename, sql );
5041 osrfAppSessionStatus(
5043 OSRF_STATUS_INTERNALSERVERERROR,
5044 "osrfMethodException",
5046 "Severe query error -- see error log for more details"
5054 // The last parameter, err, is used to report an error condition by updating an int owned by
5055 // the calling code.
5057 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5058 // It is the responsibility of the calling code to initialize *err before the
5059 // call, so that it will be able to make sense of the result.
5061 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5062 // redundant anyway.
5063 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5064 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5067 dbhandle = writehandle;
5069 char* core_class = osrfHashGet( class_meta, "classname" );
5070 char* pkey = osrfHashGet( class_meta, "primarykey" );
5072 const jsonObject* _tmp;
5074 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5076 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5081 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5083 dbi_result result = dbi_conn_query( dbhandle, sql );
5084 if( NULL == result ) {
5085 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
5086 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql );
5087 osrfAppSessionStatus(
5089 OSRF_STATUS_INTERNALSERVERERROR,
5090 "osrfMethodException",
5092 "Severe query error -- see error log for more details"
5099 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5102 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5103 jsonObject* row_obj = NULL;
5105 if( dbi_result_first_row( result )) {
5107 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5108 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5109 // eliminate the duplicates.
5110 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5111 osrfHash* dedup = osrfNewHash();
5113 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5114 char* pkey_val = oilsFMGetString( row_obj, pkey );
5115 if( osrfHashGet( dedup, pkey_val ) ) {
5116 jsonObjectFree( row_obj );
5119 osrfHashSet( dedup, pkey_val, pkey_val );
5120 jsonObjectPush( res_list, row_obj );
5122 } while( dbi_result_next_row( result ));
5123 osrfHashFree( dedup );
5126 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5130 /* clean up the query */
5131 dbi_result_free( result );
5134 // If we're asked to flesh, and there's anything to flesh, then flesh.
5135 if( res_list->size && query_hash ) {
5136 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5138 // Get the flesh depth
5139 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5140 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5141 flesh_depth = max_flesh_depth;
5143 // We need a non-zero flesh depth, and a list of fields to flesh
5144 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5145 if( temp_blob && flesh_depth > 0 ) {
5147 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5148 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5150 osrfStringArray* link_fields = NULL;
5151 osrfHash* links = osrfHashGet( class_meta, "links" );
5153 // Make an osrfStringArray of the names of fields to be fleshed
5154 if( flesh_fields ) {
5155 if( flesh_fields->size == 1 ) {
5156 const char* _t = jsonObjectGetString(
5157 jsonObjectGetIndex( flesh_fields, 0 ) );
5158 if( !strcmp( _t, "*" ))
5159 link_fields = osrfHashKeys( links );
5162 if( !link_fields ) {
5164 link_fields = osrfNewStringArray( 1 );
5165 jsonIterator* _i = jsonNewIterator( flesh_fields );
5166 while ((_f = jsonIteratorNext( _i ))) {
5167 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5169 jsonIteratorFree( _i );
5173 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5175 // Iterate over the JSON_ARRAY of rows
5177 unsigned long res_idx = 0;
5178 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5181 const char* link_field;
5183 // Iterate over the list of fleshable fields
5184 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5186 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5188 osrfHash* kid_link = osrfHashGet( links, link_field );
5190 continue; // Not a link field; skip it
5192 osrfHash* field = osrfHashGet( fields, link_field );
5194 continue; // Not a field at all; skip it (IDL is ill-formed)
5196 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5197 osrfHashGet( kid_link, "class" ));
5199 continue; // The class it links to doesn't exist; skip it
5201 const char* reltype = osrfHashGet( kid_link, "reltype" );
5203 continue; // No reltype; skip it (IDL is ill-formed)
5205 osrfHash* value_field = field;
5207 if( !strcmp( reltype, "has_many" )
5208 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5209 value_field = osrfHashGet(
5210 fields, osrfHashGet( class_meta, "primarykey" ) );
5213 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5215 if( link_map->size > 0 ) {
5216 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5219 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5224 osrfHashGet( kid_link, "class" ),
5231 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5232 osrfHashGet( kid_link, "field" ),
5233 osrfHashGet( kid_link, "class" ),
5234 osrfHashGet( kid_link, "key" ),
5235 osrfHashGet( kid_link, "reltype" )
5238 const char* search_key = jsonObjectGetString(
5239 jsonObjectGetIndex( cur,
5240 atoi( osrfHashGet( value_field, "array_position" ) )
5245 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5249 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5251 // construct WHERE clause
5252 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5255 osrfHashGet( kid_link, "key" ),
5256 jsonNewObject( search_key )
5259 // construct the rest of the query, mostly
5260 // by copying pieces of the previous level of query
5261 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5262 jsonObjectSetKey( rest_of_query, "flesh",
5263 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5267 jsonObjectSetKey( rest_of_query, "flesh_fields",
5268 jsonObjectClone( flesh_blob ));
5270 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5271 jsonObjectSetKey( rest_of_query, "order_by",
5272 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5276 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5277 jsonObjectSetKey( rest_of_query, "select",
5278 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5282 // do the query, recursively, to expand the fleshable field
5283 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5284 where_clause, rest_of_query, err );
5286 jsonObjectFree( where_clause );
5287 jsonObjectFree( rest_of_query );
5290 osrfStringArrayFree( link_fields );
5291 jsonObjectFree( res_list );
5292 jsonObjectFree( flesh_blob );
5296 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5297 osrfHashGet( kid_link, "class" ), kids->size );
5299 // Traverse the result set
5300 jsonObject* X = NULL;
5301 if( link_map->size > 0 && kids->size > 0 ) {
5303 kids = jsonNewObjectType( JSON_ARRAY );
5305 jsonObject* _k_node;
5306 unsigned long res_idx = 0;
5307 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5313 (unsigned long) atoi(
5319 osrfHashGet( kid_link, "class" )
5323 osrfStringArrayGetString( link_map, 0 )
5331 } // end while loop traversing X
5334 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5335 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5336 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5337 osrfHashGet( kid_link, "field" ));
5340 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5341 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5345 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5347 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5348 osrfHashGet( kid_link, "field" ) );
5351 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5352 jsonObjectClone( kids )
5357 jsonObjectFree( kids );
5361 jsonObjectFree( kids );
5363 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5364 osrfHashGet( kid_link, "field" ) );
5365 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5367 } // end while loop traversing list of fleshable fields
5368 } // end while loop traversing res_list
5369 jsonObjectFree( flesh_blob );
5370 osrfStringArrayFree( link_fields );
5379 int doUpdate( osrfMethodContext* ctx ) {
5380 if( osrfMethodVerifyContext( ctx )) {
5381 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5386 timeout_needs_resetting = 1;
5388 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5390 jsonObject* target = NULL;
5392 target = jsonObjectGetIndex( ctx->params, 1 );
5394 target = jsonObjectGetIndex( ctx->params, 0 );
5396 if(!verifyObjectClass( ctx, target )) {
5397 osrfAppRespondComplete( ctx, NULL );
5401 if( getXactId( ctx ) == NULL ) {
5402 osrfAppSessionStatus(
5404 OSRF_STATUS_BADREQUEST,
5405 "osrfMethodException",
5407 "No active transaction -- required for UPDATE"
5409 osrfAppRespondComplete( ctx, NULL );
5413 // The following test is harmless but redundant. If a class is
5414 // readonly, we don't register an update method for it.
5415 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5416 osrfAppSessionStatus(
5418 OSRF_STATUS_BADREQUEST,
5419 "osrfMethodException",
5421 "Cannot UPDATE readonly class"
5423 osrfAppRespondComplete( ctx, NULL );
5427 dbhandle = writehandle;
5428 const char* trans_id = getXactId( ctx );
5430 // Set the last_xact_id
5431 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5433 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5434 trans_id, target->classname, index );
5435 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5438 char* pkey = osrfHashGet( meta, "primarykey" );
5439 osrfHash* fields = osrfHashGet( meta, "fields" );
5441 char* id = oilsFMGetString( target, pkey );
5445 "%s updating %s object with %s = %s",
5447 osrfHashGet( meta, "fieldmapper" ),
5452 growing_buffer* sql = buffer_init( 128 );
5453 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5456 osrfHash* field_def = NULL;
5457 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5458 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5460 // Skip virtual fields, and the primary key
5461 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5464 const char* field_name = osrfHashIteratorKey( field_itr );
5465 if( ! strcmp( field_name, pkey ) )
5468 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5470 int value_is_numeric = 0; // boolean
5472 if( field_object && field_object->classname ) {
5473 value = oilsFMGetString(
5475 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5477 } else if( field_object && JSON_BOOL == field_object->type ) {
5478 if( jsonBoolIsTrue( field_object ) )
5479 value = strdup( "t" );
5481 value = strdup( "f" );
5483 value = jsonObjectToSimpleString( field_object );
5484 if( field_object && JSON_NUMBER == field_object->type )
5485 value_is_numeric = 1;
5488 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5489 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5491 if( !field_object || field_object->type == JSON_NULL ) {
5492 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5493 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5497 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5498 buffer_fadd( sql, " %s = NULL", field_name );
5501 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5505 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5507 const char* numtype = get_datatype( field_def );
5508 if( !strncmp( numtype, "INT", 3 ) ) {
5509 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5510 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5511 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5513 // Must really be intended as a string, so quote it
5514 if( dbi_conn_quote_string( dbhandle, &value )) {
5515 buffer_fadd( sql, " %s = %s", field_name, value );
5517 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5518 modulename, value );
5519 osrfAppSessionStatus(
5521 OSRF_STATUS_INTERNALSERVERERROR,
5522 "osrfMethodException",
5524 "Error quoting string -- please see the error log for more details"
5528 osrfHashIteratorFree( field_itr );
5530 osrfAppRespondComplete( ctx, NULL );
5535 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5538 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5542 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5543 buffer_fadd( sql, " %s = %s", field_name, value );
5545 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5546 osrfAppSessionStatus(
5548 OSRF_STATUS_INTERNALSERVERERROR,
5549 "osrfMethodException",
5551 "Error quoting string -- please see the error log for more details"
5555 osrfHashIteratorFree( field_itr );
5557 osrfAppRespondComplete( ctx, NULL );
5566 osrfHashIteratorFree( field_itr );
5568 jsonObject* obj = jsonNewObject( id );
5570 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5571 dbi_conn_quote_string( dbhandle, &id );
5573 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5575 char* query = buffer_release( sql );
5576 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5578 dbi_result result = dbi_conn_query( dbhandle, query );
5582 jsonObjectFree( obj );
5583 obj = jsonNewObject( NULL );
5586 "%s ERROR updating %s object with %s = %s",
5588 osrfHashGet( meta, "fieldmapper" ),
5595 osrfAppRespondComplete( ctx, obj );
5596 jsonObjectFree( obj );
5600 int doDelete( osrfMethodContext* ctx ) {
5601 if( osrfMethodVerifyContext( ctx )) {
5602 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5607 timeout_needs_resetting = 1;
5609 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5611 if( getXactId( ctx ) == NULL ) {
5612 osrfAppSessionStatus(
5614 OSRF_STATUS_BADREQUEST,
5615 "osrfMethodException",
5617 "No active transaction -- required for DELETE"
5619 osrfAppRespondComplete( ctx, NULL );
5623 // The following test is harmless but redundant. If a class is
5624 // readonly, we don't register a delete method for it.
5625 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5626 osrfAppSessionStatus(
5628 OSRF_STATUS_BADREQUEST,
5629 "osrfMethodException",
5631 "Cannot DELETE readonly class"
5633 osrfAppRespondComplete( ctx, NULL );
5637 dbhandle = writehandle;
5639 char* pkey = osrfHashGet( meta, "primarykey" );
5646 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5647 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5648 osrfAppRespondComplete( ctx, NULL );
5652 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5654 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5655 osrfAppRespondComplete( ctx, NULL );
5658 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5663 "%s deleting %s object with %s = %s",
5665 osrfHashGet( meta, "fieldmapper" ),
5670 jsonObject* obj = jsonNewObject( id );
5672 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5673 dbi_conn_quote_string( writehandle, &id );
5675 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
5676 osrfHashGet( meta, "tablename" ), pkey, id );
5679 jsonObjectFree( obj );
5680 obj = jsonNewObject( NULL );
5683 "%s ERROR deleting %s object with %s = %s",
5685 osrfHashGet( meta, "fieldmapper" ),
5693 osrfAppRespondComplete( ctx, obj );
5694 jsonObjectFree( obj );
5699 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5700 @param result An iterator for a result set; we only look at the current row.
5701 @param @meta Pointer to the class metadata for the core class.
5702 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5704 If a column is not defined in the IDL, or if it has no array_position defined for it in
5705 the IDL, or if it is defined as virtual, ignore it.
5707 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5708 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5709 array_position in the IDL.
5711 A field defined in the IDL but not represented in the returned row will leave a hole
5712 in the JSON_ARRAY. In effect it will be treated as a null value.
5714 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5715 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5716 classname corresponding to the @a meta argument.
5718 The calling code is responsible for freeing the the resulting jsonObject by calling
5721 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5722 if( !( result && meta )) return NULL;
5724 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5725 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
5726 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
5728 osrfHash* fields = osrfHashGet( meta, "fields" );
5730 int columnIndex = 1;
5731 const char* columnName;
5733 /* cycle through the columns in the row returned from the database */
5734 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
5736 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5738 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5740 /* determine the field type and storage attributes */
5741 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
5742 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5744 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5745 // or if it has no sequence number there, or if it's virtual, skip it.
5746 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
5749 if( str_is_true( osrfHashGet( _f, "virtual" )))
5750 continue; // skip this column: IDL says it's virtual
5752 const char* pos = (char*) osrfHashGet( _f, "array_position" );
5753 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
5754 continue; // since we assign sequence numbers dynamically as we load the IDL.
5756 fmIndex = atoi( pos );
5757 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
5759 continue; // This field is not defined in the IDL
5762 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
5763 // sequence number from the IDL (which is likely to be different from the sequence
5764 // of columns in the SELECT clause).
5765 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5766 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
5771 case DBI_TYPE_INTEGER :
5773 if( attr & DBI_INTEGER_SIZE8 )
5774 jsonObjectSetIndex( object, fmIndex,
5775 jsonNewNumberObject(
5776 dbi_result_get_longlong_idx( result, columnIndex )));
5778 jsonObjectSetIndex( object, fmIndex,
5779 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
5783 case DBI_TYPE_DECIMAL :
5784 jsonObjectSetIndex( object, fmIndex,
5785 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
5788 case DBI_TYPE_STRING :
5793 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
5798 case DBI_TYPE_DATETIME : {
5800 char dt_string[ 256 ] = "";
5803 // Fetch the date column as a time_t
5804 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
5806 // Translate the time_t to a human-readable string
5807 if( !( attr & DBI_DATETIME_DATE )) {
5808 gmtime_r( &_tmp_dt, &gmdt );
5809 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
5810 } else if( !( attr & DBI_DATETIME_TIME )) {
5811 localtime_r( &_tmp_dt, &gmdt );
5812 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
5814 localtime_r( &_tmp_dt, &gmdt );
5815 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
5818 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
5822 case DBI_TYPE_BINARY :
5823 osrfLogError( OSRF_LOG_MARK,
5824 "Can't do binary at column %s : index %d", columnName, columnIndex );
5833 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5834 if( !result ) return NULL;
5836 jsonObject* object = jsonNewObject( NULL );
5839 char dt_string[ 256 ];
5843 int columnIndex = 1;
5845 unsigned short type;
5846 const char* columnName;
5848 /* cycle through the column list */
5849 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
5851 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5853 fmIndex = -1; // reset the position
5855 /* determine the field type and storage attributes */
5856 type = dbi_result_get_field_type_idx( result, columnIndex );
5857 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5859 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5860 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
5865 case DBI_TYPE_INTEGER :
5867 if( attr & DBI_INTEGER_SIZE8 )
5868 jsonObjectSetKey( object, columnName,
5869 jsonNewNumberObject( dbi_result_get_longlong_idx(
5870 result, columnIndex )) );
5872 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
5873 dbi_result_get_int_idx( result, columnIndex )) );
5876 case DBI_TYPE_DECIMAL :
5877 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
5878 dbi_result_get_double_idx( result, columnIndex )) );
5881 case DBI_TYPE_STRING :
5882 jsonObjectSetKey( object, columnName,
5883 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
5886 case DBI_TYPE_DATETIME :
5888 memset( dt_string, '\0', sizeof( dt_string ));
5889 memset( &gmdt, '\0', sizeof( gmdt ));
5891 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
5893 if( !( attr & DBI_DATETIME_DATE )) {
5894 gmtime_r( &_tmp_dt, &gmdt );
5895 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
5896 } else if( !( attr & DBI_DATETIME_TIME )) {
5897 localtime_r( &_tmp_dt, &gmdt );
5898 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
5900 localtime_r( &_tmp_dt, &gmdt );
5901 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
5904 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
5907 case DBI_TYPE_BINARY :
5908 osrfLogError( OSRF_LOG_MARK,
5909 "Can't do binary at column %s : index %d", columnName, columnIndex );
5913 } // end while loop traversing result
5918 // Interpret a string as true or false
5919 int str_is_true( const char* str ) {
5920 if( NULL == str || strcasecmp( str, "true" ) )
5926 // Interpret a jsonObject as true or false
5927 static int obj_is_true( const jsonObject* obj ) {
5930 else switch( obj->type )
5938 if( strcasecmp( obj->value.s, "true" ) )
5942 case JSON_NUMBER : // Support 1/0 for perl's sake
5943 if( jsonObjectGetNumber( obj ) == 1.0 )
5952 // Translate a numeric code into a text string identifying a type of
5953 // jsonObject. To be used for building error messages.
5954 static const char* json_type( int code ) {
5960 return "JSON_ARRAY";
5962 return "JSON_STRING";
5964 return "JSON_NUMBER";
5970 return "(unrecognized)";
5974 // Extract the "primitive" attribute from an IDL field definition.
5975 // If we haven't initialized the app, then we must be running in
5976 // some kind of testbed. In that case, default to "string".
5977 static const char* get_primitive( osrfHash* field ) {
5978 const char* s = osrfHashGet( field, "primitive" );
5980 if( child_initialized )
5983 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5985 osrfHashGet( field, "name" )
5993 // Extract the "datatype" attribute from an IDL field definition.
5994 // If we haven't initialized the app, then we must be running in
5995 // some kind of testbed. In that case, default to to NUMERIC,
5996 // since we look at the datatype only for numbers.
5997 static const char* get_datatype( osrfHash* field ) {
5998 const char* s = osrfHashGet( field, "datatype" );
6000 if( child_initialized )
6003 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6005 osrfHashGet( field, "name" )
6014 @brief Determine whether a string is potentially a valid SQL identifier.
6015 @param s The identifier to be tested.
6016 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6018 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6019 need to follow all the rules exactly, such as requiring that the first character not
6022 We allow leading and trailing white space. In between, we do not allow punctuation
6023 (except for underscores and dollar signs), control characters, or embedded white space.
6025 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6026 for the foreseeable future such quoted identifiers are not likely to be an issue.
6028 static int is_identifier( const char* s) {
6032 // Skip leading white space
6033 while( isspace( (unsigned char) *s ) )
6037 return 0; // Nothing but white space? Not okay.
6039 // Check each character until we reach white space or
6040 // end-of-string. Letters, digits, underscores, and
6041 // dollar signs are okay. With the exception of periods
6042 // (as in schema.identifier), control characters and other
6043 // punctuation characters are not okay. Anything else
6044 // is okay -- it could for example be part of a multibyte
6045 // UTF8 character such as a letter with diacritical marks,
6046 // and those are allowed.
6048 if( isalnum( (unsigned char) *s )
6052 ; // Fine; keep going
6053 else if( ispunct( (unsigned char) *s )
6054 || iscntrl( (unsigned char) *s ) )
6057 } while( *s && ! isspace( (unsigned char) *s ) );
6059 // If we found any white space in the above loop,
6060 // the rest had better be all white space.
6062 while( isspace( (unsigned char) *s ) )
6066 return 0; // White space was embedded within non-white space
6072 @brief Determine whether to accept a character string as a comparison operator.
6073 @param op The candidate comparison operator.
6074 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6076 We don't validate the operator for real. We just make sure that it doesn't contain
6077 any semicolons or white space (with special exceptions for a few specific operators).
6078 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6079 space but it's still not a valid operator, then the database will complain.
6081 Another approach would be to compare the string against a short list of approved operators.
6082 We don't do that because we want to allow custom operators like ">100*", which at this
6083 writing would be difficult or impossible to express otherwise in a JSON query.
6085 static int is_good_operator( const char* op ) {
6086 if( !op ) return 0; // Sanity check
6090 if( isspace( (unsigned char) *s ) ) {
6091 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6092 // and IS NOT DISTINCT FROM.
6093 if( !strcasecmp( op, "similar to" ) )
6095 else if( !strcasecmp( op, "is distinct from" ) )
6097 else if( !strcasecmp( op, "is not distinct from" ) )
6102 else if( ';' == *s )
6110 @name Query Frame Management
6112 The following machinery supports a stack of query frames for use by SELECT().
6114 A query frame caches information about one level of a SELECT query. When we enter
6115 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6117 The query frame stores information about the core class, and about any joined classes
6120 The main purpose is to map table aliases to classes and tables, so that a query can
6121 join to the same table more than once. A secondary goal is to reduce the number of
6122 lookups in the IDL by caching the results.
6126 #define STATIC_CLASS_INFO_COUNT 3
6128 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6131 @brief Allocate a ClassInfo as raw memory.
6132 @return Pointer to the newly allocated ClassInfo.
6134 Except for the in_use flag, which is used only by the allocation and deallocation
6135 logic, we don't initialize the ClassInfo here.
6137 static ClassInfo* allocate_class_info( void ) {
6138 // In order to reduce the number of mallocs and frees, we return a static
6139 // instance of ClassInfo, if we can find one that we're not already using.
6140 // We rely on the fact that the compiler will implicitly initialize the
6141 // static instances so that in_use == 0.
6144 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6145 if( ! static_class_info[ i ].in_use ) {
6146 static_class_info[ i ].in_use = 1;
6147 return static_class_info + i;
6151 // The static ones are all in use. Malloc one.
6153 return safe_malloc( sizeof( ClassInfo ) );
6157 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6158 @param info Pointer to the ClassInfo to be cleared.
6160 static void clear_class_info( ClassInfo* info ) {
6165 // Free any malloc'd strings
6167 if( info->alias != info->alias_store )
6168 free( info->alias );
6170 if( info->class_name != info->class_name_store )
6171 free( info->class_name );
6173 free( info->source_def );
6175 info->alias = info->class_name = info->source_def = NULL;
6180 @brief Free a ClassInfo and everything it owns.
6181 @param info Pointer to the ClassInfo to be freed.
6183 static void free_class_info( ClassInfo* info ) {
6188 clear_class_info( info );
6190 // If it's one of the static instances, just mark it as not in use
6193 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6194 if( info == static_class_info + i ) {
6195 static_class_info[ i ].in_use = 0;
6200 // Otherwise it must have been malloc'd, so free it
6206 @brief Populate an already-allocated ClassInfo.
6207 @param info Pointer to the ClassInfo to be populated.
6208 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6210 @param class Name of the class.
6211 @return Zero if successful, or 1 if not.
6213 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6214 the relevant portions of the IDL for the specified class.
6216 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6219 osrfLogError( OSRF_LOG_MARK,
6220 "%s ERROR: No ClassInfo available to populate", modulename );
6221 info->alias = info->class_name = info->source_def = NULL;
6222 info->class_def = info->fields = info->links = NULL;
6227 osrfLogError( OSRF_LOG_MARK,
6228 "%s ERROR: No class name provided for lookup", modulename );
6229 info->alias = info->class_name = info->source_def = NULL;
6230 info->class_def = info->fields = info->links = NULL;
6234 // Alias defaults to class name if not supplied
6235 if( ! alias || ! alias[ 0 ] )
6238 // Look up class info in the IDL
6239 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6241 osrfLogError( OSRF_LOG_MARK,
6242 "%s ERROR: Class %s not defined in IDL", modulename, class );
6243 info->alias = info->class_name = info->source_def = NULL;
6244 info->class_def = info->fields = info->links = NULL;
6246 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6247 osrfLogError( OSRF_LOG_MARK,
6248 "%s ERROR: Class %s is defined as virtual", modulename, class );
6249 info->alias = info->class_name = info->source_def = NULL;
6250 info->class_def = info->fields = info->links = NULL;
6254 osrfHash* links = osrfHashGet( class_def, "links" );
6256 osrfLogError( OSRF_LOG_MARK,
6257 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6258 info->alias = info->class_name = info->source_def = NULL;
6259 info->class_def = info->fields = info->links = NULL;
6263 osrfHash* fields = osrfHashGet( class_def, "fields" );
6265 osrfLogError( OSRF_LOG_MARK,
6266 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6267 info->alias = info->class_name = info->source_def = NULL;
6268 info->class_def = info->fields = info->links = NULL;
6272 char* source_def = oilsGetRelation( class_def );
6276 // We got everything we need, so populate the ClassInfo
6277 if( strlen( alias ) > ALIAS_STORE_SIZE )
6278 info->alias = strdup( alias );
6280 strcpy( info->alias_store, alias );
6281 info->alias = info->alias_store;
6284 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6285 info->class_name = strdup( class );
6287 strcpy( info->class_name_store, class );
6288 info->class_name = info->class_name_store;
6291 info->source_def = source_def;
6293 info->class_def = class_def;
6294 info->links = links;
6295 info->fields = fields;
6300 #define STATIC_FRAME_COUNT 3
6302 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6305 @brief Allocate a QueryFrame as raw memory.
6306 @return Pointer to the newly allocated QueryFrame.
6308 Except for the in_use flag, which is used only by the allocation and deallocation
6309 logic, we don't initialize the QueryFrame here.
6311 static QueryFrame* allocate_frame( void ) {
6312 // In order to reduce the number of mallocs and frees, we return a static
6313 // instance of QueryFrame, if we can find one that we're not already using.
6314 // We rely on the fact that the compiler will implicitly initialize the
6315 // static instances so that in_use == 0.
6318 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6319 if( ! static_frame[ i ].in_use ) {
6320 static_frame[ i ].in_use = 1;
6321 return static_frame + i;
6325 // The static ones are all in use. Malloc one.
6327 return safe_malloc( sizeof( QueryFrame ) );
6331 @brief Free a QueryFrame, and all the memory it owns.
6332 @param frame Pointer to the QueryFrame to be freed.
6334 static void free_query_frame( QueryFrame* frame ) {
6339 clear_class_info( &frame->core );
6341 // Free the join list
6343 ClassInfo* info = frame->join_list;
6346 free_class_info( info );
6350 frame->join_list = NULL;
6353 // If the frame is a static instance, just mark it as unused
6355 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6356 if( frame == static_frame + i ) {
6357 static_frame[ i ].in_use = 0;
6362 // Otherwise it must have been malloc'd, so free it
6368 @brief Search a given QueryFrame for a specified alias.
6369 @param frame Pointer to the QueryFrame to be searched.
6370 @param target The alias for which to search.
6371 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6373 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6374 if( ! frame || ! target ) {
6378 ClassInfo* found_class = NULL;
6380 if( !strcmp( target, frame->core.alias ) )
6381 return &(frame->core);
6383 ClassInfo* curr_class = frame->join_list;
6384 while( curr_class ) {
6385 if( strcmp( target, curr_class->alias ) )
6386 curr_class = curr_class->next;
6388 found_class = curr_class;
6398 @brief Push a new (blank) QueryFrame onto the stack.
6400 static void push_query_frame( void ) {
6401 QueryFrame* frame = allocate_frame();
6402 frame->join_list = NULL;
6403 frame->next = curr_query;
6405 // Initialize the ClassInfo for the core class
6406 ClassInfo* core = &frame->core;
6407 core->alias = core->class_name = core->source_def = NULL;
6408 core->class_def = core->fields = core->links = NULL;
6414 @brief Pop a QueryFrame off the stack and destroy it.
6416 static void pop_query_frame( void ) {
6421 QueryFrame* popped = curr_query;
6422 curr_query = popped->next;
6424 free_query_frame( popped );
6428 @brief Populate the ClassInfo for the core class.
6429 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6430 class name as an alias.
6431 @param class_name Name of the core class.
6432 @return Zero if successful, or 1 if not.
6434 Populate the ClassInfo of the core class with copies of the alias and class name, and
6435 with pointers to the relevant portions of the IDL for the core class.
6437 static int add_query_core( const char* alias, const char* class_name ) {
6440 if( ! curr_query ) {
6441 osrfLogError( OSRF_LOG_MARK,
6442 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6444 } else if( curr_query->core.alias ) {
6445 osrfLogError( OSRF_LOG_MARK,
6446 "%s ERROR: Core class %s already populated as %s",
6447 modulename, curr_query->core.class_name, curr_query->core.alias );
6451 build_class_info( &curr_query->core, alias, class_name );
6452 if( curr_query->core.alias )
6455 osrfLogError( OSRF_LOG_MARK,
6456 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6462 @brief Search the current QueryFrame for a specified alias.
6463 @param target The alias for which to search.
6464 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6466 static inline ClassInfo* search_alias( const char* target ) {
6467 return search_alias_in_frame( curr_query, target );
6471 @brief Search all levels of query for a specified alias, starting with the current query.
6472 @param target The alias for which to search.
6473 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6475 static ClassInfo* search_all_alias( const char* target ) {
6476 ClassInfo* found_class = NULL;
6477 QueryFrame* curr_frame = curr_query;
6479 while( curr_frame ) {
6480 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6483 curr_frame = curr_frame->next;
6490 @brief Add a class to the list of classes joined to the current query.
6491 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6492 the class name as an alias.
6493 @param classname The name of the class to be added.
6494 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6496 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6498 if( ! classname || ! *classname ) { // sanity check
6499 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6506 const ClassInfo* conflict = search_alias( alias );
6508 osrfLogError( OSRF_LOG_MARK,
6509 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6510 modulename, alias, conflict->class_name );
6514 ClassInfo* info = allocate_class_info();
6516 if( build_class_info( info, alias, classname ) ) {
6517 free_class_info( info );
6521 // Add the new ClassInfo to the join list of the current QueryFrame
6522 info->next = curr_query->join_list;
6523 curr_query->join_list = info;
6529 @brief Destroy all nodes on the query stack.
6531 static void clear_query_stack( void ) {