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;
1254 @brief For PCRUD: Determine whether the current user may access the current row.
1255 @param ctx Pointer to the method context.
1256 @param obj Pointer to the row being potentially accessed.
1257 @return 1 if access is permitted, or 0 if it isn't.
1259 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1261 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1263 dbhandle = writehandle;
1265 // Figure out what class and method are involved
1266 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1267 osrfHash* class = osrfHashGet( method_metadata, "class" );
1268 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1271 if( *method_type == 's' || *method_type == 'i' ) {
1272 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1273 } else if( *method_type == 'u' || *method_type == 'd' ) {
1274 fetch = 1; // MUST go to the db for the object for update and delete
1277 // Get the appropriate permacrud entry from the IDL, depending on method type
1278 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1280 // No permacrud for this method type on this class
1282 growing_buffer* msg = buffer_init( 128 );
1285 "%s: %s on class %s has no permacrud IDL entry",
1287 osrfHashGet( method_metadata, "methodtype" ),
1288 osrfHashGet( class, "classname" )
1291 char* m = buffer_release( msg );
1292 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1293 "osrfMethodException", ctx->request, m );
1300 // Get the user id, and make sure the user is logged in
1301 const jsonObject* user = verifyUserPCRUD( ctx );
1303 return 0; // Not logged in? No access.
1305 int userid = atoi( oilsFMGetString( user, "id" ) );
1307 // Get a list of permissions from the permacrud entry.
1308 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1310 // Build a list of org units that own the row. This is fairly convoluted because there
1311 // are several different ways that an org unit may own the row, as defined by the
1314 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1315 // identifying an owning org_unit..
1316 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1318 // Foreign context adds a layer of indirection. The row points to some other row that
1319 // an org unit may own. The "jump" attribute, if present, adds another layer of
1321 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1323 // The following string array stores the list of org units. (We don't have a thingie
1324 // for storing lists of integers, so we fake it with a list of strings.)
1325 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1328 char* pkey_value = NULL;
1329 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1330 // If the global_required attribute is present and true, then the only owning
1331 // org unit is the root org unit, i.e. the one with no parent.
1332 osrfLogDebug( OSRF_LOG_MARK,
1333 "global-level permissions required, fetching top of the org tree" );
1335 // check for perm at top of org tree
1336 char* org_tree_root_id = org_tree_root( ctx );
1337 if( org_tree_root_id ) {
1338 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1339 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1341 osrfStringArrayFree( context_org_array );
1346 // If the global_required attribute is absent or false, then we look for
1347 // local and/or foreign context. In order to find the relevant foreign
1348 // keys, we must either read the relevant row from the database, or look at
1349 // the image of the row that we already have in memory.
1351 // (Herein lies a bug. Even if we have an image of the row in memory, that
1352 // image may not include the foreign key column(s) that we need.)
1354 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1355 "fetching context org ids" );
1356 const char* pkey = osrfHashGet( class, "primarykey" );
1357 jsonObject *param = NULL;
1359 if( obj->classname ) {
1360 pkey_value = oilsFMGetString( obj, pkey );
1362 param = jsonObjectClone( obj );
1363 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1366 pkey_value = jsonObjectToSimpleString( obj );
1368 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1369 "of %s and retrieving from the database", pkey_value );
1373 // Fetch the row so that we can look at the foreign key(s)
1374 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1375 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1376 jsonObjectFree( _tmp_params );
1378 param = jsonObjectExtractIndex( _list, 0 );
1379 jsonObjectFree( _list );
1383 // The row doesn't exist. Complain, and deny access.
1384 osrfLogDebug( OSRF_LOG_MARK,
1385 "Object not found in the database with primary key %s of %s",
1388 growing_buffer* msg = buffer_init( 128 );
1391 "%s: no object found with primary key %s of %s",
1397 char* m = buffer_release( msg );
1398 osrfAppSessionStatus(
1400 OSRF_STATUS_INTERNALSERVERERROR,
1401 "osrfMethodException",
1413 if( local_context && local_context->size > 0 ) {
1414 // The IDL provides a list of column names for the foreign keys denoting
1415 // local context. Look up the value of each one, and add it to the
1416 // list of org units.
1417 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1418 local_context->size );
1420 const char* lcontext = NULL;
1421 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1422 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1425 "adding class-local field %s (value: %s) to the context org list",
1427 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1432 if( foreign_context ) {
1433 unsigned long class_count = osrfHashGetCount( foreign_context );
1434 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1436 if( class_count > 0 ) {
1438 // The IDL provides a list of foreign key columns pointing to rows that
1439 // an org unit may own. Follow each link, identify the owning org unit,
1440 // and add it to the list.
1441 osrfHash* fcontext = NULL;
1442 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1443 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1444 // For each linked class...
1445 const char* class_name = osrfHashIteratorKey( class_itr );
1446 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1450 "%d foreign context fields(s) specified for class %s",
1451 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1455 // Get the name of the key field in the foreign table
1456 char* foreign_pkey = osrfHashGet( fcontext, "field" );
1458 // Get the value of the foreign key pointing to the foreign table
1459 char* foreign_pkey_value =
1460 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1462 // Look up the row to which the foreign key points
1463 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1464 jsonObject* _list = doFieldmapperSearch(
1465 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1467 jsonObject* _fparam = NULL;
1468 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1469 _fparam = jsonObjectExtractIndex( _list, 0 );
1471 jsonObjectFree( _tmp_params );
1472 jsonObjectFree( _list );
1474 // At this point _fparam either points to the row identified by the
1475 // foreign key, or it's NULL (no such row found).
1477 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1479 if( _fparam && jump_list ) {
1480 // Follow another level of indirection to find one or more owners
1481 const char* flink = NULL;
1483 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1484 // For each entry in the jump list. Each entry is the name of a
1485 // foreign key colum in the foreign table.
1487 osrfHash* foreign_link_hash =
1488 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1490 free( foreign_pkey_value );
1491 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1492 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1494 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1496 _list = doFieldmapperSearch(
1498 osrfHashGet( oilsIDL(),
1499 osrfHashGet( foreign_link_hash, "class" ) ),
1505 jsonObjectFree( _fparam );
1507 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1508 _fparam = jsonObjectExtractIndex( _list, 0 );
1509 jsonObjectFree( _tmp_params );
1510 jsonObjectFree( _list );
1516 growing_buffer* msg = buffer_init( 128 );
1519 "%s: no object found with primary key %s of %s",
1525 char* m = buffer_release( msg );
1526 osrfAppSessionStatus(
1528 OSRF_STATUS_INTERNALSERVERERROR,
1529 "osrfMethodException",
1535 osrfHashIteratorFree( class_itr );
1536 free( foreign_pkey_value );
1537 jsonObjectFree( param );
1542 free( foreign_pkey_value );
1544 // Examine each context column of the foreign row,
1545 // and add its value to the list of org units.
1547 const char* foreign_field = NULL;
1548 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1549 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1550 osrfStringArrayAdd( context_org_array,
1551 oilsFMGetString( _fparam, foreign_field ) );
1554 "adding foreign class %s field %s (value: %s) to the context org list",
1557 osrfStringArrayGetString(
1558 context_org_array, context_org_array->size - 1 )
1562 jsonObjectFree( _fparam );
1565 osrfHashIteratorFree( class_itr );
1569 jsonObjectFree( param );
1572 const char* context_org = NULL;
1573 const char* perm = NULL;
1576 if( permission->size == 0 ) {
1577 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1582 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1584 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1590 "Checking object permission [%s] for user %d "
1591 "on object %s (class %s) at org %d",
1595 osrfHashGet( class, "classname" ),
1599 result = dbi_conn_queryf(
1601 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1604 osrfHashGet( class, "classname" ),
1612 "Received a result for object permission [%s] "
1613 "for user %d on object %s (class %s) at org %d",
1617 osrfHashGet( class, "classname" ),
1621 if( dbi_result_first_row( result )) {
1622 jsonObject* return_val = oilsMakeJSONFromResult( result );
1623 const char* has_perm = jsonObjectGetString(
1624 jsonObjectGetKeyConst( return_val, "has_perm" ));
1628 "Status of object permission [%s] for user %d "
1629 "on object %s (class %s) at org %d is %s",
1633 osrfHashGet(class, "classname"),
1638 if( *has_perm == 't' )
1640 jsonObjectFree( return_val );
1643 dbi_result_free( result );
1649 osrfLogDebug( OSRF_LOG_MARK,
1650 "Checking non-object permission [%s] for user %d at org %d",
1651 perm, userid, atoi(context_org) );
1652 result = dbi_conn_queryf(
1654 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1661 osrfLogDebug( OSRF_LOG_MARK,
1662 "Received a result for permission [%s] for user %d at org %d",
1663 perm, userid, atoi( context_org ));
1664 if( dbi_result_first_row( result )) {
1665 jsonObject* return_val = oilsMakeJSONFromResult( result );
1666 const char* has_perm = jsonObjectGetString(
1667 jsonObjectGetKeyConst( return_val, "has_perm" ));
1668 osrfLogDebug( OSRF_LOG_MARK,
1669 "Status of permission [%s] for user %d at org %d is [%s]",
1670 perm, userid, atoi( context_org ), has_perm );
1671 if( *has_perm == 't' )
1673 jsonObjectFree( return_val );
1676 dbi_result_free( result );
1688 osrfStringArrayFree( context_org_array );
1694 @brief Look up the root of the org_unit tree.
1695 @param ctx Pointer to the method context.
1696 @return The id of the root org unit, as a character string.
1698 Query actor.org_unit where parent_ou is null, and return the id as a string.
1700 This function assumes that there is only one root org unit, i.e. that we
1701 have a single tree, not a forest.
1703 The calling code is responsible for freeing the returned string.
1705 static char* org_tree_root( osrfMethodContext* ctx ) {
1707 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1708 static time_t last_lookup_time = 0;
1709 time_t current_time = time( NULL );
1711 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1712 // We successfully looked this up less than an hour ago.
1713 // It's not likely to have changed since then.
1714 return strdup( cached_root_id );
1716 last_lookup_time = current_time;
1719 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1720 jsonObject* result = doFieldmapperSearch(
1721 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1722 jsonObjectFree( where_clause );
1724 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1727 jsonObjectFree( result );
1729 growing_buffer* msg = buffer_init( 128 );
1730 OSRF_BUFFER_ADD( msg, modulename );
1731 OSRF_BUFFER_ADD( msg,
1732 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1734 char* m = buffer_release( msg );
1735 osrfAppSessionStatus( ctx->session,
1736 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1739 cached_root_id[ 0 ] = '\0';
1743 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1744 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1746 jsonObjectFree( result );
1748 strcpy( cached_root_id, root_org_unit_id );
1749 return root_org_unit_id;
1753 @brief Create a JSON_HASH with a single key/value pair.
1754 @param key The key of the key/value pair.
1755 @param value the value of the key/value pair.
1756 @return Pointer to a newly created jsonObject of type JSON_HASH.
1758 The value of the key/value is either a string or (if @a value is NULL) a null.
1760 static jsonObject* single_hash( const char* key, const char* value ) {
1762 if( ! key ) key = "";
1764 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1765 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1770 int doCreate( osrfMethodContext* ctx ) {
1771 if(osrfMethodVerifyContext( ctx )) {
1772 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1777 timeout_needs_resetting = 1;
1779 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1780 jsonObject* target = NULL;
1781 jsonObject* options = NULL;
1783 if( enforce_pcrud ) {
1784 target = jsonObjectGetIndex( ctx->params, 1 );
1785 options = jsonObjectGetIndex( ctx->params, 2 );
1787 target = jsonObjectGetIndex( ctx->params, 0 );
1788 options = jsonObjectGetIndex( ctx->params, 1 );
1791 if( !verifyObjectClass( ctx, target )) {
1792 osrfAppRespondComplete( ctx, NULL );
1796 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1798 const char* trans_id = getXactId( ctx );
1800 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1802 osrfAppSessionStatus(
1804 OSRF_STATUS_BADREQUEST,
1805 "osrfMethodException",
1807 "No active transaction -- required for CREATE"
1809 osrfAppRespondComplete( ctx, NULL );
1813 // The following test is harmless but redundant. If a class is
1814 // readonly, we don't register a create method for it.
1815 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1816 osrfAppSessionStatus(
1818 OSRF_STATUS_BADREQUEST,
1819 "osrfMethodException",
1821 "Cannot INSERT readonly class"
1823 osrfAppRespondComplete( ctx, NULL );
1827 // Set the last_xact_id
1828 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1830 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1831 trans_id, target->classname, index);
1832 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1835 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1837 dbhandle = writehandle;
1839 osrfHash* fields = osrfHashGet( meta, "fields" );
1840 char* pkey = osrfHashGet( meta, "primarykey" );
1841 char* seq = osrfHashGet( meta, "sequence" );
1843 growing_buffer* table_buf = buffer_init( 128 );
1844 growing_buffer* col_buf = buffer_init( 128 );
1845 growing_buffer* val_buf = buffer_init( 128 );
1847 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1848 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1849 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1850 buffer_add( val_buf,"VALUES (" );
1854 osrfHash* field = NULL;
1855 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1856 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1858 const char* field_name = osrfHashIteratorKey( field_itr );
1860 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1863 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1866 if( field_object && field_object->classname ) {
1867 value = oilsFMGetString(
1869 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
1871 } else if( field_object && JSON_BOOL == field_object->type ) {
1872 if( jsonBoolIsTrue( field_object ) )
1873 value = strdup( "t" );
1875 value = strdup( "f" );
1877 value = jsonObjectToSimpleString( field_object );
1883 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1884 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1887 buffer_add( col_buf, field_name );
1889 if( !field_object || field_object->type == JSON_NULL ) {
1890 buffer_add( val_buf, "DEFAULT" );
1892 } else if( !strcmp( get_primitive( field ), "number" )) {
1893 const char* numtype = get_datatype( field );
1894 if( !strcmp( numtype, "INT8" )) {
1895 buffer_fadd( val_buf, "%lld", atoll( value ));
1897 } else if( !strcmp( numtype, "INT" )) {
1898 buffer_fadd( val_buf, "%d", atoi( value ));
1900 } else if( !strcmp( numtype, "NUMERIC" )) {
1901 buffer_fadd( val_buf, "%f", atof( value ));
1904 if( dbi_conn_quote_string( writehandle, &value )) {
1905 OSRF_BUFFER_ADD( val_buf, value );
1908 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
1909 osrfAppSessionStatus(
1911 OSRF_STATUS_INTERNALSERVERERROR,
1912 "osrfMethodException",
1914 "Error quoting string -- please see the error log for more details"
1917 buffer_free( table_buf );
1918 buffer_free( col_buf );
1919 buffer_free( val_buf );
1920 osrfAppRespondComplete( ctx, NULL );
1928 osrfHashIteratorFree( field_itr );
1930 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1931 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1933 char* table_str = buffer_release( table_buf );
1934 char* col_str = buffer_release( col_buf );
1935 char* val_str = buffer_release( val_buf );
1936 growing_buffer* sql = buffer_init( 128 );
1937 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1942 char* query = buffer_release( sql );
1944 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
1946 jsonObject* obj = NULL;
1949 dbi_result result = dbi_conn_query( writehandle, query );
1951 obj = jsonNewObject( NULL );
1954 "%s ERROR inserting %s object using query [%s]",
1956 osrfHashGet(meta, "fieldmapper"),
1959 osrfAppSessionStatus(
1961 OSRF_STATUS_INTERNALSERVERERROR,
1962 "osrfMethodException",
1964 "INSERT error -- please see the error log for more details"
1969 char* id = oilsFMGetString( target, pkey );
1971 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
1972 growing_buffer* _id = buffer_init( 10 );
1973 buffer_fadd( _id, "%lld", new_id );
1974 id = buffer_release( _id );
1977 // Find quietness specification, if present
1978 const char* quiet_str = NULL;
1980 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1982 quiet_str = jsonObjectGetString( quiet_obj );
1985 if( str_is_true( quiet_str )) { // if quietness is specified
1986 obj = jsonNewObject( id );
1990 // Fetch the row that we just inserted, so that we can return it to the client
1991 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1992 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
1995 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
1999 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2001 jsonObjectFree( list );
2002 jsonObjectFree( where_clause );
2009 osrfAppRespondComplete( ctx, obj );
2010 jsonObjectFree( obj );
2015 @brief Implement the retrieve method.
2016 @param ctx Pointer to the method context.
2017 @param err Pointer through which to return an error code.
2018 @return If successful, a pointer to the result to be returned to the client;
2021 From the method's class, fetch a row with a specified value in the primary key. This
2022 method relies on the database design convention that a primary key consists of a single
2026 - authkey (PCRUD only)
2027 - value of the primary key for the desired row, for building the WHERE clause
2028 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2030 Return to client: One row from the query.
2032 int doRetrieve( osrfMethodContext* ctx ) {
2033 if(osrfMethodVerifyContext( ctx )) {
2034 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2039 timeout_needs_resetting = 1;
2044 if( enforce_pcrud ) {
2049 // Get the class metadata
2050 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2052 // Get the value of the primary key, from a method parameter
2053 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2057 "%s retrieving %s object with primary key value of %s",
2059 osrfHashGet( class_def, "fieldmapper" ),
2060 jsonObjectGetString( id_obj )
2063 // Build a WHERE clause based on the key value
2064 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2067 osrfHashGet( class_def, "primarykey" ), // name of key column
2068 jsonObjectClone( id_obj ) // value of key column
2071 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2075 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2077 jsonObjectFree( where_clause );
2079 osrfAppRespondComplete( ctx, NULL );
2083 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2084 jsonObjectFree( list );
2086 if( enforce_pcrud ) {
2087 if(!verifyObjectPCRUD( ctx, obj )) {
2088 jsonObjectFree( obj );
2090 growing_buffer* msg = buffer_init( 128 );
2091 OSRF_BUFFER_ADD( msg, modulename );
2092 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2094 char* m = buffer_release( msg );
2095 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2099 osrfAppRespondComplete( ctx, NULL );
2104 osrfAppRespondComplete( ctx, obj );
2105 jsonObjectFree( obj );
2110 @brief Translate a numeric value to a string representation for the database.
2111 @param field Pointer to the IDL field definition.
2112 @param value Pointer to a jsonObject holding the value of a field.
2113 @return Pointer to a newly allocated string.
2115 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2116 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2117 or (what is worse) valid SQL that is wrong.
2119 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2121 The calling code is responsible for freeing the resulting string by calling free().
2123 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2124 growing_buffer* val_buf = buffer_init( 32 );
2125 const char* numtype = get_datatype( field );
2127 // For historical reasons the following contains cruft that could be cleaned up.
2128 if( !strncmp( numtype, "INT", 3 ) ) {
2129 if( value->type == JSON_NUMBER )
2130 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2131 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2133 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2136 } else if( !strcmp( numtype, "NUMERIC" )) {
2137 if( value->type == JSON_NUMBER )
2138 buffer_fadd( val_buf, jsonObjectGetString( value ));
2140 buffer_fadd( val_buf, jsonObjectGetString( value ));
2144 // Presumably this was really intended to be a string, so quote it
2145 char* str = jsonObjectToSimpleString( value );
2146 if( dbi_conn_quote_string( dbhandle, &str )) {
2147 OSRF_BUFFER_ADD( val_buf, str );
2150 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2152 buffer_free( val_buf );
2157 return buffer_release( val_buf );
2160 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2161 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2162 growing_buffer* sql_buf = buffer_init( 32 );
2168 osrfHashGet( field, "name" )
2172 buffer_add( sql_buf, "IN (" );
2173 } else if( !strcasecmp( op,"not in" )) {
2174 buffer_add( sql_buf, "NOT IN (" );
2176 buffer_add( sql_buf, "IN (" );
2179 if( node->type == JSON_HASH ) {
2180 // subquery predicate
2181 char* subpred = buildQuery( ctx, node, SUBSELECT );
2183 buffer_free( sql_buf );
2187 buffer_add( sql_buf, subpred );
2190 } else if( node->type == JSON_ARRAY ) {
2191 // literal value list
2192 int in_item_index = 0;
2193 int in_item_first = 1;
2194 const jsonObject* in_item;
2195 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2200 buffer_add( sql_buf, ", " );
2203 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2204 osrfLogError( OSRF_LOG_MARK,
2205 "%s: Expected string or number within IN list; found %s",
2206 modulename, json_type( in_item->type ) );
2207 buffer_free( sql_buf );
2211 // Append the literal value -- quoted if not a number
2212 if( JSON_NUMBER == in_item->type ) {
2213 char* val = jsonNumberToDBString( field, in_item );
2214 OSRF_BUFFER_ADD( sql_buf, val );
2217 } else if( !strcmp( get_primitive( field ), "number" )) {
2218 char* val = jsonNumberToDBString( field, in_item );
2219 OSRF_BUFFER_ADD( sql_buf, val );
2223 char* key_string = jsonObjectToSimpleString( in_item );
2224 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2225 OSRF_BUFFER_ADD( sql_buf, key_string );
2228 osrfLogError( OSRF_LOG_MARK,
2229 "%s: Error quoting key string [%s]", modulename, key_string );
2231 buffer_free( sql_buf );
2237 if( in_item_first ) {
2238 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2239 buffer_free( sql_buf );
2243 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2244 modulename, json_type( node->type ));
2245 buffer_free( sql_buf );
2249 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2251 return buffer_release( sql_buf );
2254 // Receive a JSON_ARRAY representing a function call. The first
2255 // entry in the array is the function name. The rest are parameters.
2256 static char* searchValueTransform( const jsonObject* array ) {
2258 if( array->size < 1 ) {
2259 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2263 // Get the function name
2264 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2265 if( func_item->type != JSON_STRING ) {
2266 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2267 modulename, json_type( func_item->type ));
2271 growing_buffer* sql_buf = buffer_init( 32 );
2273 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2274 OSRF_BUFFER_ADD( sql_buf, "( " );
2276 // Get the parameters
2277 int func_item_index = 1; // We already grabbed the zeroth entry
2278 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2280 // Add a separator comma, if we need one
2281 if( func_item_index > 2 )
2282 buffer_add( sql_buf, ", " );
2284 // Add the current parameter
2285 if( func_item->type == JSON_NULL ) {
2286 buffer_add( sql_buf, "NULL" );
2288 char* val = jsonObjectToSimpleString( func_item );
2289 if( dbi_conn_quote_string( dbhandle, &val )) {
2290 OSRF_BUFFER_ADD( sql_buf, val );
2293 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2295 buffer_free( sql_buf );
2302 buffer_add( sql_buf, " )" );
2304 return buffer_release( sql_buf );
2307 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2308 const jsonObject* node, const char* op ) {
2310 if( ! is_good_operator( op ) ) {
2311 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2315 char* val = searchValueTransform( node );
2319 growing_buffer* sql_buf = buffer_init( 32 );
2324 osrfHashGet( field, "name" ),
2331 return buffer_release( sql_buf );
2334 // class_alias is a class name or other table alias
2335 // field is a field definition as stored in the IDL
2336 // node comes from the method parameter, and may represent an entry in the SELECT list
2337 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2338 const jsonObject* node ) {
2339 growing_buffer* sql_buf = buffer_init( 32 );
2341 const char* field_transform = jsonObjectGetString(
2342 jsonObjectGetKeyConst( node, "transform" ) );
2343 const char* transform_subcolumn = jsonObjectGetString(
2344 jsonObjectGetKeyConst( node, "result_field" ) );
2346 if( transform_subcolumn ) {
2347 if( ! is_identifier( transform_subcolumn ) ) {
2348 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2349 modulename, transform_subcolumn );
2350 buffer_free( sql_buf );
2353 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2356 if( field_transform ) {
2358 if( ! is_identifier( field_transform ) ) {
2359 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2360 modulename, field_transform );
2361 buffer_free( sql_buf );
2365 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2366 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2367 field_transform, class_alias, osrfHashGet( field, "name" ));
2369 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2370 field_transform, class_alias, osrfHashGet( field, "name" ));
2373 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2376 if( array->type != JSON_ARRAY ) {
2377 osrfLogError( OSRF_LOG_MARK,
2378 "%s: Expected JSON_ARRAY for function params; found %s",
2379 modulename, json_type( array->type ) );
2380 buffer_free( sql_buf );
2383 int func_item_index = 0;
2384 jsonObject* func_item;
2385 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2387 char* val = jsonObjectToSimpleString( func_item );
2390 buffer_add( sql_buf, ",NULL" );
2391 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2392 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2393 OSRF_BUFFER_ADD( sql_buf, val );
2395 osrfLogError( OSRF_LOG_MARK,
2396 "%s: Error quoting key string [%s]", modulename, val );
2398 buffer_free( sql_buf );
2405 buffer_add( sql_buf, " )" );
2408 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2411 if( transform_subcolumn )
2412 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2414 return buffer_release( sql_buf );
2417 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2418 const jsonObject* node, const char* op ) {
2420 if( ! is_good_operator( op ) ) {
2421 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2425 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2426 if( ! field_transform )
2429 int extra_parens = 0; // boolean
2431 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2433 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2435 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2437 free( field_transform );
2441 } else if( value_obj->type == JSON_ARRAY ) {
2442 value = searchValueTransform( value_obj );
2444 osrfLogError( OSRF_LOG_MARK,
2445 "%s: Error building value transform for field transform", modulename );
2446 free( field_transform );
2449 } else if( value_obj->type == JSON_HASH ) {
2450 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2452 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2454 free( field_transform );
2458 } else if( value_obj->type == JSON_NUMBER ) {
2459 value = jsonNumberToDBString( field, value_obj );
2460 } else if( value_obj->type == JSON_NULL ) {
2461 osrfLogError( OSRF_LOG_MARK,
2462 "%s: Error building predicate for field transform: null value", modulename );
2463 free( field_transform );
2465 } else if( value_obj->type == JSON_BOOL ) {
2466 osrfLogError( OSRF_LOG_MARK,
2467 "%s: Error building predicate for field transform: boolean value", modulename );
2468 free( field_transform );
2471 if( !strcmp( get_primitive( field ), "number") ) {
2472 value = jsonNumberToDBString( field, value_obj );
2474 value = jsonObjectToSimpleString( value_obj );
2475 if( !dbi_conn_quote_string( dbhandle, &value )) {
2476 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2477 modulename, value );
2479 free( field_transform );
2485 const char* left_parens = "";
2486 const char* right_parens = "";
2488 if( extra_parens ) {
2493 growing_buffer* sql_buf = buffer_init( 32 );
2497 "%s%s %s %s %s %s%s",
2508 free( field_transform );
2510 return buffer_release( sql_buf );
2513 static char* searchSimplePredicate( const char* op, const char* class_alias,
2514 osrfHash* field, const jsonObject* node ) {
2516 if( ! is_good_operator( op ) ) {
2517 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2523 // Get the value to which we are comparing the specified column
2524 if( node->type != JSON_NULL ) {
2525 if( node->type == JSON_NUMBER ) {
2526 val = jsonNumberToDBString( field, node );
2527 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2528 val = jsonNumberToDBString( field, node );
2530 val = jsonObjectToSimpleString( node );
2535 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2536 // Value is not numeric; enclose it in quotes
2537 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2538 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2545 // Compare to a null value
2546 val = strdup( "NULL" );
2547 if( strcmp( op, "=" ))
2553 growing_buffer* sql_buf = buffer_init( 32 );
2554 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2555 char* pred = buffer_release( sql_buf );
2562 static char* searchBETWEENPredicate( const char* class_alias,
2563 osrfHash* field, const jsonObject* node ) {
2565 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2566 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2568 if( NULL == y_node ) {
2569 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2572 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2573 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2580 if( !strcmp( get_primitive( field ), "number") ) {
2581 x_string = jsonNumberToDBString( field, x_node );
2582 y_string = jsonNumberToDBString( field, y_node );
2585 x_string = jsonObjectToSimpleString( x_node );
2586 y_string = jsonObjectToSimpleString( y_node );
2587 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2588 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2589 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2590 modulename, x_string, y_string );
2597 growing_buffer* sql_buf = buffer_init( 32 );
2598 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2599 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2603 return buffer_release( sql_buf );
2606 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2607 jsonObject* node, osrfMethodContext* ctx ) {
2610 if( node->type == JSON_ARRAY ) { // equality IN search
2611 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2612 } else if( node->type == JSON_HASH ) { // other search
2613 jsonIterator* pred_itr = jsonNewIterator( node );
2614 if( !jsonIteratorHasNext( pred_itr ) ) {
2615 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2616 modulename, osrfHashGet(field, "name" ));
2618 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2620 // Verify that there are no additional predicates
2621 if( jsonIteratorHasNext( pred_itr ) ) {
2622 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2623 modulename, osrfHashGet(field, "name" ));
2624 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2625 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2626 else if( !(strcasecmp( pred_itr->key,"in" ))
2627 || !(strcasecmp( pred_itr->key,"not in" )) )
2628 pred = searchINPredicate(
2629 class_info->alias, field, pred_node, pred_itr->key, ctx );
2630 else if( pred_node->type == JSON_ARRAY )
2631 pred = searchFunctionPredicate(
2632 class_info->alias, field, pred_node, pred_itr->key );
2633 else if( pred_node->type == JSON_HASH )
2634 pred = searchFieldTransformPredicate(
2635 class_info, field, pred_node, pred_itr->key );
2637 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2639 jsonIteratorFree( pred_itr );
2641 } else if( node->type == JSON_NULL ) { // IS NULL search
2642 growing_buffer* _p = buffer_init( 64 );
2645 "\"%s\".%s IS NULL",
2646 class_info->class_name,
2647 osrfHashGet( field, "name" )
2649 pred = buffer_release( _p );
2650 } else { // equality search
2651 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2670 field : call_number,
2686 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2688 const jsonObject* working_hash;
2689 jsonObject* freeable_hash = NULL;
2691 if( join_hash->type == JSON_HASH ) {
2692 working_hash = join_hash;
2693 } else if( join_hash->type == JSON_STRING ) {
2694 // turn it into a JSON_HASH by creating a wrapper
2695 // around a copy of the original
2696 const char* _tmp = jsonObjectGetString( join_hash );
2697 freeable_hash = jsonNewObjectType( JSON_HASH );
2698 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2699 working_hash = freeable_hash;
2703 "%s: JOIN failed; expected JSON object type not found",
2709 growing_buffer* join_buf = buffer_init( 128 );
2710 const char* leftclass = left_info->class_name;
2712 jsonObject* snode = NULL;
2713 jsonIterator* search_itr = jsonNewIterator( working_hash );
2715 while ( (snode = jsonIteratorNext( search_itr )) ) {
2716 const char* right_alias = search_itr->key;
2718 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2720 class = right_alias;
2722 const ClassInfo* right_info = add_joined_class( right_alias, class );
2726 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2730 jsonIteratorFree( search_itr );
2731 buffer_free( join_buf );
2733 jsonObjectFree( freeable_hash );
2736 osrfHash* links = right_info->links;
2737 const char* table = right_info->source_def;
2739 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2740 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2742 if( field && !fkey ) {
2743 // Look up the corresponding join column in the IDL.
2744 // The link must be defined in the child table,
2745 // and point to the right parent table.
2746 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2747 const char* reltype = NULL;
2748 const char* other_class = NULL;
2749 reltype = osrfHashGet( idl_link, "reltype" );
2750 if( reltype && strcmp( reltype, "has_many" ) )
2751 other_class = osrfHashGet( idl_link, "class" );
2752 if( other_class && !strcmp( other_class, leftclass ) )
2753 fkey = osrfHashGet( idl_link, "key" );
2757 "%s: JOIN failed. No link defined from %s.%s to %s",
2763 buffer_free( join_buf );
2765 jsonObjectFree( freeable_hash );
2766 jsonIteratorFree( search_itr );
2770 } else if( !field && fkey ) {
2771 // Look up the corresponding join column in the IDL.
2772 // The link must be defined in the child table,
2773 // and point to the right parent table.
2774 osrfHash* left_links = left_info->links;
2775 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2776 const char* reltype = NULL;
2777 const char* other_class = NULL;
2778 reltype = osrfHashGet( idl_link, "reltype" );
2779 if( reltype && strcmp( reltype, "has_many" ) )
2780 other_class = osrfHashGet( idl_link, "class" );
2781 if( other_class && !strcmp( other_class, class ) )
2782 field = osrfHashGet( idl_link, "key" );
2786 "%s: JOIN failed. No link defined from %s.%s to %s",
2792 buffer_free( join_buf );
2794 jsonObjectFree( freeable_hash );
2795 jsonIteratorFree( search_itr );
2799 } else if( !field && !fkey ) {
2800 osrfHash* left_links = left_info->links;
2802 // For each link defined for the left class:
2803 // see if the link references the joined class
2804 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2805 osrfHash* curr_link = NULL;
2806 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2807 const char* other_class = osrfHashGet( curr_link, "class" );
2808 if( other_class && !strcmp( other_class, class ) ) {
2810 // In the IDL, the parent class doesn't always know then names of the child
2811 // columns that are pointing to it, so don't use that end of the link
2812 const char* reltype = osrfHashGet( curr_link, "reltype" );
2813 if( reltype && strcmp( reltype, "has_many" ) ) {
2814 // Found a link between the classes
2815 fkey = osrfHashIteratorKey( itr );
2816 field = osrfHashGet( curr_link, "key" );
2821 osrfHashIteratorFree( itr );
2823 if( !field || !fkey ) {
2824 // Do another such search, with the classes reversed
2826 // For each link defined for the joined class:
2827 // see if the link references the left class
2828 osrfHashIterator* itr = osrfNewHashIterator( links );
2829 osrfHash* curr_link = NULL;
2830 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2831 const char* other_class = osrfHashGet( curr_link, "class" );
2832 if( other_class && !strcmp( other_class, leftclass ) ) {
2834 // In the IDL, the parent class doesn't know then names of the child
2835 // columns that are pointing to it, so don't use that end of the link
2836 const char* reltype = osrfHashGet( curr_link, "reltype" );
2837 if( reltype && strcmp( reltype, "has_many" ) ) {
2838 // Found a link between the classes
2839 field = osrfHashIteratorKey( itr );
2840 fkey = osrfHashGet( curr_link, "key" );
2845 osrfHashIteratorFree( itr );
2848 if( !field || !fkey ) {
2851 "%s: JOIN failed. No link defined between %s and %s",
2856 buffer_free( join_buf );
2858 jsonObjectFree( freeable_hash );
2859 jsonIteratorFree( search_itr );
2864 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2866 if( !strcasecmp( type,"left" )) {
2867 buffer_add( join_buf, " LEFT JOIN" );
2868 } else if( !strcasecmp( type,"right" )) {
2869 buffer_add( join_buf, " RIGHT JOIN" );
2870 } else if( !strcasecmp( type,"full" )) {
2871 buffer_add( join_buf, " FULL JOIN" );
2873 buffer_add( join_buf, " INNER JOIN" );
2876 buffer_add( join_buf, " INNER JOIN" );
2879 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2880 table, right_alias, right_alias, field, left_info->alias, fkey );
2882 // Add any other join conditions as specified by "filter"
2883 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2885 const char* filter_op = jsonObjectGetString(
2886 jsonObjectGetKeyConst( snode, "filter_op" ) );
2887 if( filter_op && !strcasecmp( "or",filter_op )) {
2888 buffer_add( join_buf, " OR " );
2890 buffer_add( join_buf, " AND " );
2893 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2895 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2896 OSRF_BUFFER_ADD( join_buf, jpred );
2901 "%s: JOIN failed. Invalid conditional expression.",
2904 jsonIteratorFree( search_itr );
2905 buffer_free( join_buf );
2907 jsonObjectFree( freeable_hash );
2912 buffer_add( join_buf, " ) " );
2914 // Recursively add a nested join, if one is present
2915 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2917 char* jpred = searchJOIN( join_filter, right_info );
2919 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2920 OSRF_BUFFER_ADD( join_buf, jpred );
2923 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
2924 jsonIteratorFree( search_itr );
2925 buffer_free( join_buf );
2927 jsonObjectFree( freeable_hash );
2934 jsonObjectFree( freeable_hash );
2935 jsonIteratorFree( search_itr );
2937 return buffer_release( join_buf );
2942 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2943 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2944 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2946 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2948 search_hash is the JSON expression of the conditions.
2949 meta is the class definition from the IDL, for the relevant table.
2950 opjoin_type indicates whether multiple conditions, if present, should be
2951 connected by AND or OR.
2952 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2953 to pass it to other functions -- and all they do with it is to use the session
2954 and request members to send error messages back to the client.
2958 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
2959 int opjoin_type, osrfMethodContext* ctx ) {
2963 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
2964 "opjoin_type = %d, ctx addr = %p",
2967 class_info->class_def,
2972 growing_buffer* sql_buf = buffer_init( 128 );
2974 jsonObject* node = NULL;
2977 if( search_hash->type == JSON_ARRAY ) {
2978 osrfLogDebug( OSRF_LOG_MARK,
2979 "%s: In WHERE clause, condition type is JSON_ARRAY", modulename );
2980 if( 0 == search_hash->size ) {
2983 "%s: Invalid predicate structure: empty JSON array",
2986 buffer_free( sql_buf );
2990 unsigned long i = 0;
2991 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
2995 if( opjoin_type == OR_OP_JOIN )
2996 buffer_add( sql_buf, " OR " );
2998 buffer_add( sql_buf, " AND " );
3001 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3003 buffer_free( sql_buf );
3007 buffer_fadd( sql_buf, "( %s )", subpred );
3011 } else if( search_hash->type == JSON_HASH ) {
3012 osrfLogDebug( OSRF_LOG_MARK,
3013 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3014 jsonIterator* search_itr = jsonNewIterator( search_hash );
3015 if( !jsonIteratorHasNext( search_itr ) ) {
3018 "%s: Invalid predicate structure: empty JSON object",
3021 jsonIteratorFree( search_itr );
3022 buffer_free( sql_buf );
3026 while( (node = jsonIteratorNext( search_itr )) ) {
3031 if( opjoin_type == OR_OP_JOIN )
3032 buffer_add( sql_buf, " OR " );
3034 buffer_add( sql_buf, " AND " );
3037 if( '+' == search_itr->key[ 0 ] ) {
3039 // This plus sign prefixes a class name or other table alias;
3040 // make sure the table alias is in scope
3041 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3042 if( ! alias_info ) {
3045 "%s: Invalid table alias \"%s\" in WHERE clause",
3049 jsonIteratorFree( search_itr );
3050 buffer_free( sql_buf );
3054 if( node->type == JSON_STRING ) {
3055 // It's the name of a column; make sure it belongs to the class
3056 const char* fieldname = jsonObjectGetString( node );
3057 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3060 "%s: Invalid column name \"%s\" in WHERE clause "
3061 "for table alias \"%s\"",
3066 jsonIteratorFree( search_itr );
3067 buffer_free( sql_buf );
3071 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3073 // It's something more complicated
3074 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3076 jsonIteratorFree( search_itr );
3077 buffer_free( sql_buf );
3081 buffer_fadd( sql_buf, "( %s )", subpred );
3084 } else if( '-' == search_itr->key[ 0 ] ) {
3085 if( !strcasecmp( "-or", search_itr->key )) {
3086 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3088 jsonIteratorFree( search_itr );
3089 buffer_free( sql_buf );
3093 buffer_fadd( sql_buf, "( %s )", subpred );
3095 } else if( !strcasecmp( "-and", search_itr->key )) {
3096 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3098 jsonIteratorFree( search_itr );
3099 buffer_free( sql_buf );
3103 buffer_fadd( sql_buf, "( %s )", subpred );
3105 } else if( !strcasecmp("-not",search_itr->key) ) {
3106 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3108 jsonIteratorFree( search_itr );
3109 buffer_free( sql_buf );
3113 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3115 } else if( !strcasecmp( "-exists", search_itr->key )) {
3116 char* subpred = buildQuery( ctx, node, SUBSELECT );
3118 jsonIteratorFree( search_itr );
3119 buffer_free( sql_buf );
3123 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3125 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3126 char* subpred = buildQuery( ctx, node, SUBSELECT );
3128 jsonIteratorFree( search_itr );
3129 buffer_free( sql_buf );
3133 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3135 } else { // Invalid "minus" operator
3138 "%s: Invalid operator \"%s\" in WHERE clause",
3142 jsonIteratorFree( search_itr );
3143 buffer_free( sql_buf );
3149 const char* class = class_info->class_name;
3150 osrfHash* fields = class_info->fields;
3151 osrfHash* field = osrfHashGet( fields, search_itr->key );
3154 const char* table = class_info->source_def;
3157 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3160 table ? table : "?",
3163 jsonIteratorFree( search_itr );
3164 buffer_free( sql_buf );
3168 char* subpred = searchPredicate( class_info, field, node, ctx );
3170 buffer_free( sql_buf );
3171 jsonIteratorFree( search_itr );
3175 buffer_add( sql_buf, subpred );
3179 jsonIteratorFree( search_itr );
3182 // ERROR ... only hash and array allowed at this level
3183 char* predicate_string = jsonObjectToJSON( search_hash );
3186 "%s: Invalid predicate structure: %s",
3190 buffer_free( sql_buf );
3191 free( predicate_string );
3195 return buffer_release( sql_buf );
3198 /* Build a JSON_ARRAY of field names for a given table alias
3200 static jsonObject* defaultSelectList( const char* table_alias ) {
3205 ClassInfo* class_info = search_all_alias( table_alias );
3206 if( ! class_info ) {
3209 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3216 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3217 osrfHash* field_def = NULL;
3218 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3219 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3220 const char* field_name = osrfHashIteratorKey( field_itr );
3221 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3222 jsonObjectPush( array, jsonNewObject( field_name ) );
3225 osrfHashIteratorFree( field_itr );
3230 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3231 // The jsonObject must be a JSON_HASH with an single entry for "union",
3232 // "intersect", or "except". The data associated with this key must be an
3233 // array of hashes, each hash being a query.
3234 // Also allowed but currently ignored: entries for "order_by" and "alias".
3235 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3237 if( ! combo || combo->type != JSON_HASH )
3238 return NULL; // should be impossible; validated by caller
3240 const jsonObject* query_array = NULL; // array of subordinate queries
3241 const char* op = NULL; // name of operator, e.g. UNION
3242 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3243 int op_count = 0; // for detecting conflicting operators
3244 int excepting = 0; // boolean
3245 int all = 0; // boolean
3246 jsonObject* order_obj = NULL;
3248 // Identify the elements in the hash
3249 jsonIterator* query_itr = jsonNewIterator( combo );
3250 jsonObject* curr_obj = NULL;
3251 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3252 if( ! strcmp( "union", query_itr->key ) ) {
3255 query_array = curr_obj;
3256 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3259 query_array = curr_obj;
3260 } else if( ! strcmp( "except", query_itr->key ) ) {
3264 query_array = curr_obj;
3265 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3268 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3271 order_obj = curr_obj;
3272 } else if( ! strcmp( "alias", query_itr->key ) ) {
3273 if( curr_obj->type != JSON_STRING ) {
3274 jsonIteratorFree( query_itr );
3277 alias = jsonObjectGetString( curr_obj );
3278 } else if( ! strcmp( "all", query_itr->key ) ) {
3279 if( obj_is_true( curr_obj ) )
3283 osrfAppSessionStatus(
3285 OSRF_STATUS_INTERNALSERVERERROR,
3286 "osrfMethodException",
3288 "Malformed query; unexpected entry in query object"
3292 "%s: Unexpected entry for \"%s\" in%squery",
3297 jsonIteratorFree( query_itr );
3301 jsonIteratorFree( query_itr );
3303 // More sanity checks
3304 if( ! query_array ) {
3306 osrfAppSessionStatus(
3308 OSRF_STATUS_INTERNALSERVERERROR,
3309 "osrfMethodException",
3311 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3315 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3318 return NULL; // should be impossible...
3319 } else if( op_count > 1 ) {
3321 osrfAppSessionStatus(
3323 OSRF_STATUS_INTERNALSERVERERROR,
3324 "osrfMethodException",
3326 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3330 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3334 } if( query_array->type != JSON_ARRAY ) {
3336 osrfAppSessionStatus(
3338 OSRF_STATUS_INTERNALSERVERERROR,
3339 "osrfMethodException",
3341 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3345 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3348 json_type( query_array->type )
3351 } if( query_array->size < 2 ) {
3353 osrfAppSessionStatus(
3355 OSRF_STATUS_INTERNALSERVERERROR,
3356 "osrfMethodException",
3358 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3362 "%s:%srequires multiple queries as operands",
3367 } else if( excepting && query_array->size > 2 ) {
3369 osrfAppSessionStatus(
3371 OSRF_STATUS_INTERNALSERVERERROR,
3372 "osrfMethodException",
3374 "EXCEPT operator has too many queries as operands"
3378 "%s:EXCEPT operator has too many queries as operands",
3382 } else if( order_obj && ! alias ) {
3384 osrfAppSessionStatus(
3386 OSRF_STATUS_INTERNALSERVERERROR,
3387 "osrfMethodException",
3389 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3393 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3399 // So far so good. Now build the SQL.
3400 growing_buffer* sql = buffer_init( 256 );
3402 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3403 // Add a layer of parentheses
3404 if( flags & SUBCOMBO )
3405 OSRF_BUFFER_ADD( sql, "( " );
3407 // Traverse the query array. Each entry should be a hash.
3408 int first = 1; // boolean
3410 jsonObject* query = NULL;
3411 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3412 if( query->type != JSON_HASH ) {
3414 osrfAppSessionStatus(
3416 OSRF_STATUS_INTERNALSERVERERROR,
3417 "osrfMethodException",
3419 "Malformed query under UNION, INTERSECT or EXCEPT"
3423 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3426 json_type( query->type )
3435 OSRF_BUFFER_ADD( sql, op );
3437 OSRF_BUFFER_ADD( sql, "ALL " );
3440 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3444 "%s: Error building query under%s",
3452 OSRF_BUFFER_ADD( sql, query_str );
3455 if( flags & SUBCOMBO )
3456 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3458 if( !(flags & SUBSELECT) )
3459 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3461 return buffer_release( sql );
3464 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3465 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3466 // or "except" to indicate the type of query.
3467 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3471 osrfAppSessionStatus(
3473 OSRF_STATUS_INTERNALSERVERERROR,
3474 "osrfMethodException",
3476 "Malformed query; no query object"
3478 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3480 } else if( query->type != JSON_HASH ) {
3482 osrfAppSessionStatus(
3484 OSRF_STATUS_INTERNALSERVERERROR,
3485 "osrfMethodException",
3487 "Malformed query object"
3491 "%s: Query object is %s instead of JSON_HASH",
3493 json_type( query->type )
3498 // Determine what kind of query it purports to be, and dispatch accordingly.
3499 if( jsonObjectGetKey( query, "union" ) ||
3500 jsonObjectGetKey( query, "intersect" ) ||
3501 jsonObjectGetKey( query, "except" ) ) {
3502 return doCombo( ctx, query, flags );
3504 // It is presumably a SELECT query
3506 // Push a node onto the stack for the current query. Every level of
3507 // subquery gets its own QueryFrame on the Stack.
3510 // Build an SQL SELECT statement
3513 jsonObjectGetKey( query, "select" ),
3514 jsonObjectGetKey( query, "from" ),
3515 jsonObjectGetKey( query, "where" ),
3516 jsonObjectGetKey( query, "having" ),
3517 jsonObjectGetKey( query, "order_by" ),
3518 jsonObjectGetKey( query, "limit" ),
3519 jsonObjectGetKey( query, "offset" ),
3528 /* method context */ osrfMethodContext* ctx,
3530 /* SELECT */ jsonObject* selhash,
3531 /* FROM */ jsonObject* join_hash,
3532 /* WHERE */ jsonObject* search_hash,
3533 /* HAVING */ jsonObject* having_hash,
3534 /* ORDER BY */ jsonObject* order_hash,
3535 /* LIMIT */ jsonObject* limit,
3536 /* OFFSET */ jsonObject* offset,
3537 /* flags */ int flags
3539 const char* locale = osrf_message_get_last_locale();
3541 // general tmp objects
3542 const jsonObject* tmp_const;
3543 jsonObject* selclass = NULL;
3544 jsonObject* snode = NULL;
3545 jsonObject* onode = NULL;
3547 char* string = NULL;
3548 int from_function = 0;
3553 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3555 // punt if there's no FROM clause
3556 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3559 "%s: FROM clause is missing or empty",
3563 osrfAppSessionStatus(
3565 OSRF_STATUS_INTERNALSERVERERROR,
3566 "osrfMethodException",
3568 "FROM clause is missing or empty in JSON query"
3573 // the core search class
3574 const char* core_class = NULL;
3576 // get the core class -- the only key of the top level FROM clause, or a string
3577 if( join_hash->type == JSON_HASH ) {
3578 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3579 snode = jsonIteratorNext( tmp_itr );
3581 // Populate the current QueryFrame with information
3582 // about the core class
3583 if( add_query_core( NULL, tmp_itr->key ) ) {
3585 osrfAppSessionStatus(
3587 OSRF_STATUS_INTERNALSERVERERROR,
3588 "osrfMethodException",
3590 "Unable to look up core class"
3594 core_class = curr_query->core.class_name;
3597 jsonObject* extra = jsonIteratorNext( tmp_itr );
3599 jsonIteratorFree( tmp_itr );
3602 // There shouldn't be more than one entry in join_hash
3606 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3610 osrfAppSessionStatus(
3612 OSRF_STATUS_INTERNALSERVERERROR,
3613 "osrfMethodException",
3615 "Malformed FROM clause in JSON query"
3617 return NULL; // Malformed join_hash; extra entry
3619 } else if( join_hash->type == JSON_ARRAY ) {
3620 // We're selecting from a function, not from a table
3622 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3625 } else if( join_hash->type == JSON_STRING ) {
3626 // Populate the current QueryFrame with information
3627 // about the core class
3628 core_class = jsonObjectGetString( join_hash );
3630 if( add_query_core( NULL, core_class ) ) {
3632 osrfAppSessionStatus(
3634 OSRF_STATUS_INTERNALSERVERERROR,
3635 "osrfMethodException",
3637 "Unable to look up core class"
3645 "%s: FROM clause is unexpected JSON type: %s",
3647 json_type( join_hash->type )
3650 osrfAppSessionStatus(
3652 OSRF_STATUS_INTERNALSERVERERROR,
3653 "osrfMethodException",
3655 "Ill-formed FROM clause in JSON query"
3660 // Build the join clause, if any, while filling out the list
3661 // of joined classes in the current QueryFrame.
3662 char* join_clause = NULL;
3663 if( join_hash && ! from_function ) {
3665 join_clause = searchJOIN( join_hash, &curr_query->core );
3666 if( ! join_clause ) {
3668 osrfAppSessionStatus(
3670 OSRF_STATUS_INTERNALSERVERERROR,
3671 "osrfMethodException",
3673 "Unable to construct JOIN clause(s)"
3679 // For in case we don't get a select list
3680 jsonObject* defaultselhash = NULL;
3682 // if there is no select list, build a default select list ...
3683 if( !selhash && !from_function ) {
3684 jsonObject* default_list = defaultSelectList( core_class );
3685 if( ! default_list ) {
3687 osrfAppSessionStatus(
3689 OSRF_STATUS_INTERNALSERVERERROR,
3690 "osrfMethodException",
3692 "Unable to build default SELECT clause in JSON query"
3694 free( join_clause );
3699 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3700 jsonObjectSetKey( selhash, core_class, default_list );
3703 // The SELECT clause can be encoded only by a hash
3704 if( !from_function && selhash->type != JSON_HASH ) {
3707 "%s: Expected JSON_HASH for SELECT clause; found %s",
3709 json_type( selhash->type )
3713 osrfAppSessionStatus(
3715 OSRF_STATUS_INTERNALSERVERERROR,
3716 "osrfMethodException",
3718 "Malformed SELECT clause in JSON query"
3720 free( join_clause );
3724 // If you see a null or wild card specifier for the core class, or an
3725 // empty array, replace it with a default SELECT list
3726 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3728 int default_needed = 0; // boolean
3729 if( JSON_STRING == tmp_const->type
3730 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3732 else if( JSON_NULL == tmp_const->type )
3735 if( default_needed ) {
3736 // Build a default SELECT list
3737 jsonObject* default_list = defaultSelectList( core_class );
3738 if( ! default_list ) {
3740 osrfAppSessionStatus(
3742 OSRF_STATUS_INTERNALSERVERERROR,
3743 "osrfMethodException",
3745 "Can't build default SELECT clause in JSON query"
3747 free( join_clause );
3752 jsonObjectSetKey( selhash, core_class, default_list );
3756 // temp buffers for the SELECT list and GROUP BY clause
3757 growing_buffer* select_buf = buffer_init( 128 );
3758 growing_buffer* group_buf = buffer_init( 128 );
3760 int aggregate_found = 0; // boolean
3762 // Build a select list
3763 if( from_function ) // From a function we select everything
3764 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3767 // Build the SELECT list as SQL
3771 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3772 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3774 const char* cname = selclass_itr->key;
3776 // Make sure the target relation is in the FROM clause.
3778 // At this point join_hash is a step down from the join_hash we
3779 // received as a parameter. If the original was a JSON_STRING,
3780 // then json_hash is now NULL. If the original was a JSON_HASH,
3781 // then json_hash is now the first (and only) entry in it,
3782 // denoting the core class. We've already excluded the
3783 // possibility that the original was a JSON_ARRAY, because in
3784 // that case from_function would be non-NULL, and we wouldn't
3787 // If the current table alias isn't in scope, bail out
3788 ClassInfo* class_info = search_alias( cname );
3789 if( ! class_info ) {
3792 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3797 osrfAppSessionStatus(
3799 OSRF_STATUS_INTERNALSERVERERROR,
3800 "osrfMethodException",
3802 "Selected class not in FROM clause in JSON query"
3804 jsonIteratorFree( selclass_itr );
3805 buffer_free( select_buf );
3806 buffer_free( group_buf );
3807 if( defaultselhash )
3808 jsonObjectFree( defaultselhash );
3809 free( join_clause );
3813 if( selclass->type != JSON_ARRAY ) {
3816 "%s: Malformed SELECT list for class \"%s\"; not an array",
3821 osrfAppSessionStatus(
3823 OSRF_STATUS_INTERNALSERVERERROR,
3824 "osrfMethodException",
3826 "Selected class not in FROM clause in JSON query"
3829 jsonIteratorFree( selclass_itr );
3830 buffer_free( select_buf );
3831 buffer_free( group_buf );
3832 if( defaultselhash )
3833 jsonObjectFree( defaultselhash );
3834 free( join_clause );
3838 // Look up some attributes of the current class
3839 osrfHash* idlClass = class_info->class_def;
3840 osrfHash* class_field_set = class_info->fields;
3841 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3842 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3844 if( 0 == selclass->size ) {
3847 "%s: No columns selected from \"%s\"",
3853 // stitch together the column list for the current table alias...
3854 unsigned long field_idx = 0;
3855 jsonObject* selfield = NULL;
3856 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3858 // If we need a separator comma, add one
3862 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3865 // if the field specification is a string, add it to the list
3866 if( selfield->type == JSON_STRING ) {
3868 // Look up the field in the IDL
3869 const char* col_name = jsonObjectGetString( selfield );
3870 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3872 // No such field in current class
3875 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3881 osrfAppSessionStatus(
3883 OSRF_STATUS_INTERNALSERVERERROR,
3884 "osrfMethodException",
3886 "Selected column not defined in JSON query"
3888 jsonIteratorFree( selclass_itr );
3889 buffer_free( select_buf );
3890 buffer_free( group_buf );
3891 if( defaultselhash )
3892 jsonObjectFree( defaultselhash );
3893 free( join_clause );
3895 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3896 // Virtual field not allowed
3899 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3905 osrfAppSessionStatus(
3907 OSRF_STATUS_INTERNALSERVERERROR,
3908 "osrfMethodException",
3910 "Selected column may not be virtual in JSON query"
3912 jsonIteratorFree( selclass_itr );
3913 buffer_free( select_buf );
3914 buffer_free( group_buf );
3915 if( defaultselhash )
3916 jsonObjectFree( defaultselhash );
3917 free( join_clause );
3923 if( flags & DISABLE_I18N )
3926 i18n = osrfHashGet( field_def, "i18n" );
3928 if( str_is_true( i18n ) ) {
3929 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
3930 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3931 class_tname, cname, col_name, class_pkey,
3932 cname, class_pkey, locale, col_name );
3934 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3935 cname, col_name, col_name );
3938 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3939 cname, col_name, col_name );
3942 // ... but it could be an object, in which case we check for a Field Transform
3943 } else if( selfield->type == JSON_HASH ) {
3945 const char* col_name = jsonObjectGetString(
3946 jsonObjectGetKeyConst( selfield, "column" ) );
3948 // Get the field definition from the IDL
3949 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3951 // No such field in current class
3954 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3960 osrfAppSessionStatus(
3962 OSRF_STATUS_INTERNALSERVERERROR,
3963 "osrfMethodException",
3965 "Selected column is not defined in JSON query"
3967 jsonIteratorFree( selclass_itr );
3968 buffer_free( select_buf );
3969 buffer_free( group_buf );
3970 if( defaultselhash )
3971 jsonObjectFree( defaultselhash );
3972 free( join_clause );
3974 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
3975 // No such field in current class
3978 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3984 osrfAppSessionStatus(
3986 OSRF_STATUS_INTERNALSERVERERROR,
3987 "osrfMethodException",
3989 "Selected column is virtual in JSON query"
3991 jsonIteratorFree( selclass_itr );
3992 buffer_free( select_buf );
3993 buffer_free( group_buf );
3994 if( defaultselhash )
3995 jsonObjectFree( defaultselhash );
3996 free( join_clause );
4000 // Decide what to use as a column alias
4002 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4003 _alias = jsonObjectGetString( tmp_const );
4004 } else { // Use field name as the alias
4008 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4009 char* transform_str = searchFieldTransform(
4010 class_info->alias, field_def, selfield );
4011 if( transform_str ) {
4012 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4013 free( transform_str );
4016 osrfAppSessionStatus(
4018 OSRF_STATUS_INTERNALSERVERERROR,
4019 "osrfMethodException",
4021 "Unable to generate transform function in JSON query"
4023 jsonIteratorFree( selclass_itr );
4024 buffer_free( select_buf );
4025 buffer_free( group_buf );
4026 if( defaultselhash )
4027 jsonObjectFree( defaultselhash );
4028 free( join_clause );
4035 if( flags & DISABLE_I18N )
4038 i18n = osrfHashGet( field_def, "i18n" );
4040 if( str_is_true( i18n ) ) {
4041 buffer_fadd( select_buf,
4042 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4043 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4044 class_tname, cname, col_name, class_pkey, cname,
4045 class_pkey, locale, _alias );
4047 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4048 cname, col_name, _alias );
4051 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4052 cname, col_name, _alias );
4059 "%s: Selected item is unexpected JSON type: %s",
4061 json_type( selfield->type )
4064 osrfAppSessionStatus(
4066 OSRF_STATUS_INTERNALSERVERERROR,
4067 "osrfMethodException",
4069 "Ill-formed SELECT item in JSON query"
4071 jsonIteratorFree( selclass_itr );
4072 buffer_free( select_buf );
4073 buffer_free( group_buf );
4074 if( defaultselhash )
4075 jsonObjectFree( defaultselhash );
4076 free( join_clause );
4080 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
4081 if( obj_is_true( agg_obj ) )
4082 aggregate_found = 1;
4084 // Append a comma (except for the first one)
4085 // and add the column to a GROUP BY clause
4089 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4091 buffer_fadd( group_buf, " %d", sel_pos );
4095 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4097 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4098 if ( ! obj_is_true( aggregate_obj ) ) {
4102 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4105 buffer_fadd(group_buf, " %d", sel_pos);
4108 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4112 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4115 _column = searchFieldTransform(class_info->alias, field, selfield);
4116 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4117 OSRF_BUFFER_ADD(group_buf, _column);
4118 _column = searchFieldTransform(class_info->alias, field, selfield);
4125 } // end while -- iterating across SELECT columns
4127 } // end while -- iterating across classes
4129 jsonIteratorFree( selclass_itr );
4133 char* col_list = buffer_release( select_buf );
4135 // Make sure the SELECT list isn't empty. This can happen, for example,
4136 // if we try to build a default SELECT clause from a non-core table.
4139 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4141 osrfAppSessionStatus(
4143 OSRF_STATUS_INTERNALSERVERERROR,
4144 "osrfMethodException",
4146 "SELECT list is empty"
4149 buffer_free( group_buf );
4150 if( defaultselhash )
4151 jsonObjectFree( defaultselhash );
4152 free( join_clause );
4158 table = searchValueTransform( join_hash );
4160 table = strdup( curr_query->core.source_def );
4164 osrfAppSessionStatus(
4166 OSRF_STATUS_INTERNALSERVERERROR,
4167 "osrfMethodException",
4169 "Unable to identify table for core class"
4172 buffer_free( group_buf );
4173 if( defaultselhash )
4174 jsonObjectFree( defaultselhash );
4175 free( join_clause );
4179 // Put it all together
4180 growing_buffer* sql_buf = buffer_init( 128 );
4181 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4185 // Append the join clause, if any
4187 buffer_add(sql_buf, join_clause );
4188 free( join_clause );
4191 char* order_by_list = NULL;
4192 char* having_buf = NULL;
4194 if( !from_function ) {
4196 // Build a WHERE clause, if there is one
4198 buffer_add( sql_buf, " WHERE " );
4200 // and it's on the WHERE clause
4201 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4204 osrfAppSessionStatus(
4206 OSRF_STATUS_INTERNALSERVERERROR,
4207 "osrfMethodException",
4209 "Severe query error in WHERE predicate -- see error log for more details"
4212 buffer_free( group_buf );
4213 buffer_free( sql_buf );
4214 if( defaultselhash )
4215 jsonObjectFree( defaultselhash );
4219 buffer_add( sql_buf, pred );
4223 // Build a HAVING clause, if there is one
4226 // and it's on the the WHERE clause
4227 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4229 if( ! having_buf ) {
4231 osrfAppSessionStatus(
4233 OSRF_STATUS_INTERNALSERVERERROR,
4234 "osrfMethodException",
4236 "Severe query error in HAVING predicate -- see error log for more details"
4239 buffer_free( group_buf );
4240 buffer_free( sql_buf );
4241 if( defaultselhash )
4242 jsonObjectFree( defaultselhash );
4247 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4249 // Build an ORDER BY clause, if there is one
4250 if( NULL == order_hash )
4251 ; // No ORDER BY? do nothing
4252 else if( JSON_ARRAY == order_hash->type ) {
4253 // Array of field specifications, each specification being a
4254 // hash to define the class, field, and other details
4256 jsonObject* order_spec;
4257 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4259 if( JSON_HASH != order_spec->type ) {
4260 osrfLogError( OSRF_LOG_MARK,
4261 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4262 modulename, json_type( order_spec->type ) );
4264 osrfAppSessionStatus(
4266 OSRF_STATUS_INTERNALSERVERERROR,
4267 "osrfMethodException",
4269 "Malformed ORDER BY clause -- see error log for more details"
4271 buffer_free( order_buf );
4273 buffer_free( group_buf );
4274 buffer_free( sql_buf );
4275 if( defaultselhash )
4276 jsonObjectFree( defaultselhash );
4280 const char* class_alias =
4281 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4283 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4286 OSRF_BUFFER_ADD( order_buf, ", " );
4288 order_buf = buffer_init( 128 );
4290 if( !field || !class_alias ) {
4291 osrfLogError( OSRF_LOG_MARK,
4292 "%s: Missing class or field name in field specification "
4293 "of ORDER BY clause",
4296 osrfAppSessionStatus(
4298 OSRF_STATUS_INTERNALSERVERERROR,
4299 "osrfMethodException",
4301 "Malformed ORDER BY clause -- see error log for more details"
4303 buffer_free( order_buf );
4305 buffer_free( group_buf );
4306 buffer_free( sql_buf );
4307 if( defaultselhash )
4308 jsonObjectFree( defaultselhash );
4312 ClassInfo* order_class_info = search_alias( class_alias );
4313 if( ! order_class_info ) {
4314 osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4315 "not in FROM clause", modulename, class_alias );
4317 osrfAppSessionStatus(
4319 OSRF_STATUS_INTERNALSERVERERROR,
4320 "osrfMethodException",
4322 "Invalid class referenced in ORDER BY clause -- "
4323 "see error log for more details"
4326 buffer_free( group_buf );
4327 buffer_free( sql_buf );
4328 if( defaultselhash )
4329 jsonObjectFree( defaultselhash );
4333 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4335 osrfLogError( OSRF_LOG_MARK,
4336 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4337 modulename, class_alias, field );
4339 osrfAppSessionStatus(
4341 OSRF_STATUS_INTERNALSERVERERROR,
4342 "osrfMethodException",
4344 "Invalid field referenced in ORDER BY clause -- "
4345 "see error log for more details"
4348 buffer_free( group_buf );
4349 buffer_free( sql_buf );
4350 if( defaultselhash )
4351 jsonObjectFree( defaultselhash );
4353 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4354 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4355 modulename, field );
4357 osrfAppSessionStatus(
4359 OSRF_STATUS_INTERNALSERVERERROR,
4360 "osrfMethodException",
4362 "Virtual field in ORDER BY clause -- see error log for more details"
4364 buffer_free( order_buf );
4366 buffer_free( group_buf );
4367 buffer_free( sql_buf );
4368 if( defaultselhash )
4369 jsonObjectFree( defaultselhash );
4373 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4374 char* transform_str = searchFieldTransform(
4375 class_alias, field_def, order_spec );
4376 if( ! transform_str ) {
4378 osrfAppSessionStatus(
4380 OSRF_STATUS_INTERNALSERVERERROR,
4381 "osrfMethodException",
4383 "Severe query error in ORDER BY clause -- "
4384 "see error log for more details"
4386 buffer_free( order_buf );
4388 buffer_free( group_buf );
4389 buffer_free( sql_buf );
4390 if( defaultselhash )
4391 jsonObjectFree( defaultselhash );
4395 OSRF_BUFFER_ADD( order_buf, transform_str );
4396 free( transform_str );
4399 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4401 const char* direction =
4402 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4404 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4405 OSRF_BUFFER_ADD( order_buf, " DESC" );
4407 OSRF_BUFFER_ADD( order_buf, " ASC" );
4410 } else if( JSON_HASH == order_hash->type ) {
4411 // This hash is keyed on class alias. Each class has either
4412 // an array of field names or a hash keyed on field name.
4413 jsonIterator* class_itr = jsonNewIterator( order_hash );
4414 while( (snode = jsonIteratorNext( class_itr )) ) {
4416 ClassInfo* order_class_info = search_alias( class_itr->key );
4417 if( ! order_class_info ) {
4418 osrfLogError( OSRF_LOG_MARK,
4419 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4420 modulename, class_itr->key );
4422 osrfAppSessionStatus(
4424 OSRF_STATUS_INTERNALSERVERERROR,
4425 "osrfMethodException",
4427 "Invalid class referenced in ORDER BY clause -- "
4428 "see error log for more details"
4430 jsonIteratorFree( class_itr );
4431 buffer_free( order_buf );
4433 buffer_free( group_buf );
4434 buffer_free( sql_buf );
4435 if( defaultselhash )
4436 jsonObjectFree( defaultselhash );
4440 osrfHash* field_list_def = order_class_info->fields;
4442 if( snode->type == JSON_HASH ) {
4444 // Hash is keyed on field names from the current class. For each field
4445 // there is another layer of hash to define the sorting details, if any,
4446 // or a string to indicate direction of sorting.
4447 jsonIterator* order_itr = jsonNewIterator( snode );
4448 while( (onode = jsonIteratorNext( order_itr )) ) {
4450 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4452 osrfLogError( OSRF_LOG_MARK,
4453 "%s: Invalid field \"%s\" in ORDER BY clause",
4454 modulename, order_itr->key );
4456 osrfAppSessionStatus(
4458 OSRF_STATUS_INTERNALSERVERERROR,
4459 "osrfMethodException",
4461 "Invalid field in ORDER BY clause -- "
4462 "see error log for more details"
4464 jsonIteratorFree( order_itr );
4465 jsonIteratorFree( class_itr );
4466 buffer_free( order_buf );
4468 buffer_free( group_buf );
4469 buffer_free( sql_buf );
4470 if( defaultselhash )
4471 jsonObjectFree( defaultselhash );
4473 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4474 osrfLogError( OSRF_LOG_MARK,
4475 "%s: Virtual field \"%s\" in ORDER BY clause",
4476 modulename, order_itr->key );
4478 osrfAppSessionStatus(
4480 OSRF_STATUS_INTERNALSERVERERROR,
4481 "osrfMethodException",
4483 "Virtual field in ORDER BY clause -- "
4484 "see error log for more details"
4486 jsonIteratorFree( order_itr );
4487 jsonIteratorFree( class_itr );
4488 buffer_free( order_buf );
4490 buffer_free( group_buf );
4491 buffer_free( sql_buf );
4492 if( defaultselhash )
4493 jsonObjectFree( defaultselhash );
4497 const char* direction = NULL;
4498 if( onode->type == JSON_HASH ) {
4499 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4500 string = searchFieldTransform(
4502 osrfHashGet( field_list_def, order_itr->key ),
4506 if( ctx ) osrfAppSessionStatus(
4508 OSRF_STATUS_INTERNALSERVERERROR,
4509 "osrfMethodException",
4511 "Severe query error in ORDER BY clause -- "
4512 "see error log for more details"
4514 jsonIteratorFree( order_itr );
4515 jsonIteratorFree( class_itr );
4517 buffer_free( group_buf );
4518 buffer_free( order_buf);
4519 buffer_free( sql_buf );
4520 if( defaultselhash )
4521 jsonObjectFree( defaultselhash );
4525 growing_buffer* field_buf = buffer_init( 16 );
4526 buffer_fadd( field_buf, "\"%s\".%s",
4527 class_itr->key, order_itr->key );
4528 string = buffer_release( field_buf );
4531 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4532 const char* dir = jsonObjectGetString( tmp_const );
4533 if(!strncasecmp( dir, "d", 1 )) {
4534 direction = " DESC";
4540 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4541 osrfLogError( OSRF_LOG_MARK,
4542 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4543 modulename, json_type( onode->type ) );
4545 osrfAppSessionStatus(
4547 OSRF_STATUS_INTERNALSERVERERROR,
4548 "osrfMethodException",
4550 "Malformed ORDER BY clause -- see error log for more details"
4552 jsonIteratorFree( order_itr );
4553 jsonIteratorFree( class_itr );
4555 buffer_free( group_buf );
4556 buffer_free( order_buf );
4557 buffer_free( sql_buf );
4558 if( defaultselhash )
4559 jsonObjectFree( defaultselhash );
4563 string = strdup( order_itr->key );
4564 const char* dir = jsonObjectGetString( onode );
4565 if( !strncasecmp( dir, "d", 1 )) {
4566 direction = " DESC";
4573 OSRF_BUFFER_ADD( order_buf, ", " );
4575 order_buf = buffer_init( 128 );
4577 OSRF_BUFFER_ADD( order_buf, string );
4581 OSRF_BUFFER_ADD( order_buf, direction );
4585 jsonIteratorFree( order_itr );
4587 } else if( snode->type == JSON_ARRAY ) {
4589 // Array is a list of fields from the current class
4590 unsigned long order_idx = 0;
4591 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4593 const char* _f = jsonObjectGetString( onode );
4595 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4597 osrfLogError( OSRF_LOG_MARK,
4598 "%s: Invalid field \"%s\" in ORDER BY clause",
4601 osrfAppSessionStatus(
4603 OSRF_STATUS_INTERNALSERVERERROR,
4604 "osrfMethodException",
4606 "Invalid field in ORDER BY clause -- "
4607 "see error log for more details"
4609 jsonIteratorFree( class_itr );
4610 buffer_free( order_buf );
4612 buffer_free( group_buf );
4613 buffer_free( sql_buf );
4614 if( defaultselhash )
4615 jsonObjectFree( defaultselhash );
4617 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4618 osrfLogError( OSRF_LOG_MARK,
4619 "%s: Virtual field \"%s\" in ORDER BY clause",
4622 osrfAppSessionStatus(
4624 OSRF_STATUS_INTERNALSERVERERROR,
4625 "osrfMethodException",
4627 "Virtual field in ORDER BY clause -- "
4628 "see error log for more details"
4630 jsonIteratorFree( class_itr );
4631 buffer_free( order_buf );
4633 buffer_free( group_buf );
4634 buffer_free( sql_buf );
4635 if( defaultselhash )
4636 jsonObjectFree( defaultselhash );
4641 OSRF_BUFFER_ADD( order_buf, ", " );
4643 order_buf = buffer_init( 128 );
4645 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4649 // IT'S THE OOOOOOOOOOOLD STYLE!
4651 osrfLogError( OSRF_LOG_MARK,
4652 "%s: Possible SQL injection attempt; direct order by is not allowed",
4655 osrfAppSessionStatus(
4657 OSRF_STATUS_INTERNALSERVERERROR,
4658 "osrfMethodException",
4660 "Severe query error -- see error log for more details"
4665 buffer_free( group_buf );
4666 buffer_free( order_buf );
4667 buffer_free( sql_buf );
4668 if( defaultselhash )
4669 jsonObjectFree( defaultselhash );
4670 jsonIteratorFree( class_itr );
4674 jsonIteratorFree( class_itr );
4676 osrfLogError( OSRF_LOG_MARK,
4677 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4678 modulename, json_type( order_hash->type ) );
4680 osrfAppSessionStatus(
4682 OSRF_STATUS_INTERNALSERVERERROR,
4683 "osrfMethodException",
4685 "Malformed ORDER BY clause -- see error log for more details"
4687 buffer_free( order_buf );
4689 buffer_free( group_buf );
4690 buffer_free( sql_buf );
4691 if( defaultselhash )
4692 jsonObjectFree( defaultselhash );
4697 order_by_list = buffer_release( order_buf );
4701 string = buffer_release( group_buf );
4703 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4704 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4705 OSRF_BUFFER_ADD( sql_buf, string );
4710 if( having_buf && *having_buf ) {
4711 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4712 OSRF_BUFFER_ADD( sql_buf, having_buf );
4716 if( order_by_list ) {
4718 if( *order_by_list ) {
4719 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4720 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4723 free( order_by_list );
4727 const char* str = jsonObjectGetString( limit );
4728 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4732 const char* str = jsonObjectGetString( offset );
4733 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4736 if( !(flags & SUBSELECT) )
4737 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4739 if( defaultselhash )
4740 jsonObjectFree( defaultselhash );
4742 return buffer_release( sql_buf );
4744 } // end of SELECT()
4746 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4748 const char* locale = osrf_message_get_last_locale();
4750 osrfHash* fields = osrfHashGet( meta, "fields" );
4751 char* core_class = osrfHashGet( meta, "classname" );
4753 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4755 jsonObject* node = NULL;
4756 jsonObject* snode = NULL;
4757 jsonObject* onode = NULL;
4758 const jsonObject* _tmp = NULL;
4759 jsonObject* selhash = NULL;
4760 jsonObject* defaultselhash = NULL;
4762 growing_buffer* sql_buf = buffer_init( 128 );
4763 growing_buffer* select_buf = buffer_init( 128 );
4765 if( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4766 defaultselhash = jsonNewObjectType( JSON_HASH );
4767 selhash = defaultselhash;
4770 // If there's no SELECT list for the core class, build one
4771 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4772 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4774 // Add every non-virtual field to the field list
4775 osrfHash* field_def = NULL;
4776 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4777 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4778 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4779 const char* field = osrfHashIteratorKey( field_itr );
4780 jsonObjectPush( field_list, jsonNewObject( field ) );
4783 osrfHashIteratorFree( field_itr );
4784 jsonObjectSetKey( selhash, core_class, field_list );
4788 jsonIterator* class_itr = jsonNewIterator( selhash );
4789 while( (snode = jsonIteratorNext( class_itr )) ) {
4791 const char* cname = class_itr->key;
4792 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4796 if( strcmp(core_class,class_itr->key )) {
4800 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4801 if( !found->size ) {
4802 jsonObjectFree( found );
4806 jsonObjectFree( found );
4809 jsonIterator* select_itr = jsonNewIterator( snode );
4810 while( (node = jsonIteratorNext( select_itr )) ) {
4811 const char* item_str = jsonObjectGetString( node );
4812 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4813 char* fname = osrfHashGet( field, "name" );
4821 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4826 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4827 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4830 i18n = osrfHashGet( field, "i18n" );
4832 if( str_is_true( i18n ) ) {
4833 char* pkey = osrfHashGet( idlClass, "primarykey" );
4834 char* tname = osrfHashGet( idlClass, "tablename" );
4836 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4837 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4838 tname, cname, fname, pkey, cname, pkey, locale, fname );
4840 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4843 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4847 jsonIteratorFree( select_itr );
4850 jsonIteratorFree( class_itr );
4852 char* col_list = buffer_release( select_buf );
4853 char* table = oilsGetRelation( meta );
4855 table = strdup( "(null)" );
4857 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4861 // Clear the query stack (as a fail-safe precaution against possible
4862 // leftover garbage); then push the first query frame onto the stack.
4863 clear_query_stack();
4865 if( add_query_core( NULL, core_class ) ) {
4867 osrfAppSessionStatus(
4869 OSRF_STATUS_INTERNALSERVERERROR,
4870 "osrfMethodException",
4872 "Unable to build query frame for core class"
4878 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4879 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
4880 OSRF_BUFFER_ADD( sql_buf, join_clause );
4881 free( join_clause );
4884 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4885 modulename, OSRF_BUFFER_C_STR( sql_buf ));
4887 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
4889 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4891 osrfAppSessionStatus(
4893 OSRF_STATUS_INTERNALSERVERERROR,
4894 "osrfMethodException",
4896 "Severe query error -- see error log for more details"
4898 buffer_free( sql_buf );
4899 if( defaultselhash )
4900 jsonObjectFree( defaultselhash );
4901 clear_query_stack();
4904 buffer_add( sql_buf, pred );
4909 char* string = NULL;
4910 if( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4912 growing_buffer* order_buf = buffer_init( 128 );
4915 jsonIterator* class_itr = jsonNewIterator( _tmp );
4916 while( (snode = jsonIteratorNext( class_itr )) ) {
4918 if( !jsonObjectGetKeyConst( selhash,class_itr->key ))
4921 if( snode->type == JSON_HASH ) {
4923 jsonIterator* order_itr = jsonNewIterator( snode );
4924 while( (onode = jsonIteratorNext( order_itr )) ) {
4926 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4927 class_itr->key, order_itr->key );
4931 char* direction = NULL;
4932 if( onode->type == JSON_HASH ) {
4933 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4934 string = searchFieldTransform( class_itr->key, field_def, onode );
4936 osrfAppSessionStatus(
4938 OSRF_STATUS_INTERNALSERVERERROR,
4939 "osrfMethodException",
4941 "Severe query error in ORDER BY clause -- "
4942 "see error log for more details"
4944 jsonIteratorFree( order_itr );
4945 jsonIteratorFree( class_itr );
4946 buffer_free( order_buf );
4947 buffer_free( sql_buf );
4948 if( defaultselhash )
4949 jsonObjectFree( defaultselhash );
4950 clear_query_stack();
4954 growing_buffer* field_buf = buffer_init( 16 );
4955 buffer_fadd( field_buf, "\"%s\".%s",
4956 class_itr->key, order_itr->key );
4957 string = buffer_release( field_buf );
4960 if( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4961 const char* dir = jsonObjectGetString( _tmp );
4962 if(!strncasecmp( dir, "d", 1 )) {
4963 direction = " DESC";
4967 string = strdup( order_itr->key );
4968 const char* dir = jsonObjectGetString( onode );
4969 if( !strncasecmp( dir, "d", 1 )) {
4970 direction = " DESC";
4979 buffer_add( order_buf, ", " );
4982 buffer_add( order_buf, string );
4986 buffer_add( order_buf, direction );
4990 jsonIteratorFree( order_itr );
4993 const char* str = jsonObjectGetString( snode );
4994 buffer_add( order_buf, str );
5000 jsonIteratorFree( class_itr );
5002 string = buffer_release( order_buf );
5005 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5006 OSRF_BUFFER_ADD( sql_buf, string );
5012 if( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ) {
5013 const char* str = jsonObjectGetString( _tmp );
5021 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
5023 const char* str = jsonObjectGetString( _tmp );
5032 if( defaultselhash )
5033 jsonObjectFree( defaultselhash );
5034 clear_query_stack();
5036 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5037 return buffer_release( sql_buf );
5040 int doJSONSearch ( osrfMethodContext* ctx ) {
5041 if(osrfMethodVerifyContext( ctx )) {
5042 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5046 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5051 dbhandle = writehandle;
5053 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5057 if( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
5058 flags |= SELECT_DISTINCT;
5060 if( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
5061 flags |= DISABLE_I18N;
5063 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5064 clear_query_stack(); // a possibly needless precaution
5065 char* sql = buildQuery( ctx, hash, flags );
5066 clear_query_stack();
5073 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5074 dbi_result result = dbi_conn_query( dbhandle, sql );
5077 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5079 if( dbi_result_first_row( result )) {
5080 /* JSONify the result */
5081 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5084 jsonObject* return_val = oilsMakeJSONFromResult( result );
5085 osrfAppRespond( ctx, return_val );
5086 jsonObjectFree( return_val );
5087 } while( dbi_result_next_row( result ));
5090 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5093 osrfAppRespondComplete( ctx, NULL );
5095 /* clean up the query */
5096 dbi_result_free( result );
5100 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]", modulename, sql );
5101 osrfAppSessionStatus(
5103 OSRF_STATUS_INTERNALSERVERERROR,
5104 "osrfMethodException",
5106 "Severe query error -- see error log for more details"
5114 // The last parameter, err, is used to report an error condition by updating an int owned by
5115 // the calling code.
5117 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5118 // It is the responsibility of the calling code to initialize *err before the
5119 // call, so that it will be able to make sense of the result.
5121 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5122 // redundant anyway.
5123 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5124 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5127 dbhandle = writehandle;
5129 char* core_class = osrfHashGet( class_meta, "classname" );
5130 char* pkey = osrfHashGet( class_meta, "primarykey" );
5132 const jsonObject* _tmp;
5134 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5136 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5141 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5143 dbi_result result = dbi_conn_query( dbhandle, sql );
5144 if( NULL == result ) {
5145 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
5146 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql );
5147 osrfAppSessionStatus(
5149 OSRF_STATUS_INTERNALSERVERERROR,
5150 "osrfMethodException",
5152 "Severe query error -- see error log for more details"
5159 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5162 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5163 jsonObject* row_obj = NULL;
5165 if( dbi_result_first_row( result )) {
5167 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5168 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5169 // eliminate the duplicates.
5170 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5171 osrfHash* dedup = osrfNewHash();
5173 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5174 char* pkey_val = oilsFMGetString( row_obj, pkey );
5175 if( osrfHashGet( dedup, pkey_val ) ) {
5176 jsonObjectFree( row_obj );
5179 osrfHashSet( dedup, pkey_val, pkey_val );
5180 jsonObjectPush( res_list, row_obj );
5182 } while( dbi_result_next_row( result ));
5183 osrfHashFree( dedup );
5186 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5190 /* clean up the query */
5191 dbi_result_free( result );
5194 // If we're asked to flesh, and there's anything to flesh, then flesh.
5195 if( res_list->size && query_hash ) {
5196 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5198 // Get the flesh depth
5199 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5200 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5201 flesh_depth = max_flesh_depth;
5203 // We need a non-zero flesh depth, and a list of fields to flesh
5204 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5205 if( temp_blob && flesh_depth > 0 ) {
5207 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5208 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5210 osrfStringArray* link_fields = NULL;
5211 osrfHash* links = osrfHashGet( class_meta, "links" );
5213 // Make an osrfStringArray of the names of fields to be fleshed
5214 if( flesh_fields ) {
5215 if( flesh_fields->size == 1 ) {
5216 const char* _t = jsonObjectGetString(
5217 jsonObjectGetIndex( flesh_fields, 0 ) );
5218 if( !strcmp( _t, "*" ))
5219 link_fields = osrfHashKeys( links );
5222 if( !link_fields ) {
5224 link_fields = osrfNewStringArray( 1 );
5225 jsonIterator* _i = jsonNewIterator( flesh_fields );
5226 while ((_f = jsonIteratorNext( _i ))) {
5227 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5229 jsonIteratorFree( _i );
5233 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5235 // Iterate over the JSON_ARRAY of rows
5237 unsigned long res_idx = 0;
5238 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5241 const char* link_field;
5243 // Iterate over the list of fleshable fields
5244 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5246 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5248 osrfHash* kid_link = osrfHashGet( links, link_field );
5250 continue; // Not a link field; skip it
5252 osrfHash* field = osrfHashGet( fields, link_field );
5254 continue; // Not a field at all; skip it (IDL is ill-formed)
5256 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5257 osrfHashGet( kid_link, "class" ));
5259 continue; // The class it links to doesn't exist; skip it
5261 const char* reltype = osrfHashGet( kid_link, "reltype" );
5263 continue; // No reltype; skip it (IDL is ill-formed)
5265 osrfHash* value_field = field;
5267 if( !strcmp( reltype, "has_many" )
5268 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5269 value_field = osrfHashGet(
5270 fields, osrfHashGet( class_meta, "primarykey" ) );
5273 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5275 if( link_map->size > 0 ) {
5276 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5279 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5284 osrfHashGet( kid_link, "class" ),
5291 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5292 osrfHashGet( kid_link, "field" ),
5293 osrfHashGet( kid_link, "class" ),
5294 osrfHashGet( kid_link, "key" ),
5295 osrfHashGet( kid_link, "reltype" )
5298 const char* search_key = jsonObjectGetString(
5299 jsonObjectGetIndex( cur,
5300 atoi( osrfHashGet( value_field, "array_position" ) )
5305 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5309 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5311 // construct WHERE clause
5312 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5315 osrfHashGet( kid_link, "key" ),
5316 jsonNewObject( search_key )
5319 // construct the rest of the query, mostly
5320 // by copying pieces of the previous level of query
5321 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5322 jsonObjectSetKey( rest_of_query, "flesh",
5323 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5327 jsonObjectSetKey( rest_of_query, "flesh_fields",
5328 jsonObjectClone( flesh_blob ));
5330 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5331 jsonObjectSetKey( rest_of_query, "order_by",
5332 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5336 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5337 jsonObjectSetKey( rest_of_query, "select",
5338 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5342 // do the query, recursively, to expand the fleshable field
5343 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5344 where_clause, rest_of_query, err );
5346 jsonObjectFree( where_clause );
5347 jsonObjectFree( rest_of_query );
5350 osrfStringArrayFree( link_fields );
5351 jsonObjectFree( res_list );
5352 jsonObjectFree( flesh_blob );
5356 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5357 osrfHashGet( kid_link, "class" ), kids->size );
5359 // Traverse the result set
5360 jsonObject* X = NULL;
5361 if( link_map->size > 0 && kids->size > 0 ) {
5363 kids = jsonNewObjectType( JSON_ARRAY );
5365 jsonObject* _k_node;
5366 unsigned long res_idx = 0;
5367 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5373 (unsigned long) atoi(
5379 osrfHashGet( kid_link, "class" )
5383 osrfStringArrayGetString( link_map, 0 )
5391 } // end while loop traversing X
5394 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5395 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5396 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5397 osrfHashGet( kid_link, "field" ));
5400 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5401 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5405 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5407 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5408 osrfHashGet( kid_link, "field" ) );
5411 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5412 jsonObjectClone( kids )
5417 jsonObjectFree( kids );
5421 jsonObjectFree( kids );
5423 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5424 osrfHashGet( kid_link, "field" ) );
5425 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5427 } // end while loop traversing list of fleshable fields
5428 } // end while loop traversing res_list
5429 jsonObjectFree( flesh_blob );
5430 osrfStringArrayFree( link_fields );
5439 int doUpdate( osrfMethodContext* ctx ) {
5440 if( osrfMethodVerifyContext( ctx )) {
5441 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5446 timeout_needs_resetting = 1;
5448 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5450 jsonObject* target = NULL;
5452 target = jsonObjectGetIndex( ctx->params, 1 );
5454 target = jsonObjectGetIndex( ctx->params, 0 );
5456 if(!verifyObjectClass( ctx, target )) {
5457 osrfAppRespondComplete( ctx, NULL );
5461 if( getXactId( ctx ) == NULL ) {
5462 osrfAppSessionStatus(
5464 OSRF_STATUS_BADREQUEST,
5465 "osrfMethodException",
5467 "No active transaction -- required for UPDATE"
5469 osrfAppRespondComplete( ctx, NULL );
5473 // The following test is harmless but redundant. If a class is
5474 // readonly, we don't register an update method for it.
5475 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5476 osrfAppSessionStatus(
5478 OSRF_STATUS_BADREQUEST,
5479 "osrfMethodException",
5481 "Cannot UPDATE readonly class"
5483 osrfAppRespondComplete( ctx, NULL );
5487 dbhandle = writehandle;
5488 const char* trans_id = getXactId( ctx );
5490 // Set the last_xact_id
5491 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5493 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5494 trans_id, target->classname, index );
5495 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5498 char* pkey = osrfHashGet( meta, "primarykey" );
5499 osrfHash* fields = osrfHashGet( meta, "fields" );
5501 char* id = oilsFMGetString( target, pkey );
5505 "%s updating %s object with %s = %s",
5507 osrfHashGet( meta, "fieldmapper" ),
5512 growing_buffer* sql = buffer_init( 128 );
5513 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5516 osrfHash* field_def = NULL;
5517 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5518 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5520 // Skip virtual fields, and the primary key
5521 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5524 const char* field_name = osrfHashIteratorKey( field_itr );
5525 if( ! strcmp( field_name, pkey ) )
5528 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5530 int value_is_numeric = 0; // boolean
5532 if( field_object && field_object->classname ) {
5533 value = oilsFMGetString(
5535 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5537 } else if( field_object && JSON_BOOL == field_object->type ) {
5538 if( jsonBoolIsTrue( field_object ) )
5539 value = strdup( "t" );
5541 value = strdup( "f" );
5543 value = jsonObjectToSimpleString( field_object );
5544 if( field_object && JSON_NUMBER == field_object->type )
5545 value_is_numeric = 1;
5548 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5549 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5551 if( !field_object || field_object->type == JSON_NULL ) {
5552 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5553 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5557 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5558 buffer_fadd( sql, " %s = NULL", field_name );
5561 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5565 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5567 const char* numtype = get_datatype( field_def );
5568 if( !strncmp( numtype, "INT", 3 ) ) {
5569 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5570 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5571 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5573 // Must really be intended as a string, so quote it
5574 if( dbi_conn_quote_string( dbhandle, &value )) {
5575 buffer_fadd( sql, " %s = %s", field_name, value );
5577 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5578 modulename, value );
5579 osrfAppSessionStatus(
5581 OSRF_STATUS_INTERNALSERVERERROR,
5582 "osrfMethodException",
5584 "Error quoting string -- please see the error log for more details"
5588 osrfHashIteratorFree( field_itr );
5590 osrfAppRespondComplete( ctx, NULL );
5595 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5598 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5602 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5603 buffer_fadd( sql, " %s = %s", field_name, value );
5605 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5606 osrfAppSessionStatus(
5608 OSRF_STATUS_INTERNALSERVERERROR,
5609 "osrfMethodException",
5611 "Error quoting string -- please see the error log for more details"
5615 osrfHashIteratorFree( field_itr );
5617 osrfAppRespondComplete( ctx, NULL );
5626 osrfHashIteratorFree( field_itr );
5628 jsonObject* obj = jsonNewObject( id );
5630 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5631 dbi_conn_quote_string( dbhandle, &id );
5633 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5635 char* query = buffer_release( sql );
5636 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5638 dbi_result result = dbi_conn_query( dbhandle, query );
5642 jsonObjectFree( obj );
5643 obj = jsonNewObject( NULL );
5646 "%s ERROR updating %s object with %s = %s",
5648 osrfHashGet( meta, "fieldmapper" ),
5655 osrfAppRespondComplete( ctx, obj );
5656 jsonObjectFree( obj );
5660 int doDelete( osrfMethodContext* ctx ) {
5661 if( osrfMethodVerifyContext( ctx )) {
5662 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5667 timeout_needs_resetting = 1;
5669 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5671 if( getXactId( ctx ) == NULL ) {
5672 osrfAppSessionStatus(
5674 OSRF_STATUS_BADREQUEST,
5675 "osrfMethodException",
5677 "No active transaction -- required for DELETE"
5679 osrfAppRespondComplete( ctx, NULL );
5683 // The following test is harmless but redundant. If a class is
5684 // readonly, we don't register a delete method for it.
5685 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5686 osrfAppSessionStatus(
5688 OSRF_STATUS_BADREQUEST,
5689 "osrfMethodException",
5691 "Cannot DELETE readonly class"
5693 osrfAppRespondComplete( ctx, NULL );
5697 dbhandle = writehandle;
5699 char* pkey = osrfHashGet( meta, "primarykey" );
5706 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5707 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5708 osrfAppRespondComplete( ctx, NULL );
5712 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5714 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5715 osrfAppRespondComplete( ctx, NULL );
5718 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5723 "%s deleting %s object with %s = %s",
5725 osrfHashGet( meta, "fieldmapper" ),
5730 jsonObject* obj = jsonNewObject( id );
5732 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5733 dbi_conn_quote_string( writehandle, &id );
5735 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
5736 osrfHashGet( meta, "tablename" ), pkey, id );
5739 jsonObjectFree( obj );
5740 obj = jsonNewObject( NULL );
5743 "%s ERROR deleting %s object with %s = %s",
5745 osrfHashGet( meta, "fieldmapper" ),
5753 osrfAppRespondComplete( ctx, obj );
5754 jsonObjectFree( obj );
5759 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5760 @param result An iterator for a result set; we only look at the current row.
5761 @param @meta Pointer to the class metadata for the core class.
5762 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5764 If a column is not defined in the IDL, or if it has no array_position defined for it in
5765 the IDL, or if it is defined as virtual, ignore it.
5767 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5768 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5769 array_position in the IDL.
5771 A field defined in the IDL but not represented in the returned row will leave a hole
5772 in the JSON_ARRAY. In effect it will be treated as a null value.
5774 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5775 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5776 classname corresponding to the @a meta argument.
5778 The calling code is responsible for freeing the the resulting jsonObject by calling
5781 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5782 if( !( result && meta )) return NULL;
5784 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5785 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
5786 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
5788 osrfHash* fields = osrfHashGet( meta, "fields" );
5790 int columnIndex = 1;
5791 const char* columnName;
5793 /* cycle through the columns in the row returned from the database */
5794 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
5796 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5798 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5800 /* determine the field type and storage attributes */
5801 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
5802 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5804 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5805 // or if it has no sequence number there, or if it's virtual, skip it.
5806 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
5809 if( str_is_true( osrfHashGet( _f, "virtual" )))
5810 continue; // skip this column: IDL says it's virtual
5812 const char* pos = (char*) osrfHashGet( _f, "array_position" );
5813 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
5814 continue; // since we assign sequence numbers dynamically as we load the IDL.
5816 fmIndex = atoi( pos );
5817 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
5819 continue; // This field is not defined in the IDL
5822 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
5823 // sequence number from the IDL (which is likely to be different from the sequence
5824 // of columns in the SELECT clause).
5825 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5826 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
5831 case DBI_TYPE_INTEGER :
5833 if( attr & DBI_INTEGER_SIZE8 )
5834 jsonObjectSetIndex( object, fmIndex,
5835 jsonNewNumberObject(
5836 dbi_result_get_longlong_idx( result, columnIndex )));
5838 jsonObjectSetIndex( object, fmIndex,
5839 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
5843 case DBI_TYPE_DECIMAL :
5844 jsonObjectSetIndex( object, fmIndex,
5845 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
5848 case DBI_TYPE_STRING :
5853 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
5858 case DBI_TYPE_DATETIME : {
5860 char dt_string[ 256 ] = "";
5863 // Fetch the date column as a time_t
5864 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
5866 // Translate the time_t to a human-readable string
5867 if( !( attr & DBI_DATETIME_DATE )) {
5868 gmtime_r( &_tmp_dt, &gmdt );
5869 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
5870 } else if( !( attr & DBI_DATETIME_TIME )) {
5871 localtime_r( &_tmp_dt, &gmdt );
5872 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
5874 localtime_r( &_tmp_dt, &gmdt );
5875 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
5878 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
5882 case DBI_TYPE_BINARY :
5883 osrfLogError( OSRF_LOG_MARK,
5884 "Can't do binary at column %s : index %d", columnName, columnIndex );
5893 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5894 if( !result ) return NULL;
5896 jsonObject* object = jsonNewObject( NULL );
5899 char dt_string[ 256 ];
5903 int columnIndex = 1;
5905 unsigned short type;
5906 const char* columnName;
5908 /* cycle through the column list */
5909 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
5911 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5913 fmIndex = -1; // reset the position
5915 /* determine the field type and storage attributes */
5916 type = dbi_result_get_field_type_idx( result, columnIndex );
5917 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5919 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5920 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
5925 case DBI_TYPE_INTEGER :
5927 if( attr & DBI_INTEGER_SIZE8 )
5928 jsonObjectSetKey( object, columnName,
5929 jsonNewNumberObject( dbi_result_get_longlong_idx(
5930 result, columnIndex )) );
5932 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
5933 dbi_result_get_int_idx( result, columnIndex )) );
5936 case DBI_TYPE_DECIMAL :
5937 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
5938 dbi_result_get_double_idx( result, columnIndex )) );
5941 case DBI_TYPE_STRING :
5942 jsonObjectSetKey( object, columnName,
5943 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
5946 case DBI_TYPE_DATETIME :
5948 memset( dt_string, '\0', sizeof( dt_string ));
5949 memset( &gmdt, '\0', sizeof( gmdt ));
5951 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
5953 if( !( attr & DBI_DATETIME_DATE )) {
5954 gmtime_r( &_tmp_dt, &gmdt );
5955 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
5956 } else if( !( attr & DBI_DATETIME_TIME )) {
5957 localtime_r( &_tmp_dt, &gmdt );
5958 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
5960 localtime_r( &_tmp_dt, &gmdt );
5961 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
5964 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
5967 case DBI_TYPE_BINARY :
5968 osrfLogError( OSRF_LOG_MARK,
5969 "Can't do binary at column %s : index %d", columnName, columnIndex );
5973 } // end while loop traversing result
5978 // Interpret a string as true or false
5979 int str_is_true( const char* str ) {
5980 if( NULL == str || strcasecmp( str, "true" ) )
5986 // Interpret a jsonObject as true or false
5987 static int obj_is_true( const jsonObject* obj ) {
5990 else switch( obj->type )
5998 if( strcasecmp( obj->value.s, "true" ) )
6002 case JSON_NUMBER : // Support 1/0 for perl's sake
6003 if( jsonObjectGetNumber( obj ) == 1.0 )
6012 // Translate a numeric code into a text string identifying a type of
6013 // jsonObject. To be used for building error messages.
6014 static const char* json_type( int code ) {
6020 return "JSON_ARRAY";
6022 return "JSON_STRING";
6024 return "JSON_NUMBER";
6030 return "(unrecognized)";
6034 // Extract the "primitive" attribute from an IDL field definition.
6035 // If we haven't initialized the app, then we must be running in
6036 // some kind of testbed. In that case, default to "string".
6037 static const char* get_primitive( osrfHash* field ) {
6038 const char* s = osrfHashGet( field, "primitive" );
6040 if( child_initialized )
6043 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6045 osrfHashGet( field, "name" )
6053 // Extract the "datatype" attribute from an IDL field definition.
6054 // If we haven't initialized the app, then we must be running in
6055 // some kind of testbed. In that case, default to to NUMERIC,
6056 // since we look at the datatype only for numbers.
6057 static const char* get_datatype( osrfHash* field ) {
6058 const char* s = osrfHashGet( field, "datatype" );
6060 if( child_initialized )
6063 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6065 osrfHashGet( field, "name" )
6074 @brief Determine whether a string is potentially a valid SQL identifier.
6075 @param s The identifier to be tested.
6076 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6078 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6079 need to follow all the rules exactly, such as requiring that the first character not
6082 We allow leading and trailing white space. In between, we do not allow punctuation
6083 (except for underscores and dollar signs), control characters, or embedded white space.
6085 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6086 for the foreseeable future such quoted identifiers are not likely to be an issue.
6088 static int is_identifier( const char* s) {
6092 // Skip leading white space
6093 while( isspace( (unsigned char) *s ) )
6097 return 0; // Nothing but white space? Not okay.
6099 // Check each character until we reach white space or
6100 // end-of-string. Letters, digits, underscores, and
6101 // dollar signs are okay. With the exception of periods
6102 // (as in schema.identifier), control characters and other
6103 // punctuation characters are not okay. Anything else
6104 // is okay -- it could for example be part of a multibyte
6105 // UTF8 character such as a letter with diacritical marks,
6106 // and those are allowed.
6108 if( isalnum( (unsigned char) *s )
6112 ; // Fine; keep going
6113 else if( ispunct( (unsigned char) *s )
6114 || iscntrl( (unsigned char) *s ) )
6117 } while( *s && ! isspace( (unsigned char) *s ) );
6119 // If we found any white space in the above loop,
6120 // the rest had better be all white space.
6122 while( isspace( (unsigned char) *s ) )
6126 return 0; // White space was embedded within non-white space
6132 @brief Determine whether to accept a character string as a comparison operator.
6133 @param op The candidate comparison operator.
6134 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6136 We don't validate the operator for real. We just make sure that it doesn't contain
6137 any semicolons or white space (with special exceptions for a few specific operators).
6138 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6139 space but it's still not a valid operator, then the database will complain.
6141 Another approach would be to compare the string against a short list of approved operators.
6142 We don't do that because we want to allow custom operators like ">100*", which at this
6143 writing would be difficult or impossible to express otherwise in a JSON query.
6145 static int is_good_operator( const char* op ) {
6146 if( !op ) return 0; // Sanity check
6150 if( isspace( (unsigned char) *s ) ) {
6151 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6152 // and IS NOT DISTINCT FROM.
6153 if( !strcasecmp( op, "similar to" ) )
6155 else if( !strcasecmp( op, "is distinct from" ) )
6157 else if( !strcasecmp( op, "is not distinct from" ) )
6162 else if( ';' == *s )
6170 @name Query Frame Management
6172 The following machinery supports a stack of query frames for use by SELECT().
6174 A query frame caches information about one level of a SELECT query. When we enter
6175 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6177 The query frame stores information about the core class, and about any joined classes
6180 The main purpose is to map table aliases to classes and tables, so that a query can
6181 join to the same table more than once. A secondary goal is to reduce the number of
6182 lookups in the IDL by caching the results.
6186 #define STATIC_CLASS_INFO_COUNT 3
6188 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6191 @brief Allocate a ClassInfo as raw memory.
6192 @return Pointer to the newly allocated ClassInfo.
6194 Except for the in_use flag, which is used only by the allocation and deallocation
6195 logic, we don't initialize the ClassInfo here.
6197 static ClassInfo* allocate_class_info( void ) {
6198 // In order to reduce the number of mallocs and frees, we return a static
6199 // instance of ClassInfo, if we can find one that we're not already using.
6200 // We rely on the fact that the compiler will implicitly initialize the
6201 // static instances so that in_use == 0.
6204 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6205 if( ! static_class_info[ i ].in_use ) {
6206 static_class_info[ i ].in_use = 1;
6207 return static_class_info + i;
6211 // The static ones are all in use. Malloc one.
6213 return safe_malloc( sizeof( ClassInfo ) );
6217 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6218 @param info Pointer to the ClassInfo to be cleared.
6220 static void clear_class_info( ClassInfo* info ) {
6225 // Free any malloc'd strings
6227 if( info->alias != info->alias_store )
6228 free( info->alias );
6230 if( info->class_name != info->class_name_store )
6231 free( info->class_name );
6233 free( info->source_def );
6235 info->alias = info->class_name = info->source_def = NULL;
6240 @brief Free a ClassInfo and everything it owns.
6241 @param info Pointer to the ClassInfo to be freed.
6243 static void free_class_info( ClassInfo* info ) {
6248 clear_class_info( info );
6250 // If it's one of the static instances, just mark it as not in use
6253 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6254 if( info == static_class_info + i ) {
6255 static_class_info[ i ].in_use = 0;
6260 // Otherwise it must have been malloc'd, so free it
6266 @brief Populate an already-allocated ClassInfo.
6267 @param info Pointer to the ClassInfo to be populated.
6268 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6270 @param class Name of the class.
6271 @return Zero if successful, or 1 if not.
6273 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6274 the relevant portions of the IDL for the specified class.
6276 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6279 osrfLogError( OSRF_LOG_MARK,
6280 "%s ERROR: No ClassInfo available to populate", modulename );
6281 info->alias = info->class_name = info->source_def = NULL;
6282 info->class_def = info->fields = info->links = NULL;
6287 osrfLogError( OSRF_LOG_MARK,
6288 "%s ERROR: No class name provided for lookup", modulename );
6289 info->alias = info->class_name = info->source_def = NULL;
6290 info->class_def = info->fields = info->links = NULL;
6294 // Alias defaults to class name if not supplied
6295 if( ! alias || ! alias[ 0 ] )
6298 // Look up class info in the IDL
6299 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6301 osrfLogError( OSRF_LOG_MARK,
6302 "%s ERROR: Class %s not defined in IDL", modulename, class );
6303 info->alias = info->class_name = info->source_def = NULL;
6304 info->class_def = info->fields = info->links = NULL;
6306 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6307 osrfLogError( OSRF_LOG_MARK,
6308 "%s ERROR: Class %s is defined as virtual", modulename, class );
6309 info->alias = info->class_name = info->source_def = NULL;
6310 info->class_def = info->fields = info->links = NULL;
6314 osrfHash* links = osrfHashGet( class_def, "links" );
6316 osrfLogError( OSRF_LOG_MARK,
6317 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6318 info->alias = info->class_name = info->source_def = NULL;
6319 info->class_def = info->fields = info->links = NULL;
6323 osrfHash* fields = osrfHashGet( class_def, "fields" );
6325 osrfLogError( OSRF_LOG_MARK,
6326 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6327 info->alias = info->class_name = info->source_def = NULL;
6328 info->class_def = info->fields = info->links = NULL;
6332 char* source_def = oilsGetRelation( class_def );
6336 // We got everything we need, so populate the ClassInfo
6337 if( strlen( alias ) > ALIAS_STORE_SIZE )
6338 info->alias = strdup( alias );
6340 strcpy( info->alias_store, alias );
6341 info->alias = info->alias_store;
6344 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6345 info->class_name = strdup( class );
6347 strcpy( info->class_name_store, class );
6348 info->class_name = info->class_name_store;
6351 info->source_def = source_def;
6353 info->class_def = class_def;
6354 info->links = links;
6355 info->fields = fields;
6360 #define STATIC_FRAME_COUNT 3
6362 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6365 @brief Allocate a QueryFrame as raw memory.
6366 @return Pointer to the newly allocated QueryFrame.
6368 Except for the in_use flag, which is used only by the allocation and deallocation
6369 logic, we don't initialize the QueryFrame here.
6371 static QueryFrame* allocate_frame( void ) {
6372 // In order to reduce the number of mallocs and frees, we return a static
6373 // instance of QueryFrame, if we can find one that we're not already using.
6374 // We rely on the fact that the compiler will implicitly initialize the
6375 // static instances so that in_use == 0.
6378 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6379 if( ! static_frame[ i ].in_use ) {
6380 static_frame[ i ].in_use = 1;
6381 return static_frame + i;
6385 // The static ones are all in use. Malloc one.
6387 return safe_malloc( sizeof( QueryFrame ) );
6391 @brief Free a QueryFrame, and all the memory it owns.
6392 @param frame Pointer to the QueryFrame to be freed.
6394 static void free_query_frame( QueryFrame* frame ) {
6399 clear_class_info( &frame->core );
6401 // Free the join list
6403 ClassInfo* info = frame->join_list;
6406 free_class_info( info );
6410 frame->join_list = NULL;
6413 // If the frame is a static instance, just mark it as unused
6415 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6416 if( frame == static_frame + i ) {
6417 static_frame[ i ].in_use = 0;
6422 // Otherwise it must have been malloc'd, so free it
6428 @brief Search a given QueryFrame for a specified alias.
6429 @param frame Pointer to the QueryFrame to be searched.
6430 @param target The alias for which to search.
6431 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6433 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6434 if( ! frame || ! target ) {
6438 ClassInfo* found_class = NULL;
6440 if( !strcmp( target, frame->core.alias ) )
6441 return &(frame->core);
6443 ClassInfo* curr_class = frame->join_list;
6444 while( curr_class ) {
6445 if( strcmp( target, curr_class->alias ) )
6446 curr_class = curr_class->next;
6448 found_class = curr_class;
6458 @brief Push a new (blank) QueryFrame onto the stack.
6460 static void push_query_frame( void ) {
6461 QueryFrame* frame = allocate_frame();
6462 frame->join_list = NULL;
6463 frame->next = curr_query;
6465 // Initialize the ClassInfo for the core class
6466 ClassInfo* core = &frame->core;
6467 core->alias = core->class_name = core->source_def = NULL;
6468 core->class_def = core->fields = core->links = NULL;
6474 @brief Pop a QueryFrame off the stack and destroy it.
6476 static void pop_query_frame( void ) {
6481 QueryFrame* popped = curr_query;
6482 curr_query = popped->next;
6484 free_query_frame( popped );
6488 @brief Populate the ClassInfo for the core class.
6489 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6490 class name as an alias.
6491 @param class_name Name of the core class.
6492 @return Zero if successful, or 1 if not.
6494 Populate the ClassInfo of the core class with copies of the alias and class name, and
6495 with pointers to the relevant portions of the IDL for the core class.
6497 static int add_query_core( const char* alias, const char* class_name ) {
6500 if( ! curr_query ) {
6501 osrfLogError( OSRF_LOG_MARK,
6502 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6504 } else if( curr_query->core.alias ) {
6505 osrfLogError( OSRF_LOG_MARK,
6506 "%s ERROR: Core class %s already populated as %s",
6507 modulename, curr_query->core.class_name, curr_query->core.alias );
6511 build_class_info( &curr_query->core, alias, class_name );
6512 if( curr_query->core.alias )
6515 osrfLogError( OSRF_LOG_MARK,
6516 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6522 @brief Search the current QueryFrame for a specified alias.
6523 @param target The alias for which to search.
6524 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6526 static inline ClassInfo* search_alias( const char* target ) {
6527 return search_alias_in_frame( curr_query, target );
6531 @brief Search all levels of query for a specified alias, starting with the current query.
6532 @param target The alias for which to search.
6533 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6535 static ClassInfo* search_all_alias( const char* target ) {
6536 ClassInfo* found_class = NULL;
6537 QueryFrame* curr_frame = curr_query;
6539 while( curr_frame ) {
6540 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6543 curr_frame = curr_frame->next;
6550 @brief Add a class to the list of classes joined to the current query.
6551 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6552 the class name as an alias.
6553 @param classname The name of the class to be added.
6554 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6556 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6558 if( ! classname || ! *classname ) { // sanity check
6559 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6566 const ClassInfo* conflict = search_alias( alias );
6568 osrfLogError( OSRF_LOG_MARK,
6569 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6570 modulename, alias, conflict->class_name );
6574 ClassInfo* info = allocate_class_info();
6576 if( build_class_info( info, alias, classname ) ) {
6577 free_class_info( info );
6581 // Add the new ClassInfo to the join list of the current QueryFrame
6582 info->next = curr_query->join_list;
6583 curr_query->join_list = info;
6589 @brief Destroy all nodes on the query stack.
6591 static void clear_query_stack( void ) {