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 const 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" );
1270 // Set fetch to 1 in all cases, meaning that for local or foreign contexts we will
1271 // always do another lookup of the current row, even if we already have a row image,
1272 // because the row image in hand may not include the foreign key(s) that we need.
1274 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1275 // but they aren't implemented yet.
1278 if( *method_type == 's' || *method_type == 'i' ) {
1279 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1280 } else if( *method_type == 'u' || *method_type == 'd' ) {
1281 fetch = 1; // MUST go to the db for the object for update and delete
1284 // Get the appropriate permacrud entry from the IDL, depending on method type
1285 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1287 // No permacrud for this method type on this class
1289 growing_buffer* msg = buffer_init( 128 );
1292 "%s: %s on class %s has no permacrud IDL entry",
1294 osrfHashGet( method_metadata, "methodtype" ),
1295 osrfHashGet( class, "classname" )
1298 char* m = buffer_release( msg );
1299 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1300 "osrfMethodException", ctx->request, m );
1307 // Get the user id, and make sure the user is logged in
1308 const jsonObject* user = verifyUserPCRUD( ctx );
1310 return 0; // Not logged in? No access.
1312 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1314 // Get a list of permissions from the permacrud entry.
1315 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1317 // Build a list of org units that own the row. This is fairly convoluted because there
1318 // are several different ways that an org unit may own the row, as defined by the
1321 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1322 // identifying an owning org_unit..
1323 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1325 // Foreign context adds a layer of indirection. The row points to some other row that
1326 // an org unit may own. The "jump" attribute, if present, adds another layer of
1328 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1330 // The following string array stores the list of org units. (We don't have a thingie
1331 // for storing lists of integers, so we fake it with a list of strings.)
1332 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1335 const char* pkey_value = NULL;
1336 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1337 // If the global_required attribute is present and true, then the only owning
1338 // org unit is the root org unit, i.e. the one with no parent.
1339 osrfLogDebug( OSRF_LOG_MARK,
1340 "global-level permissions required, fetching top of the org tree" );
1342 // check for perm at top of org tree
1343 const char* org_tree_root_id = org_tree_root( ctx );
1344 if( org_tree_root_id ) {
1345 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1346 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1348 osrfStringArrayFree( context_org_array );
1353 // If the global_required attribute is absent or false, then we look for
1354 // local and/or foreign context. In order to find the relevant foreign
1355 // keys, we must either read the relevant row from the database, or look at
1356 // the image of the row that we already have in memory.
1358 // (Herein lies a bug. Even if we have an image of the row in memory, that
1359 // image may not include the foreign key column(s) that we need.)
1361 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1362 "fetching context org ids" );
1363 const char* pkey = osrfHashGet( class, "primarykey" );
1364 jsonObject *param = NULL;
1366 if( obj->classname ) {
1367 pkey_value = oilsFMGetStringConst( obj, pkey );
1369 param = jsonObjectClone( obj );
1370 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1373 pkey_value = jsonObjectGetString( obj );
1375 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1376 "of %s and retrieving from the database", pkey_value );
1380 // Fetch the row so that we can look at the foreign key(s)
1381 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1382 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1383 jsonObjectFree( _tmp_params );
1385 param = jsonObjectExtractIndex( _list, 0 );
1386 jsonObjectFree( _list );
1390 // The row doesn't exist. Complain, and deny access.
1391 osrfLogDebug( OSRF_LOG_MARK,
1392 "Object not found in the database with primary key %s of %s",
1395 growing_buffer* msg = buffer_init( 128 );
1398 "%s: no object found with primary key %s of %s",
1404 char* m = buffer_release( msg );
1405 osrfAppSessionStatus(
1407 OSRF_STATUS_INTERNALSERVERERROR,
1408 "osrfMethodException",
1417 if( local_context && local_context->size > 0 ) {
1418 // The IDL provides a list of column names for the foreign keys denoting
1419 // local context. Look up the value of each one, and if it isn't null,
1420 // add it to the list of org units.
1421 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1422 local_context->size );
1424 const char* lcontext = NULL;
1425 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1426 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1427 if( fkey_value ) { // if not null
1428 osrfStringArrayAdd( context_org_array, fkey_value );
1431 "adding class-local field %s (value: %s) to the context org list",
1433 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1439 if( foreign_context ) {
1440 unsigned long class_count = osrfHashGetCount( foreign_context );
1441 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1443 if( class_count > 0 ) {
1445 // The IDL provides a list of foreign key columns pointing to rows that
1446 // an org unit may own. Follow each link, identify the owning org unit,
1447 // and add it to the list.
1448 osrfHash* fcontext = NULL;
1449 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1450 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1451 // For each linked class...
1452 const char* class_name = osrfHashIteratorKey( class_itr );
1453 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1457 "%d foreign context fields(s) specified for class %s",
1458 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1462 // Get the name of the key field in the foreign table
1463 char* foreign_pkey = osrfHashGet( fcontext, "field" );
1465 // Get the value of the foreign key pointing to the foreign table
1466 char* foreign_pkey_value =
1467 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1468 if( !foreign_pkey_value )
1469 continue; // Foreign key value is null; skip it
1471 // Look up the row to which the foreign key points
1472 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1473 jsonObject* _list = doFieldmapperSearch(
1474 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1476 jsonObject* _fparam = NULL;
1477 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1478 _fparam = jsonObjectExtractIndex( _list, 0 );
1480 jsonObjectFree( _tmp_params );
1481 jsonObjectFree( _list );
1483 // At this point _fparam either points to the row identified by the
1484 // foreign key, or it's NULL (no such row found).
1486 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1488 if( _fparam && jump_list ) {
1489 // Follow another level of indirection to find one or more owners
1490 const char* flink = NULL;
1492 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1493 // For each entry in the jump list. Each entry is the name of a
1494 // foreign key colum in the foreign table.
1496 osrfHash* foreign_link_hash =
1497 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1499 free( foreign_pkey_value );
1500 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1501 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1503 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1505 _list = doFieldmapperSearch(
1507 osrfHashGet( oilsIDL(),
1508 osrfHashGet( foreign_link_hash, "class" ) ),
1514 jsonObjectFree( _fparam );
1516 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1517 _fparam = jsonObjectExtractIndex( _list, 0 );
1518 jsonObjectFree( _tmp_params );
1519 jsonObjectFree( _list );
1525 growing_buffer* msg = buffer_init( 128 );
1528 "%s: no object found with primary key %s of %s",
1531 foreign_pkey_value ? foreign_pkey_value : "(null)"
1534 char* m = buffer_release( msg );
1535 osrfAppSessionStatus(
1537 OSRF_STATUS_INTERNALSERVERERROR,
1538 "osrfMethodException",
1544 osrfHashIteratorFree( class_itr );
1545 free( foreign_pkey_value );
1546 jsonObjectFree( param );
1551 free( foreign_pkey_value );
1553 // Examine each context column of the foreign row,
1554 // and add its value to the list of org units.
1556 const char* foreign_field = NULL;
1557 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1558 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1559 osrfStringArrayAdd( context_org_array,
1560 oilsFMGetStringConst( _fparam, foreign_field ));
1563 "adding foreign class %s field %s (value: %s) to the context org list",
1566 osrfStringArrayGetString(
1567 context_org_array, context_org_array->size - 1 )
1571 jsonObjectFree( _fparam );
1574 osrfHashIteratorFree( class_itr );
1578 jsonObjectFree( param );
1581 const char* context_org = NULL;
1582 const char* perm = NULL;
1585 if( permission->size == 0 ) {
1586 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1591 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1593 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1599 "Checking object permission [%s] for user %d "
1600 "on object %s (class %s) at org %d",
1604 osrfHashGet( class, "classname" ),
1608 result = dbi_conn_queryf(
1610 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1613 osrfHashGet( class, "classname" ),
1621 "Received a result for object permission [%s] "
1622 "for user %d on object %s (class %s) at org %d",
1626 osrfHashGet( class, "classname" ),
1630 if( dbi_result_first_row( result )) {
1631 jsonObject* return_val = oilsMakeJSONFromResult( result );
1632 const char* has_perm = jsonObjectGetString(
1633 jsonObjectGetKeyConst( return_val, "has_perm" ));
1637 "Status of object permission [%s] for user %d "
1638 "on object %s (class %s) at org %d is %s",
1642 osrfHashGet(class, "classname"),
1647 if( *has_perm == 't' )
1649 jsonObjectFree( return_val );
1652 dbi_result_free( result );
1658 osrfLogDebug( OSRF_LOG_MARK,
1659 "Checking non-object permission [%s] for user %d at org %d",
1660 perm, userid, atoi(context_org) );
1661 result = dbi_conn_queryf(
1663 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1670 osrfLogDebug( OSRF_LOG_MARK,
1671 "Received a result for permission [%s] for user %d at org %d",
1672 perm, userid, atoi( context_org ));
1673 if( dbi_result_first_row( result )) {
1674 jsonObject* return_val = oilsMakeJSONFromResult( result );
1675 const char* has_perm = jsonObjectGetString(
1676 jsonObjectGetKeyConst( return_val, "has_perm" ));
1677 osrfLogDebug( OSRF_LOG_MARK,
1678 "Status of permission [%s] for user %d at org %d is [%s]",
1679 perm, userid, atoi( context_org ), has_perm );
1680 if( *has_perm == 't' )
1682 jsonObjectFree( return_val );
1685 dbi_result_free( result );
1695 osrfStringArrayFree( context_org_array );
1701 @brief Look up the root of the org_unit tree.
1702 @param ctx Pointer to the method context.
1703 @return The id of the root org unit, as a character string.
1705 Query actor.org_unit where parent_ou is null, and return the id as a string.
1707 This function assumes that there is only one root org unit, i.e. that we
1708 have a single tree, not a forest.
1710 The calling code is responsible for freeing the returned string.
1712 static const char* org_tree_root( osrfMethodContext* ctx ) {
1714 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1715 static time_t last_lookup_time = 0;
1716 time_t current_time = time( NULL );
1718 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1719 // We successfully looked this up less than an hour ago.
1720 // It's not likely to have changed since then.
1721 return strdup( cached_root_id );
1723 last_lookup_time = current_time;
1726 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1727 jsonObject* result = doFieldmapperSearch(
1728 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1729 jsonObjectFree( where_clause );
1731 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1734 jsonObjectFree( result );
1736 growing_buffer* msg = buffer_init( 128 );
1737 OSRF_BUFFER_ADD( msg, modulename );
1738 OSRF_BUFFER_ADD( msg,
1739 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1741 char* m = buffer_release( msg );
1742 osrfAppSessionStatus( ctx->session,
1743 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1746 cached_root_id[ 0 ] = '\0';
1750 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
1751 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1753 strcpy( cached_root_id, root_org_unit_id );
1754 jsonObjectFree( result );
1755 return cached_root_id;
1759 @brief Create a JSON_HASH with a single key/value pair.
1760 @param key The key of the key/value pair.
1761 @param value the value of the key/value pair.
1762 @return Pointer to a newly created jsonObject of type JSON_HASH.
1764 The value of the key/value is either a string or (if @a value is NULL) a null.
1766 static jsonObject* single_hash( const char* key, const char* value ) {
1768 if( ! key ) key = "";
1770 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1771 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1776 int doCreate( osrfMethodContext* ctx ) {
1777 if(osrfMethodVerifyContext( ctx )) {
1778 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1783 timeout_needs_resetting = 1;
1785 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1786 jsonObject* target = NULL;
1787 jsonObject* options = NULL;
1789 if( enforce_pcrud ) {
1790 target = jsonObjectGetIndex( ctx->params, 1 );
1791 options = jsonObjectGetIndex( ctx->params, 2 );
1793 target = jsonObjectGetIndex( ctx->params, 0 );
1794 options = jsonObjectGetIndex( ctx->params, 1 );
1797 if( !verifyObjectClass( ctx, target )) {
1798 osrfAppRespondComplete( ctx, NULL );
1802 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1804 const char* trans_id = getXactId( ctx );
1806 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1808 osrfAppSessionStatus(
1810 OSRF_STATUS_BADREQUEST,
1811 "osrfMethodException",
1813 "No active transaction -- required for CREATE"
1815 osrfAppRespondComplete( ctx, NULL );
1819 // The following test is harmless but redundant. If a class is
1820 // readonly, we don't register a create method for it.
1821 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1822 osrfAppSessionStatus(
1824 OSRF_STATUS_BADREQUEST,
1825 "osrfMethodException",
1827 "Cannot INSERT readonly class"
1829 osrfAppRespondComplete( ctx, NULL );
1833 // Set the last_xact_id
1834 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1836 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1837 trans_id, target->classname, index);
1838 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1841 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1843 dbhandle = writehandle;
1845 osrfHash* fields = osrfHashGet( meta, "fields" );
1846 char* pkey = osrfHashGet( meta, "primarykey" );
1847 char* seq = osrfHashGet( meta, "sequence" );
1849 growing_buffer* table_buf = buffer_init( 128 );
1850 growing_buffer* col_buf = buffer_init( 128 );
1851 growing_buffer* val_buf = buffer_init( 128 );
1853 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1854 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1855 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1856 buffer_add( val_buf,"VALUES (" );
1860 osrfHash* field = NULL;
1861 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1862 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1864 const char* field_name = osrfHashIteratorKey( field_itr );
1866 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1869 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1872 if( field_object && field_object->classname ) {
1873 value = oilsFMGetString(
1875 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
1877 } else if( field_object && JSON_BOOL == field_object->type ) {
1878 if( jsonBoolIsTrue( field_object ) )
1879 value = strdup( "t" );
1881 value = strdup( "f" );
1883 value = jsonObjectToSimpleString( field_object );
1889 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1890 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1893 buffer_add( col_buf, field_name );
1895 if( !field_object || field_object->type == JSON_NULL ) {
1896 buffer_add( val_buf, "DEFAULT" );
1898 } else if( !strcmp( get_primitive( field ), "number" )) {
1899 const char* numtype = get_datatype( field );
1900 if( !strcmp( numtype, "INT8" )) {
1901 buffer_fadd( val_buf, "%lld", atoll( value ));
1903 } else if( !strcmp( numtype, "INT" )) {
1904 buffer_fadd( val_buf, "%d", atoi( value ));
1906 } else if( !strcmp( numtype, "NUMERIC" )) {
1907 buffer_fadd( val_buf, "%f", atof( value ));
1910 if( dbi_conn_quote_string( writehandle, &value )) {
1911 OSRF_BUFFER_ADD( val_buf, value );
1914 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
1915 osrfAppSessionStatus(
1917 OSRF_STATUS_INTERNALSERVERERROR,
1918 "osrfMethodException",
1920 "Error quoting string -- please see the error log for more details"
1923 buffer_free( table_buf );
1924 buffer_free( col_buf );
1925 buffer_free( val_buf );
1926 osrfAppRespondComplete( ctx, NULL );
1934 osrfHashIteratorFree( field_itr );
1936 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1937 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1939 char* table_str = buffer_release( table_buf );
1940 char* col_str = buffer_release( col_buf );
1941 char* val_str = buffer_release( val_buf );
1942 growing_buffer* sql = buffer_init( 128 );
1943 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1948 char* query = buffer_release( sql );
1950 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
1952 jsonObject* obj = NULL;
1955 dbi_result result = dbi_conn_query( writehandle, query );
1957 obj = jsonNewObject( NULL );
1960 "%s ERROR inserting %s object using query [%s]",
1962 osrfHashGet(meta, "fieldmapper"),
1965 osrfAppSessionStatus(
1967 OSRF_STATUS_INTERNALSERVERERROR,
1968 "osrfMethodException",
1970 "INSERT error -- please see the error log for more details"
1975 char* id = oilsFMGetString( target, pkey );
1977 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
1978 growing_buffer* _id = buffer_init( 10 );
1979 buffer_fadd( _id, "%lld", new_id );
1980 id = buffer_release( _id );
1983 // Find quietness specification, if present
1984 const char* quiet_str = NULL;
1986 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1988 quiet_str = jsonObjectGetString( quiet_obj );
1991 if( str_is_true( quiet_str )) { // if quietness is specified
1992 obj = jsonNewObject( id );
1996 // Fetch the row that we just inserted, so that we can return it to the client
1997 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1998 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2001 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2005 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2007 jsonObjectFree( list );
2008 jsonObjectFree( where_clause );
2015 osrfAppRespondComplete( ctx, obj );
2016 jsonObjectFree( obj );
2021 @brief Implement the retrieve method.
2022 @param ctx Pointer to the method context.
2023 @param err Pointer through which to return an error code.
2024 @return If successful, a pointer to the result to be returned to the client;
2027 From the method's class, fetch a row with a specified value in the primary key. This
2028 method relies on the database design convention that a primary key consists of a single
2032 - authkey (PCRUD only)
2033 - value of the primary key for the desired row, for building the WHERE clause
2034 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2036 Return to client: One row from the query.
2038 int doRetrieve( osrfMethodContext* ctx ) {
2039 if(osrfMethodVerifyContext( ctx )) {
2040 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2045 timeout_needs_resetting = 1;
2050 if( enforce_pcrud ) {
2055 // Get the class metadata
2056 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2058 // Get the value of the primary key, from a method parameter
2059 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2063 "%s retrieving %s object with primary key value of %s",
2065 osrfHashGet( class_def, "fieldmapper" ),
2066 jsonObjectGetString( id_obj )
2069 // Build a WHERE clause based on the key value
2070 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2073 osrfHashGet( class_def, "primarykey" ), // name of key column
2074 jsonObjectClone( id_obj ) // value of key column
2077 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2081 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2083 jsonObjectFree( where_clause );
2085 osrfAppRespondComplete( ctx, NULL );
2089 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2090 jsonObjectFree( list );
2092 if( enforce_pcrud ) {
2093 if(!verifyObjectPCRUD( ctx, obj )) {
2094 jsonObjectFree( obj );
2096 growing_buffer* msg = buffer_init( 128 );
2097 OSRF_BUFFER_ADD( msg, modulename );
2098 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2100 char* m = buffer_release( msg );
2101 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2105 osrfAppRespondComplete( ctx, NULL );
2110 osrfAppRespondComplete( ctx, obj );
2111 jsonObjectFree( obj );
2116 @brief Translate a numeric value to a string representation for the database.
2117 @param field Pointer to the IDL field definition.
2118 @param value Pointer to a jsonObject holding the value of a field.
2119 @return Pointer to a newly allocated string.
2121 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2122 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2123 or (what is worse) valid SQL that is wrong.
2125 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2127 The calling code is responsible for freeing the resulting string by calling free().
2129 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2130 growing_buffer* val_buf = buffer_init( 32 );
2131 const char* numtype = get_datatype( field );
2133 // For historical reasons the following contains cruft that could be cleaned up.
2134 if( !strncmp( numtype, "INT", 3 ) ) {
2135 if( value->type == JSON_NUMBER )
2136 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2137 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2139 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2142 } else if( !strcmp( numtype, "NUMERIC" )) {
2143 if( value->type == JSON_NUMBER )
2144 buffer_fadd( val_buf, jsonObjectGetString( value ));
2146 buffer_fadd( val_buf, jsonObjectGetString( value ));
2150 // Presumably this was really intended to be a string, so quote it
2151 char* str = jsonObjectToSimpleString( value );
2152 if( dbi_conn_quote_string( dbhandle, &str )) {
2153 OSRF_BUFFER_ADD( val_buf, str );
2156 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2158 buffer_free( val_buf );
2163 return buffer_release( val_buf );
2166 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2167 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2168 growing_buffer* sql_buf = buffer_init( 32 );
2174 osrfHashGet( field, "name" )
2178 buffer_add( sql_buf, "IN (" );
2179 } else if( !strcasecmp( op,"not in" )) {
2180 buffer_add( sql_buf, "NOT IN (" );
2182 buffer_add( sql_buf, "IN (" );
2185 if( node->type == JSON_HASH ) {
2186 // subquery predicate
2187 char* subpred = buildQuery( ctx, node, SUBSELECT );
2189 buffer_free( sql_buf );
2193 buffer_add( sql_buf, subpred );
2196 } else if( node->type == JSON_ARRAY ) {
2197 // literal value list
2198 int in_item_index = 0;
2199 int in_item_first = 1;
2200 const jsonObject* in_item;
2201 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2206 buffer_add( sql_buf, ", " );
2209 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2210 osrfLogError( OSRF_LOG_MARK,
2211 "%s: Expected string or number within IN list; found %s",
2212 modulename, json_type( in_item->type ) );
2213 buffer_free( sql_buf );
2217 // Append the literal value -- quoted if not a number
2218 if( JSON_NUMBER == in_item->type ) {
2219 char* val = jsonNumberToDBString( field, in_item );
2220 OSRF_BUFFER_ADD( sql_buf, val );
2223 } else if( !strcmp( get_primitive( field ), "number" )) {
2224 char* val = jsonNumberToDBString( field, in_item );
2225 OSRF_BUFFER_ADD( sql_buf, val );
2229 char* key_string = jsonObjectToSimpleString( in_item );
2230 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2231 OSRF_BUFFER_ADD( sql_buf, key_string );
2234 osrfLogError( OSRF_LOG_MARK,
2235 "%s: Error quoting key string [%s]", modulename, key_string );
2237 buffer_free( sql_buf );
2243 if( in_item_first ) {
2244 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2245 buffer_free( sql_buf );
2249 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2250 modulename, json_type( node->type ));
2251 buffer_free( sql_buf );
2255 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2257 return buffer_release( sql_buf );
2260 // Receive a JSON_ARRAY representing a function call. The first
2261 // entry in the array is the function name. The rest are parameters.
2262 static char* searchValueTransform( const jsonObject* array ) {
2264 if( array->size < 1 ) {
2265 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2269 // Get the function name
2270 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2271 if( func_item->type != JSON_STRING ) {
2272 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2273 modulename, json_type( func_item->type ));
2277 growing_buffer* sql_buf = buffer_init( 32 );
2279 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2280 OSRF_BUFFER_ADD( sql_buf, "( " );
2282 // Get the parameters
2283 int func_item_index = 1; // We already grabbed the zeroth entry
2284 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2286 // Add a separator comma, if we need one
2287 if( func_item_index > 2 )
2288 buffer_add( sql_buf, ", " );
2290 // Add the current parameter
2291 if( func_item->type == JSON_NULL ) {
2292 buffer_add( sql_buf, "NULL" );
2294 char* val = jsonObjectToSimpleString( func_item );
2295 if( dbi_conn_quote_string( dbhandle, &val )) {
2296 OSRF_BUFFER_ADD( sql_buf, val );
2299 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2301 buffer_free( sql_buf );
2308 buffer_add( sql_buf, " )" );
2310 return buffer_release( sql_buf );
2313 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2314 const jsonObject* node, const char* op ) {
2316 if( ! is_good_operator( op ) ) {
2317 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2321 char* val = searchValueTransform( node );
2325 growing_buffer* sql_buf = buffer_init( 32 );
2330 osrfHashGet( field, "name" ),
2337 return buffer_release( sql_buf );
2340 // class_alias is a class name or other table alias
2341 // field is a field definition as stored in the IDL
2342 // node comes from the method parameter, and may represent an entry in the SELECT list
2343 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2344 const jsonObject* node ) {
2345 growing_buffer* sql_buf = buffer_init( 32 );
2347 const char* field_transform = jsonObjectGetString(
2348 jsonObjectGetKeyConst( node, "transform" ) );
2349 const char* transform_subcolumn = jsonObjectGetString(
2350 jsonObjectGetKeyConst( node, "result_field" ) );
2352 if( transform_subcolumn ) {
2353 if( ! is_identifier( transform_subcolumn ) ) {
2354 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2355 modulename, transform_subcolumn );
2356 buffer_free( sql_buf );
2359 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2362 if( field_transform ) {
2364 if( ! is_identifier( field_transform ) ) {
2365 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2366 modulename, field_transform );
2367 buffer_free( sql_buf );
2371 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2372 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2373 field_transform, class_alias, osrfHashGet( field, "name" ));
2375 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2376 field_transform, class_alias, osrfHashGet( field, "name" ));
2379 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2382 if( array->type != JSON_ARRAY ) {
2383 osrfLogError( OSRF_LOG_MARK,
2384 "%s: Expected JSON_ARRAY for function params; found %s",
2385 modulename, json_type( array->type ) );
2386 buffer_free( sql_buf );
2389 int func_item_index = 0;
2390 jsonObject* func_item;
2391 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2393 char* val = jsonObjectToSimpleString( func_item );
2396 buffer_add( sql_buf, ",NULL" );
2397 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2398 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2399 OSRF_BUFFER_ADD( sql_buf, val );
2401 osrfLogError( OSRF_LOG_MARK,
2402 "%s: Error quoting key string [%s]", modulename, val );
2404 buffer_free( sql_buf );
2411 buffer_add( sql_buf, " )" );
2414 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2417 if( transform_subcolumn )
2418 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2420 return buffer_release( sql_buf );
2423 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2424 const jsonObject* node, const char* op ) {
2426 if( ! is_good_operator( op ) ) {
2427 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2431 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2432 if( ! field_transform )
2435 int extra_parens = 0; // boolean
2437 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2439 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2441 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2443 free( field_transform );
2447 } else if( value_obj->type == JSON_ARRAY ) {
2448 value = searchValueTransform( value_obj );
2450 osrfLogError( OSRF_LOG_MARK,
2451 "%s: Error building value transform for field transform", modulename );
2452 free( field_transform );
2455 } else if( value_obj->type == JSON_HASH ) {
2456 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2458 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2460 free( field_transform );
2464 } else if( value_obj->type == JSON_NUMBER ) {
2465 value = jsonNumberToDBString( field, value_obj );
2466 } else if( value_obj->type == JSON_NULL ) {
2467 osrfLogError( OSRF_LOG_MARK,
2468 "%s: Error building predicate for field transform: null value", modulename );
2469 free( field_transform );
2471 } else if( value_obj->type == JSON_BOOL ) {
2472 osrfLogError( OSRF_LOG_MARK,
2473 "%s: Error building predicate for field transform: boolean value", modulename );
2474 free( field_transform );
2477 if( !strcmp( get_primitive( field ), "number") ) {
2478 value = jsonNumberToDBString( field, value_obj );
2480 value = jsonObjectToSimpleString( value_obj );
2481 if( !dbi_conn_quote_string( dbhandle, &value )) {
2482 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2483 modulename, value );
2485 free( field_transform );
2491 const char* left_parens = "";
2492 const char* right_parens = "";
2494 if( extra_parens ) {
2499 growing_buffer* sql_buf = buffer_init( 32 );
2503 "%s%s %s %s %s %s%s",
2514 free( field_transform );
2516 return buffer_release( sql_buf );
2519 static char* searchSimplePredicate( const char* op, const char* class_alias,
2520 osrfHash* field, const jsonObject* node ) {
2522 if( ! is_good_operator( op ) ) {
2523 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2529 // Get the value to which we are comparing the specified column
2530 if( node->type != JSON_NULL ) {
2531 if( node->type == JSON_NUMBER ) {
2532 val = jsonNumberToDBString( field, node );
2533 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2534 val = jsonNumberToDBString( field, node );
2536 val = jsonObjectToSimpleString( node );
2541 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2542 // Value is not numeric; enclose it in quotes
2543 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2544 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2551 // Compare to a null value
2552 val = strdup( "NULL" );
2553 if( strcmp( op, "=" ))
2559 growing_buffer* sql_buf = buffer_init( 32 );
2560 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2561 char* pred = buffer_release( sql_buf );
2568 static char* searchBETWEENPredicate( const char* class_alias,
2569 osrfHash* field, const jsonObject* node ) {
2571 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2572 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2574 if( NULL == y_node ) {
2575 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2578 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2579 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2586 if( !strcmp( get_primitive( field ), "number") ) {
2587 x_string = jsonNumberToDBString( field, x_node );
2588 y_string = jsonNumberToDBString( field, y_node );
2591 x_string = jsonObjectToSimpleString( x_node );
2592 y_string = jsonObjectToSimpleString( y_node );
2593 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2594 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2595 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2596 modulename, x_string, y_string );
2603 growing_buffer* sql_buf = buffer_init( 32 );
2604 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2605 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2609 return buffer_release( sql_buf );
2612 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2613 jsonObject* node, osrfMethodContext* ctx ) {
2616 if( node->type == JSON_ARRAY ) { // equality IN search
2617 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2618 } else if( node->type == JSON_HASH ) { // other search
2619 jsonIterator* pred_itr = jsonNewIterator( node );
2620 if( !jsonIteratorHasNext( pred_itr ) ) {
2621 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2622 modulename, osrfHashGet(field, "name" ));
2624 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2626 // Verify that there are no additional predicates
2627 if( jsonIteratorHasNext( pred_itr ) ) {
2628 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2629 modulename, osrfHashGet(field, "name" ));
2630 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2631 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2632 else if( !(strcasecmp( pred_itr->key,"in" ))
2633 || !(strcasecmp( pred_itr->key,"not in" )) )
2634 pred = searchINPredicate(
2635 class_info->alias, field, pred_node, pred_itr->key, ctx );
2636 else if( pred_node->type == JSON_ARRAY )
2637 pred = searchFunctionPredicate(
2638 class_info->alias, field, pred_node, pred_itr->key );
2639 else if( pred_node->type == JSON_HASH )
2640 pred = searchFieldTransformPredicate(
2641 class_info, field, pred_node, pred_itr->key );
2643 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2645 jsonIteratorFree( pred_itr );
2647 } else if( node->type == JSON_NULL ) { // IS NULL search
2648 growing_buffer* _p = buffer_init( 64 );
2651 "\"%s\".%s IS NULL",
2652 class_info->class_name,
2653 osrfHashGet( field, "name" )
2655 pred = buffer_release( _p );
2656 } else { // equality search
2657 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2676 field : call_number,
2692 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2694 const jsonObject* working_hash;
2695 jsonObject* freeable_hash = NULL;
2697 if( join_hash->type == JSON_HASH ) {
2698 working_hash = join_hash;
2699 } else if( join_hash->type == JSON_STRING ) {
2700 // turn it into a JSON_HASH by creating a wrapper
2701 // around a copy of the original
2702 const char* _tmp = jsonObjectGetString( join_hash );
2703 freeable_hash = jsonNewObjectType( JSON_HASH );
2704 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2705 working_hash = freeable_hash;
2709 "%s: JOIN failed; expected JSON object type not found",
2715 growing_buffer* join_buf = buffer_init( 128 );
2716 const char* leftclass = left_info->class_name;
2718 jsonObject* snode = NULL;
2719 jsonIterator* search_itr = jsonNewIterator( working_hash );
2721 while ( (snode = jsonIteratorNext( search_itr )) ) {
2722 const char* right_alias = search_itr->key;
2724 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2726 class = right_alias;
2728 const ClassInfo* right_info = add_joined_class( right_alias, class );
2732 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2736 jsonIteratorFree( search_itr );
2737 buffer_free( join_buf );
2739 jsonObjectFree( freeable_hash );
2742 osrfHash* links = right_info->links;
2743 const char* table = right_info->source_def;
2745 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2746 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2748 if( field && !fkey ) {
2749 // Look up the corresponding join column in the IDL.
2750 // The link must be defined in the child table,
2751 // and point to the right parent table.
2752 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2753 const char* reltype = NULL;
2754 const char* other_class = NULL;
2755 reltype = osrfHashGet( idl_link, "reltype" );
2756 if( reltype && strcmp( reltype, "has_many" ) )
2757 other_class = osrfHashGet( idl_link, "class" );
2758 if( other_class && !strcmp( other_class, leftclass ) )
2759 fkey = osrfHashGet( idl_link, "key" );
2763 "%s: JOIN failed. No link defined from %s.%s to %s",
2769 buffer_free( join_buf );
2771 jsonObjectFree( freeable_hash );
2772 jsonIteratorFree( search_itr );
2776 } else if( !field && fkey ) {
2777 // Look up the corresponding join column in the IDL.
2778 // The link must be defined in the child table,
2779 // and point to the right parent table.
2780 osrfHash* left_links = left_info->links;
2781 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2782 const char* reltype = NULL;
2783 const char* other_class = NULL;
2784 reltype = osrfHashGet( idl_link, "reltype" );
2785 if( reltype && strcmp( reltype, "has_many" ) )
2786 other_class = osrfHashGet( idl_link, "class" );
2787 if( other_class && !strcmp( other_class, class ) )
2788 field = osrfHashGet( idl_link, "key" );
2792 "%s: JOIN failed. No link defined from %s.%s to %s",
2798 buffer_free( join_buf );
2800 jsonObjectFree( freeable_hash );
2801 jsonIteratorFree( search_itr );
2805 } else if( !field && !fkey ) {
2806 osrfHash* left_links = left_info->links;
2808 // For each link defined for the left class:
2809 // see if the link references the joined class
2810 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2811 osrfHash* curr_link = NULL;
2812 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2813 const char* other_class = osrfHashGet( curr_link, "class" );
2814 if( other_class && !strcmp( other_class, class ) ) {
2816 // In the IDL, the parent class doesn't always know then names of the child
2817 // columns that are pointing to it, so don't use that end of the link
2818 const char* reltype = osrfHashGet( curr_link, "reltype" );
2819 if( reltype && strcmp( reltype, "has_many" ) ) {
2820 // Found a link between the classes
2821 fkey = osrfHashIteratorKey( itr );
2822 field = osrfHashGet( curr_link, "key" );
2827 osrfHashIteratorFree( itr );
2829 if( !field || !fkey ) {
2830 // Do another such search, with the classes reversed
2832 // For each link defined for the joined class:
2833 // see if the link references the left class
2834 osrfHashIterator* itr = osrfNewHashIterator( links );
2835 osrfHash* curr_link = NULL;
2836 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2837 const char* other_class = osrfHashGet( curr_link, "class" );
2838 if( other_class && !strcmp( other_class, leftclass ) ) {
2840 // In the IDL, the parent class doesn't know then names of the child
2841 // columns that are pointing to it, so don't use that end of the link
2842 const char* reltype = osrfHashGet( curr_link, "reltype" );
2843 if( reltype && strcmp( reltype, "has_many" ) ) {
2844 // Found a link between the classes
2845 field = osrfHashIteratorKey( itr );
2846 fkey = osrfHashGet( curr_link, "key" );
2851 osrfHashIteratorFree( itr );
2854 if( !field || !fkey ) {
2857 "%s: JOIN failed. No link defined between %s and %s",
2862 buffer_free( join_buf );
2864 jsonObjectFree( freeable_hash );
2865 jsonIteratorFree( search_itr );
2870 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2872 if( !strcasecmp( type,"left" )) {
2873 buffer_add( join_buf, " LEFT JOIN" );
2874 } else if( !strcasecmp( type,"right" )) {
2875 buffer_add( join_buf, " RIGHT JOIN" );
2876 } else if( !strcasecmp( type,"full" )) {
2877 buffer_add( join_buf, " FULL JOIN" );
2879 buffer_add( join_buf, " INNER JOIN" );
2882 buffer_add( join_buf, " INNER JOIN" );
2885 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2886 table, right_alias, right_alias, field, left_info->alias, fkey );
2888 // Add any other join conditions as specified by "filter"
2889 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2891 const char* filter_op = jsonObjectGetString(
2892 jsonObjectGetKeyConst( snode, "filter_op" ) );
2893 if( filter_op && !strcasecmp( "or",filter_op )) {
2894 buffer_add( join_buf, " OR " );
2896 buffer_add( join_buf, " AND " );
2899 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2901 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2902 OSRF_BUFFER_ADD( join_buf, jpred );
2907 "%s: JOIN failed. Invalid conditional expression.",
2910 jsonIteratorFree( search_itr );
2911 buffer_free( join_buf );
2913 jsonObjectFree( freeable_hash );
2918 buffer_add( join_buf, " ) " );
2920 // Recursively add a nested join, if one is present
2921 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2923 char* jpred = searchJOIN( join_filter, right_info );
2925 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2926 OSRF_BUFFER_ADD( join_buf, jpred );
2929 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
2930 jsonIteratorFree( search_itr );
2931 buffer_free( join_buf );
2933 jsonObjectFree( freeable_hash );
2940 jsonObjectFree( freeable_hash );
2941 jsonIteratorFree( search_itr );
2943 return buffer_release( join_buf );
2948 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2949 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2950 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2952 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2954 search_hash is the JSON expression of the conditions.
2955 meta is the class definition from the IDL, for the relevant table.
2956 opjoin_type indicates whether multiple conditions, if present, should be
2957 connected by AND or OR.
2958 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2959 to pass it to other functions -- and all they do with it is to use the session
2960 and request members to send error messages back to the client.
2964 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
2965 int opjoin_type, osrfMethodContext* ctx ) {
2969 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
2970 "opjoin_type = %d, ctx addr = %p",
2973 class_info->class_def,
2978 growing_buffer* sql_buf = buffer_init( 128 );
2980 jsonObject* node = NULL;
2983 if( search_hash->type == JSON_ARRAY ) {
2984 osrfLogDebug( OSRF_LOG_MARK,
2985 "%s: In WHERE clause, condition type is JSON_ARRAY", modulename );
2986 if( 0 == search_hash->size ) {
2989 "%s: Invalid predicate structure: empty JSON array",
2992 buffer_free( sql_buf );
2996 unsigned long i = 0;
2997 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3001 if( opjoin_type == OR_OP_JOIN )
3002 buffer_add( sql_buf, " OR " );
3004 buffer_add( sql_buf, " AND " );
3007 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3009 buffer_free( sql_buf );
3013 buffer_fadd( sql_buf, "( %s )", subpred );
3017 } else if( search_hash->type == JSON_HASH ) {
3018 osrfLogDebug( OSRF_LOG_MARK,
3019 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3020 jsonIterator* search_itr = jsonNewIterator( search_hash );
3021 if( !jsonIteratorHasNext( search_itr ) ) {
3024 "%s: Invalid predicate structure: empty JSON object",
3027 jsonIteratorFree( search_itr );
3028 buffer_free( sql_buf );
3032 while( (node = jsonIteratorNext( search_itr )) ) {
3037 if( opjoin_type == OR_OP_JOIN )
3038 buffer_add( sql_buf, " OR " );
3040 buffer_add( sql_buf, " AND " );
3043 if( '+' == search_itr->key[ 0 ] ) {
3045 // This plus sign prefixes a class name or other table alias;
3046 // make sure the table alias is in scope
3047 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3048 if( ! alias_info ) {
3051 "%s: Invalid table alias \"%s\" in WHERE clause",
3055 jsonIteratorFree( search_itr );
3056 buffer_free( sql_buf );
3060 if( node->type == JSON_STRING ) {
3061 // It's the name of a column; make sure it belongs to the class
3062 const char* fieldname = jsonObjectGetString( node );
3063 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3066 "%s: Invalid column name \"%s\" in WHERE clause "
3067 "for table alias \"%s\"",
3072 jsonIteratorFree( search_itr );
3073 buffer_free( sql_buf );
3077 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3079 // It's something more complicated
3080 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3082 jsonIteratorFree( search_itr );
3083 buffer_free( sql_buf );
3087 buffer_fadd( sql_buf, "( %s )", subpred );
3090 } else if( '-' == search_itr->key[ 0 ] ) {
3091 if( !strcasecmp( "-or", search_itr->key )) {
3092 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3094 jsonIteratorFree( search_itr );
3095 buffer_free( sql_buf );
3099 buffer_fadd( sql_buf, "( %s )", subpred );
3101 } else if( !strcasecmp( "-and", search_itr->key )) {
3102 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3104 jsonIteratorFree( search_itr );
3105 buffer_free( sql_buf );
3109 buffer_fadd( sql_buf, "( %s )", subpred );
3111 } else if( !strcasecmp("-not",search_itr->key) ) {
3112 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3114 jsonIteratorFree( search_itr );
3115 buffer_free( sql_buf );
3119 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3121 } else if( !strcasecmp( "-exists", search_itr->key )) {
3122 char* subpred = buildQuery( ctx, node, SUBSELECT );
3124 jsonIteratorFree( search_itr );
3125 buffer_free( sql_buf );
3129 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3131 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3132 char* subpred = buildQuery( ctx, node, SUBSELECT );
3134 jsonIteratorFree( search_itr );
3135 buffer_free( sql_buf );
3139 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3141 } else { // Invalid "minus" operator
3144 "%s: Invalid operator \"%s\" in WHERE clause",
3148 jsonIteratorFree( search_itr );
3149 buffer_free( sql_buf );
3155 const char* class = class_info->class_name;
3156 osrfHash* fields = class_info->fields;
3157 osrfHash* field = osrfHashGet( fields, search_itr->key );
3160 const char* table = class_info->source_def;
3163 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3166 table ? table : "?",
3169 jsonIteratorFree( search_itr );
3170 buffer_free( sql_buf );
3174 char* subpred = searchPredicate( class_info, field, node, ctx );
3176 buffer_free( sql_buf );
3177 jsonIteratorFree( search_itr );
3181 buffer_add( sql_buf, subpred );
3185 jsonIteratorFree( search_itr );
3188 // ERROR ... only hash and array allowed at this level
3189 char* predicate_string = jsonObjectToJSON( search_hash );
3192 "%s: Invalid predicate structure: %s",
3196 buffer_free( sql_buf );
3197 free( predicate_string );
3201 return buffer_release( sql_buf );
3204 /* Build a JSON_ARRAY of field names for a given table alias
3206 static jsonObject* defaultSelectList( const char* table_alias ) {
3211 ClassInfo* class_info = search_all_alias( table_alias );
3212 if( ! class_info ) {
3215 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3222 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3223 osrfHash* field_def = NULL;
3224 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3225 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3226 const char* field_name = osrfHashIteratorKey( field_itr );
3227 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3228 jsonObjectPush( array, jsonNewObject( field_name ) );
3231 osrfHashIteratorFree( field_itr );
3236 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3237 // The jsonObject must be a JSON_HASH with an single entry for "union",
3238 // "intersect", or "except". The data associated with this key must be an
3239 // array of hashes, each hash being a query.
3240 // Also allowed but currently ignored: entries for "order_by" and "alias".
3241 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3243 if( ! combo || combo->type != JSON_HASH )
3244 return NULL; // should be impossible; validated by caller
3246 const jsonObject* query_array = NULL; // array of subordinate queries
3247 const char* op = NULL; // name of operator, e.g. UNION
3248 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3249 int op_count = 0; // for detecting conflicting operators
3250 int excepting = 0; // boolean
3251 int all = 0; // boolean
3252 jsonObject* order_obj = NULL;
3254 // Identify the elements in the hash
3255 jsonIterator* query_itr = jsonNewIterator( combo );
3256 jsonObject* curr_obj = NULL;
3257 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3258 if( ! strcmp( "union", query_itr->key ) ) {
3261 query_array = curr_obj;
3262 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3265 query_array = curr_obj;
3266 } else if( ! strcmp( "except", query_itr->key ) ) {
3270 query_array = curr_obj;
3271 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3274 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3277 order_obj = curr_obj;
3278 } else if( ! strcmp( "alias", query_itr->key ) ) {
3279 if( curr_obj->type != JSON_STRING ) {
3280 jsonIteratorFree( query_itr );
3283 alias = jsonObjectGetString( curr_obj );
3284 } else if( ! strcmp( "all", query_itr->key ) ) {
3285 if( obj_is_true( curr_obj ) )
3289 osrfAppSessionStatus(
3291 OSRF_STATUS_INTERNALSERVERERROR,
3292 "osrfMethodException",
3294 "Malformed query; unexpected entry in query object"
3298 "%s: Unexpected entry for \"%s\" in%squery",
3303 jsonIteratorFree( query_itr );
3307 jsonIteratorFree( query_itr );
3309 // More sanity checks
3310 if( ! query_array ) {
3312 osrfAppSessionStatus(
3314 OSRF_STATUS_INTERNALSERVERERROR,
3315 "osrfMethodException",
3317 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3321 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3324 return NULL; // should be impossible...
3325 } else if( op_count > 1 ) {
3327 osrfAppSessionStatus(
3329 OSRF_STATUS_INTERNALSERVERERROR,
3330 "osrfMethodException",
3332 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3336 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3340 } if( query_array->type != JSON_ARRAY ) {
3342 osrfAppSessionStatus(
3344 OSRF_STATUS_INTERNALSERVERERROR,
3345 "osrfMethodException",
3347 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3351 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3354 json_type( query_array->type )
3357 } if( query_array->size < 2 ) {
3359 osrfAppSessionStatus(
3361 OSRF_STATUS_INTERNALSERVERERROR,
3362 "osrfMethodException",
3364 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3368 "%s:%srequires multiple queries as operands",
3373 } else if( excepting && query_array->size > 2 ) {
3375 osrfAppSessionStatus(
3377 OSRF_STATUS_INTERNALSERVERERROR,
3378 "osrfMethodException",
3380 "EXCEPT operator has too many queries as operands"
3384 "%s:EXCEPT operator has too many queries as operands",
3388 } else if( order_obj && ! alias ) {
3390 osrfAppSessionStatus(
3392 OSRF_STATUS_INTERNALSERVERERROR,
3393 "osrfMethodException",
3395 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3399 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3405 // So far so good. Now build the SQL.
3406 growing_buffer* sql = buffer_init( 256 );
3408 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3409 // Add a layer of parentheses
3410 if( flags & SUBCOMBO )
3411 OSRF_BUFFER_ADD( sql, "( " );
3413 // Traverse the query array. Each entry should be a hash.
3414 int first = 1; // boolean
3416 jsonObject* query = NULL;
3417 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3418 if( query->type != JSON_HASH ) {
3420 osrfAppSessionStatus(
3422 OSRF_STATUS_INTERNALSERVERERROR,
3423 "osrfMethodException",
3425 "Malformed query under UNION, INTERSECT or EXCEPT"
3429 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3432 json_type( query->type )
3441 OSRF_BUFFER_ADD( sql, op );
3443 OSRF_BUFFER_ADD( sql, "ALL " );
3446 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3450 "%s: Error building query under%s",
3458 OSRF_BUFFER_ADD( sql, query_str );
3461 if( flags & SUBCOMBO )
3462 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3464 if( !(flags & SUBSELECT) )
3465 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3467 return buffer_release( sql );
3470 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3471 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3472 // or "except" to indicate the type of query.
3473 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3477 osrfAppSessionStatus(
3479 OSRF_STATUS_INTERNALSERVERERROR,
3480 "osrfMethodException",
3482 "Malformed query; no query object"
3484 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3486 } else if( query->type != JSON_HASH ) {
3488 osrfAppSessionStatus(
3490 OSRF_STATUS_INTERNALSERVERERROR,
3491 "osrfMethodException",
3493 "Malformed query object"
3497 "%s: Query object is %s instead of JSON_HASH",
3499 json_type( query->type )
3504 // Determine what kind of query it purports to be, and dispatch accordingly.
3505 if( jsonObjectGetKey( query, "union" ) ||
3506 jsonObjectGetKey( query, "intersect" ) ||
3507 jsonObjectGetKey( query, "except" ) ) {
3508 return doCombo( ctx, query, flags );
3510 // It is presumably a SELECT query
3512 // Push a node onto the stack for the current query. Every level of
3513 // subquery gets its own QueryFrame on the Stack.
3516 // Build an SQL SELECT statement
3519 jsonObjectGetKey( query, "select" ),
3520 jsonObjectGetKey( query, "from" ),
3521 jsonObjectGetKey( query, "where" ),
3522 jsonObjectGetKey( query, "having" ),
3523 jsonObjectGetKey( query, "order_by" ),
3524 jsonObjectGetKey( query, "limit" ),
3525 jsonObjectGetKey( query, "offset" ),
3534 /* method context */ osrfMethodContext* ctx,
3536 /* SELECT */ jsonObject* selhash,
3537 /* FROM */ jsonObject* join_hash,
3538 /* WHERE */ jsonObject* search_hash,
3539 /* HAVING */ jsonObject* having_hash,
3540 /* ORDER BY */ jsonObject* order_hash,
3541 /* LIMIT */ jsonObject* limit,
3542 /* OFFSET */ jsonObject* offset,
3543 /* flags */ int flags
3545 const char* locale = osrf_message_get_last_locale();
3547 // general tmp objects
3548 const jsonObject* tmp_const;
3549 jsonObject* selclass = NULL;
3550 jsonObject* snode = NULL;
3551 jsonObject* onode = NULL;
3553 char* string = NULL;
3554 int from_function = 0;
3559 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3561 // punt if there's no FROM clause
3562 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3565 "%s: FROM clause is missing or empty",
3569 osrfAppSessionStatus(
3571 OSRF_STATUS_INTERNALSERVERERROR,
3572 "osrfMethodException",
3574 "FROM clause is missing or empty in JSON query"
3579 // the core search class
3580 const char* core_class = NULL;
3582 // get the core class -- the only key of the top level FROM clause, or a string
3583 if( join_hash->type == JSON_HASH ) {
3584 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3585 snode = jsonIteratorNext( tmp_itr );
3587 // Populate the current QueryFrame with information
3588 // about the core class
3589 if( add_query_core( NULL, tmp_itr->key ) ) {
3591 osrfAppSessionStatus(
3593 OSRF_STATUS_INTERNALSERVERERROR,
3594 "osrfMethodException",
3596 "Unable to look up core class"
3600 core_class = curr_query->core.class_name;
3603 jsonObject* extra = jsonIteratorNext( tmp_itr );
3605 jsonIteratorFree( tmp_itr );
3608 // There shouldn't be more than one entry in join_hash
3612 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3616 osrfAppSessionStatus(
3618 OSRF_STATUS_INTERNALSERVERERROR,
3619 "osrfMethodException",
3621 "Malformed FROM clause in JSON query"
3623 return NULL; // Malformed join_hash; extra entry
3625 } else if( join_hash->type == JSON_ARRAY ) {
3626 // We're selecting from a function, not from a table
3628 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3631 } else if( join_hash->type == JSON_STRING ) {
3632 // Populate the current QueryFrame with information
3633 // about the core class
3634 core_class = jsonObjectGetString( join_hash );
3636 if( add_query_core( NULL, core_class ) ) {
3638 osrfAppSessionStatus(
3640 OSRF_STATUS_INTERNALSERVERERROR,
3641 "osrfMethodException",
3643 "Unable to look up core class"
3651 "%s: FROM clause is unexpected JSON type: %s",
3653 json_type( join_hash->type )
3656 osrfAppSessionStatus(
3658 OSRF_STATUS_INTERNALSERVERERROR,
3659 "osrfMethodException",
3661 "Ill-formed FROM clause in JSON query"
3666 // Build the join clause, if any, while filling out the list
3667 // of joined classes in the current QueryFrame.
3668 char* join_clause = NULL;
3669 if( join_hash && ! from_function ) {
3671 join_clause = searchJOIN( join_hash, &curr_query->core );
3672 if( ! join_clause ) {
3674 osrfAppSessionStatus(
3676 OSRF_STATUS_INTERNALSERVERERROR,
3677 "osrfMethodException",
3679 "Unable to construct JOIN clause(s)"
3685 // For in case we don't get a select list
3686 jsonObject* defaultselhash = NULL;
3688 // if there is no select list, build a default select list ...
3689 if( !selhash && !from_function ) {
3690 jsonObject* default_list = defaultSelectList( core_class );
3691 if( ! default_list ) {
3693 osrfAppSessionStatus(
3695 OSRF_STATUS_INTERNALSERVERERROR,
3696 "osrfMethodException",
3698 "Unable to build default SELECT clause in JSON query"
3700 free( join_clause );
3705 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3706 jsonObjectSetKey( selhash, core_class, default_list );
3709 // The SELECT clause can be encoded only by a hash
3710 if( !from_function && selhash->type != JSON_HASH ) {
3713 "%s: Expected JSON_HASH for SELECT clause; found %s",
3715 json_type( selhash->type )
3719 osrfAppSessionStatus(
3721 OSRF_STATUS_INTERNALSERVERERROR,
3722 "osrfMethodException",
3724 "Malformed SELECT clause in JSON query"
3726 free( join_clause );
3730 // If you see a null or wild card specifier for the core class, or an
3731 // empty array, replace it with a default SELECT list
3732 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3734 int default_needed = 0; // boolean
3735 if( JSON_STRING == tmp_const->type
3736 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3738 else if( JSON_NULL == tmp_const->type )
3741 if( default_needed ) {
3742 // Build a default SELECT list
3743 jsonObject* default_list = defaultSelectList( core_class );
3744 if( ! default_list ) {
3746 osrfAppSessionStatus(
3748 OSRF_STATUS_INTERNALSERVERERROR,
3749 "osrfMethodException",
3751 "Can't build default SELECT clause in JSON query"
3753 free( join_clause );
3758 jsonObjectSetKey( selhash, core_class, default_list );
3762 // temp buffers for the SELECT list and GROUP BY clause
3763 growing_buffer* select_buf = buffer_init( 128 );
3764 growing_buffer* group_buf = buffer_init( 128 );
3766 int aggregate_found = 0; // boolean
3768 // Build a select list
3769 if( from_function ) // From a function we select everything
3770 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3773 // Build the SELECT list as SQL
3777 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3778 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3780 const char* cname = selclass_itr->key;
3782 // Make sure the target relation is in the FROM clause.
3784 // At this point join_hash is a step down from the join_hash we
3785 // received as a parameter. If the original was a JSON_STRING,
3786 // then json_hash is now NULL. If the original was a JSON_HASH,
3787 // then json_hash is now the first (and only) entry in it,
3788 // denoting the core class. We've already excluded the
3789 // possibility that the original was a JSON_ARRAY, because in
3790 // that case from_function would be non-NULL, and we wouldn't
3793 // If the current table alias isn't in scope, bail out
3794 ClassInfo* class_info = search_alias( cname );
3795 if( ! class_info ) {
3798 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3803 osrfAppSessionStatus(
3805 OSRF_STATUS_INTERNALSERVERERROR,
3806 "osrfMethodException",
3808 "Selected class not in FROM clause in JSON query"
3810 jsonIteratorFree( selclass_itr );
3811 buffer_free( select_buf );
3812 buffer_free( group_buf );
3813 if( defaultselhash )
3814 jsonObjectFree( defaultselhash );
3815 free( join_clause );
3819 if( selclass->type != JSON_ARRAY ) {
3822 "%s: Malformed SELECT list for class \"%s\"; not an array",
3827 osrfAppSessionStatus(
3829 OSRF_STATUS_INTERNALSERVERERROR,
3830 "osrfMethodException",
3832 "Selected class not in FROM clause in JSON query"
3835 jsonIteratorFree( selclass_itr );
3836 buffer_free( select_buf );
3837 buffer_free( group_buf );
3838 if( defaultselhash )
3839 jsonObjectFree( defaultselhash );
3840 free( join_clause );
3844 // Look up some attributes of the current class
3845 osrfHash* idlClass = class_info->class_def;
3846 osrfHash* class_field_set = class_info->fields;
3847 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3848 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3850 if( 0 == selclass->size ) {
3853 "%s: No columns selected from \"%s\"",
3859 // stitch together the column list for the current table alias...
3860 unsigned long field_idx = 0;
3861 jsonObject* selfield = NULL;
3862 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3864 // If we need a separator comma, add one
3868 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3871 // if the field specification is a string, add it to the list
3872 if( selfield->type == JSON_STRING ) {
3874 // Look up the field in the IDL
3875 const char* col_name = jsonObjectGetString( selfield );
3876 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3878 // No such field in current class
3881 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3887 osrfAppSessionStatus(
3889 OSRF_STATUS_INTERNALSERVERERROR,
3890 "osrfMethodException",
3892 "Selected column not defined in JSON query"
3894 jsonIteratorFree( selclass_itr );
3895 buffer_free( select_buf );
3896 buffer_free( group_buf );
3897 if( defaultselhash )
3898 jsonObjectFree( defaultselhash );
3899 free( join_clause );
3901 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3902 // Virtual field not allowed
3905 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3911 osrfAppSessionStatus(
3913 OSRF_STATUS_INTERNALSERVERERROR,
3914 "osrfMethodException",
3916 "Selected column may not be virtual in JSON query"
3918 jsonIteratorFree( selclass_itr );
3919 buffer_free( select_buf );
3920 buffer_free( group_buf );
3921 if( defaultselhash )
3922 jsonObjectFree( defaultselhash );
3923 free( join_clause );
3929 if( flags & DISABLE_I18N )
3932 i18n = osrfHashGet( field_def, "i18n" );
3934 if( str_is_true( i18n ) ) {
3935 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
3936 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3937 class_tname, cname, col_name, class_pkey,
3938 cname, class_pkey, locale, col_name );
3940 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3941 cname, col_name, col_name );
3944 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3945 cname, col_name, col_name );
3948 // ... but it could be an object, in which case we check for a Field Transform
3949 } else if( selfield->type == JSON_HASH ) {
3951 const char* col_name = jsonObjectGetString(
3952 jsonObjectGetKeyConst( selfield, "column" ) );
3954 // Get the field definition from the IDL
3955 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3957 // No such field in current class
3960 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3966 osrfAppSessionStatus(
3968 OSRF_STATUS_INTERNALSERVERERROR,
3969 "osrfMethodException",
3971 "Selected column is not defined in JSON query"
3973 jsonIteratorFree( selclass_itr );
3974 buffer_free( select_buf );
3975 buffer_free( group_buf );
3976 if( defaultselhash )
3977 jsonObjectFree( defaultselhash );
3978 free( join_clause );
3980 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
3981 // No such field in current class
3984 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3990 osrfAppSessionStatus(
3992 OSRF_STATUS_INTERNALSERVERERROR,
3993 "osrfMethodException",
3995 "Selected column is virtual in JSON query"
3997 jsonIteratorFree( selclass_itr );
3998 buffer_free( select_buf );
3999 buffer_free( group_buf );
4000 if( defaultselhash )
4001 jsonObjectFree( defaultselhash );
4002 free( join_clause );
4006 // Decide what to use as a column alias
4008 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4009 _alias = jsonObjectGetString( tmp_const );
4010 } else { // Use field name as the alias
4014 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4015 char* transform_str = searchFieldTransform(
4016 class_info->alias, field_def, selfield );
4017 if( transform_str ) {
4018 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4019 free( transform_str );
4022 osrfAppSessionStatus(
4024 OSRF_STATUS_INTERNALSERVERERROR,
4025 "osrfMethodException",
4027 "Unable to generate transform function in JSON query"
4029 jsonIteratorFree( selclass_itr );
4030 buffer_free( select_buf );
4031 buffer_free( group_buf );
4032 if( defaultselhash )
4033 jsonObjectFree( defaultselhash );
4034 free( join_clause );
4041 if( flags & DISABLE_I18N )
4044 i18n = osrfHashGet( field_def, "i18n" );
4046 if( str_is_true( i18n ) ) {
4047 buffer_fadd( select_buf,
4048 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4049 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4050 class_tname, cname, col_name, class_pkey, cname,
4051 class_pkey, locale, _alias );
4053 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4054 cname, col_name, _alias );
4057 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4058 cname, col_name, _alias );
4065 "%s: Selected item is unexpected JSON type: %s",
4067 json_type( selfield->type )
4070 osrfAppSessionStatus(
4072 OSRF_STATUS_INTERNALSERVERERROR,
4073 "osrfMethodException",
4075 "Ill-formed SELECT item in JSON query"
4077 jsonIteratorFree( selclass_itr );
4078 buffer_free( select_buf );
4079 buffer_free( group_buf );
4080 if( defaultselhash )
4081 jsonObjectFree( defaultselhash );
4082 free( join_clause );
4086 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
4087 if( obj_is_true( agg_obj ) )
4088 aggregate_found = 1;
4090 // Append a comma (except for the first one)
4091 // and add the column to a GROUP BY clause
4095 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4097 buffer_fadd( group_buf, " %d", sel_pos );
4101 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4103 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4104 if ( ! obj_is_true( aggregate_obj ) ) {
4108 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4111 buffer_fadd(group_buf, " %d", sel_pos);
4114 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4118 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4121 _column = searchFieldTransform(class_info->alias, field, selfield);
4122 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4123 OSRF_BUFFER_ADD(group_buf, _column);
4124 _column = searchFieldTransform(class_info->alias, field, selfield);
4131 } // end while -- iterating across SELECT columns
4133 } // end while -- iterating across classes
4135 jsonIteratorFree( selclass_itr );
4139 char* col_list = buffer_release( select_buf );
4141 // Make sure the SELECT list isn't empty. This can happen, for example,
4142 // if we try to build a default SELECT clause from a non-core table.
4145 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4147 osrfAppSessionStatus(
4149 OSRF_STATUS_INTERNALSERVERERROR,
4150 "osrfMethodException",
4152 "SELECT list is empty"
4155 buffer_free( group_buf );
4156 if( defaultselhash )
4157 jsonObjectFree( defaultselhash );
4158 free( join_clause );
4164 table = searchValueTransform( join_hash );
4166 table = strdup( curr_query->core.source_def );
4170 osrfAppSessionStatus(
4172 OSRF_STATUS_INTERNALSERVERERROR,
4173 "osrfMethodException",
4175 "Unable to identify table for core class"
4178 buffer_free( group_buf );
4179 if( defaultselhash )
4180 jsonObjectFree( defaultselhash );
4181 free( join_clause );
4185 // Put it all together
4186 growing_buffer* sql_buf = buffer_init( 128 );
4187 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4191 // Append the join clause, if any
4193 buffer_add(sql_buf, join_clause );
4194 free( join_clause );
4197 char* order_by_list = NULL;
4198 char* having_buf = NULL;
4200 if( !from_function ) {
4202 // Build a WHERE clause, if there is one
4204 buffer_add( sql_buf, " WHERE " );
4206 // and it's on the WHERE clause
4207 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4210 osrfAppSessionStatus(
4212 OSRF_STATUS_INTERNALSERVERERROR,
4213 "osrfMethodException",
4215 "Severe query error in WHERE predicate -- see error log for more details"
4218 buffer_free( group_buf );
4219 buffer_free( sql_buf );
4220 if( defaultselhash )
4221 jsonObjectFree( defaultselhash );
4225 buffer_add( sql_buf, pred );
4229 // Build a HAVING clause, if there is one
4232 // and it's on the the WHERE clause
4233 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4235 if( ! having_buf ) {
4237 osrfAppSessionStatus(
4239 OSRF_STATUS_INTERNALSERVERERROR,
4240 "osrfMethodException",
4242 "Severe query error in HAVING predicate -- see error log for more details"
4245 buffer_free( group_buf );
4246 buffer_free( sql_buf );
4247 if( defaultselhash )
4248 jsonObjectFree( defaultselhash );
4253 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4255 // Build an ORDER BY clause, if there is one
4256 if( NULL == order_hash )
4257 ; // No ORDER BY? do nothing
4258 else if( JSON_ARRAY == order_hash->type ) {
4259 // Array of field specifications, each specification being a
4260 // hash to define the class, field, and other details
4262 jsonObject* order_spec;
4263 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4265 if( JSON_HASH != order_spec->type ) {
4266 osrfLogError( OSRF_LOG_MARK,
4267 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4268 modulename, json_type( order_spec->type ) );
4270 osrfAppSessionStatus(
4272 OSRF_STATUS_INTERNALSERVERERROR,
4273 "osrfMethodException",
4275 "Malformed ORDER BY clause -- see error log for more details"
4277 buffer_free( order_buf );
4279 buffer_free( group_buf );
4280 buffer_free( sql_buf );
4281 if( defaultselhash )
4282 jsonObjectFree( defaultselhash );
4286 const char* class_alias =
4287 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4289 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4292 OSRF_BUFFER_ADD( order_buf, ", " );
4294 order_buf = buffer_init( 128 );
4296 if( !field || !class_alias ) {
4297 osrfLogError( OSRF_LOG_MARK,
4298 "%s: Missing class or field name in field specification "
4299 "of ORDER BY clause",
4302 osrfAppSessionStatus(
4304 OSRF_STATUS_INTERNALSERVERERROR,
4305 "osrfMethodException",
4307 "Malformed ORDER BY clause -- see error log for more details"
4309 buffer_free( order_buf );
4311 buffer_free( group_buf );
4312 buffer_free( sql_buf );
4313 if( defaultselhash )
4314 jsonObjectFree( defaultselhash );
4318 ClassInfo* order_class_info = search_alias( class_alias );
4319 if( ! order_class_info ) {
4320 osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4321 "not in FROM clause", modulename, class_alias );
4323 osrfAppSessionStatus(
4325 OSRF_STATUS_INTERNALSERVERERROR,
4326 "osrfMethodException",
4328 "Invalid class referenced in ORDER BY clause -- "
4329 "see error log for more details"
4332 buffer_free( group_buf );
4333 buffer_free( sql_buf );
4334 if( defaultselhash )
4335 jsonObjectFree( defaultselhash );
4339 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4341 osrfLogError( OSRF_LOG_MARK,
4342 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4343 modulename, class_alias, field );
4345 osrfAppSessionStatus(
4347 OSRF_STATUS_INTERNALSERVERERROR,
4348 "osrfMethodException",
4350 "Invalid field referenced in ORDER BY clause -- "
4351 "see error log for more details"
4354 buffer_free( group_buf );
4355 buffer_free( sql_buf );
4356 if( defaultselhash )
4357 jsonObjectFree( defaultselhash );
4359 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4360 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4361 modulename, field );
4363 osrfAppSessionStatus(
4365 OSRF_STATUS_INTERNALSERVERERROR,
4366 "osrfMethodException",
4368 "Virtual field in ORDER BY clause -- see error log for more details"
4370 buffer_free( order_buf );
4372 buffer_free( group_buf );
4373 buffer_free( sql_buf );
4374 if( defaultselhash )
4375 jsonObjectFree( defaultselhash );
4379 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4380 char* transform_str = searchFieldTransform(
4381 class_alias, field_def, order_spec );
4382 if( ! transform_str ) {
4384 osrfAppSessionStatus(
4386 OSRF_STATUS_INTERNALSERVERERROR,
4387 "osrfMethodException",
4389 "Severe query error in ORDER BY clause -- "
4390 "see error log for more details"
4392 buffer_free( order_buf );
4394 buffer_free( group_buf );
4395 buffer_free( sql_buf );
4396 if( defaultselhash )
4397 jsonObjectFree( defaultselhash );
4401 OSRF_BUFFER_ADD( order_buf, transform_str );
4402 free( transform_str );
4405 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4407 const char* direction =
4408 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4410 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4411 OSRF_BUFFER_ADD( order_buf, " DESC" );
4413 OSRF_BUFFER_ADD( order_buf, " ASC" );
4416 } else if( JSON_HASH == order_hash->type ) {
4417 // This hash is keyed on class alias. Each class has either
4418 // an array of field names or a hash keyed on field name.
4419 jsonIterator* class_itr = jsonNewIterator( order_hash );
4420 while( (snode = jsonIteratorNext( class_itr )) ) {
4422 ClassInfo* order_class_info = search_alias( class_itr->key );
4423 if( ! order_class_info ) {
4424 osrfLogError( OSRF_LOG_MARK,
4425 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4426 modulename, class_itr->key );
4428 osrfAppSessionStatus(
4430 OSRF_STATUS_INTERNALSERVERERROR,
4431 "osrfMethodException",
4433 "Invalid class referenced in ORDER BY clause -- "
4434 "see error log for more details"
4436 jsonIteratorFree( class_itr );
4437 buffer_free( order_buf );
4439 buffer_free( group_buf );
4440 buffer_free( sql_buf );
4441 if( defaultselhash )
4442 jsonObjectFree( defaultselhash );
4446 osrfHash* field_list_def = order_class_info->fields;
4448 if( snode->type == JSON_HASH ) {
4450 // Hash is keyed on field names from the current class. For each field
4451 // there is another layer of hash to define the sorting details, if any,
4452 // or a string to indicate direction of sorting.
4453 jsonIterator* order_itr = jsonNewIterator( snode );
4454 while( (onode = jsonIteratorNext( order_itr )) ) {
4456 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4458 osrfLogError( OSRF_LOG_MARK,
4459 "%s: Invalid field \"%s\" in ORDER BY clause",
4460 modulename, order_itr->key );
4462 osrfAppSessionStatus(
4464 OSRF_STATUS_INTERNALSERVERERROR,
4465 "osrfMethodException",
4467 "Invalid field in ORDER BY clause -- "
4468 "see error log for more details"
4470 jsonIteratorFree( order_itr );
4471 jsonIteratorFree( class_itr );
4472 buffer_free( order_buf );
4474 buffer_free( group_buf );
4475 buffer_free( sql_buf );
4476 if( defaultselhash )
4477 jsonObjectFree( defaultselhash );
4479 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4480 osrfLogError( OSRF_LOG_MARK,
4481 "%s: Virtual field \"%s\" in ORDER BY clause",
4482 modulename, order_itr->key );
4484 osrfAppSessionStatus(
4486 OSRF_STATUS_INTERNALSERVERERROR,
4487 "osrfMethodException",
4489 "Virtual field in ORDER BY clause -- "
4490 "see error log for more details"
4492 jsonIteratorFree( order_itr );
4493 jsonIteratorFree( class_itr );
4494 buffer_free( order_buf );
4496 buffer_free( group_buf );
4497 buffer_free( sql_buf );
4498 if( defaultselhash )
4499 jsonObjectFree( defaultselhash );
4503 const char* direction = NULL;
4504 if( onode->type == JSON_HASH ) {
4505 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4506 string = searchFieldTransform(
4508 osrfHashGet( field_list_def, order_itr->key ),
4512 if( ctx ) osrfAppSessionStatus(
4514 OSRF_STATUS_INTERNALSERVERERROR,
4515 "osrfMethodException",
4517 "Severe query error in ORDER BY clause -- "
4518 "see error log for more details"
4520 jsonIteratorFree( order_itr );
4521 jsonIteratorFree( class_itr );
4523 buffer_free( group_buf );
4524 buffer_free( order_buf);
4525 buffer_free( sql_buf );
4526 if( defaultselhash )
4527 jsonObjectFree( defaultselhash );
4531 growing_buffer* field_buf = buffer_init( 16 );
4532 buffer_fadd( field_buf, "\"%s\".%s",
4533 class_itr->key, order_itr->key );
4534 string = buffer_release( field_buf );
4537 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4538 const char* dir = jsonObjectGetString( tmp_const );
4539 if(!strncasecmp( dir, "d", 1 )) {
4540 direction = " DESC";
4546 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4547 osrfLogError( OSRF_LOG_MARK,
4548 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4549 modulename, json_type( onode->type ) );
4551 osrfAppSessionStatus(
4553 OSRF_STATUS_INTERNALSERVERERROR,
4554 "osrfMethodException",
4556 "Malformed ORDER BY clause -- see error log for more details"
4558 jsonIteratorFree( order_itr );
4559 jsonIteratorFree( class_itr );
4561 buffer_free( group_buf );
4562 buffer_free( order_buf );
4563 buffer_free( sql_buf );
4564 if( defaultselhash )
4565 jsonObjectFree( defaultselhash );
4569 string = strdup( order_itr->key );
4570 const char* dir = jsonObjectGetString( onode );
4571 if( !strncasecmp( dir, "d", 1 )) {
4572 direction = " DESC";
4579 OSRF_BUFFER_ADD( order_buf, ", " );
4581 order_buf = buffer_init( 128 );
4583 OSRF_BUFFER_ADD( order_buf, string );
4587 OSRF_BUFFER_ADD( order_buf, direction );
4591 jsonIteratorFree( order_itr );
4593 } else if( snode->type == JSON_ARRAY ) {
4595 // Array is a list of fields from the current class
4596 unsigned long order_idx = 0;
4597 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4599 const char* _f = jsonObjectGetString( onode );
4601 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4603 osrfLogError( OSRF_LOG_MARK,
4604 "%s: Invalid field \"%s\" in ORDER BY clause",
4607 osrfAppSessionStatus(
4609 OSRF_STATUS_INTERNALSERVERERROR,
4610 "osrfMethodException",
4612 "Invalid field in ORDER BY clause -- "
4613 "see error log for more details"
4615 jsonIteratorFree( class_itr );
4616 buffer_free( order_buf );
4618 buffer_free( group_buf );
4619 buffer_free( sql_buf );
4620 if( defaultselhash )
4621 jsonObjectFree( defaultselhash );
4623 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4624 osrfLogError( OSRF_LOG_MARK,
4625 "%s: Virtual field \"%s\" in ORDER BY clause",
4628 osrfAppSessionStatus(
4630 OSRF_STATUS_INTERNALSERVERERROR,
4631 "osrfMethodException",
4633 "Virtual field in ORDER BY clause -- "
4634 "see error log for more details"
4636 jsonIteratorFree( class_itr );
4637 buffer_free( order_buf );
4639 buffer_free( group_buf );
4640 buffer_free( sql_buf );
4641 if( defaultselhash )
4642 jsonObjectFree( defaultselhash );
4647 OSRF_BUFFER_ADD( order_buf, ", " );
4649 order_buf = buffer_init( 128 );
4651 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4655 // IT'S THE OOOOOOOOOOOLD STYLE!
4657 osrfLogError( OSRF_LOG_MARK,
4658 "%s: Possible SQL injection attempt; direct order by is not allowed",
4661 osrfAppSessionStatus(
4663 OSRF_STATUS_INTERNALSERVERERROR,
4664 "osrfMethodException",
4666 "Severe query error -- see error log for more details"
4671 buffer_free( group_buf );
4672 buffer_free( order_buf );
4673 buffer_free( sql_buf );
4674 if( defaultselhash )
4675 jsonObjectFree( defaultselhash );
4676 jsonIteratorFree( class_itr );
4680 jsonIteratorFree( class_itr );
4682 osrfLogError( OSRF_LOG_MARK,
4683 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4684 modulename, json_type( order_hash->type ) );
4686 osrfAppSessionStatus(
4688 OSRF_STATUS_INTERNALSERVERERROR,
4689 "osrfMethodException",
4691 "Malformed ORDER BY clause -- see error log for more details"
4693 buffer_free( order_buf );
4695 buffer_free( group_buf );
4696 buffer_free( sql_buf );
4697 if( defaultselhash )
4698 jsonObjectFree( defaultselhash );
4703 order_by_list = buffer_release( order_buf );
4707 string = buffer_release( group_buf );
4709 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4710 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4711 OSRF_BUFFER_ADD( sql_buf, string );
4716 if( having_buf && *having_buf ) {
4717 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4718 OSRF_BUFFER_ADD( sql_buf, having_buf );
4722 if( order_by_list ) {
4724 if( *order_by_list ) {
4725 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4726 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4729 free( order_by_list );
4733 const char* str = jsonObjectGetString( limit );
4734 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4738 const char* str = jsonObjectGetString( offset );
4739 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4742 if( !(flags & SUBSELECT) )
4743 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4745 if( defaultselhash )
4746 jsonObjectFree( defaultselhash );
4748 return buffer_release( sql_buf );
4750 } // end of SELECT()
4752 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4754 const char* locale = osrf_message_get_last_locale();
4756 osrfHash* fields = osrfHashGet( meta, "fields" );
4757 char* core_class = osrfHashGet( meta, "classname" );
4759 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4761 jsonObject* node = NULL;
4762 jsonObject* snode = NULL;
4763 jsonObject* onode = NULL;
4764 const jsonObject* _tmp = NULL;
4765 jsonObject* selhash = NULL;
4766 jsonObject* defaultselhash = NULL;
4768 growing_buffer* sql_buf = buffer_init( 128 );
4769 growing_buffer* select_buf = buffer_init( 128 );
4771 if( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4772 defaultselhash = jsonNewObjectType( JSON_HASH );
4773 selhash = defaultselhash;
4776 // If there's no SELECT list for the core class, build one
4777 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4778 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4780 // Add every non-virtual field to the field list
4781 osrfHash* field_def = NULL;
4782 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4783 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4784 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4785 const char* field = osrfHashIteratorKey( field_itr );
4786 jsonObjectPush( field_list, jsonNewObject( field ) );
4789 osrfHashIteratorFree( field_itr );
4790 jsonObjectSetKey( selhash, core_class, field_list );
4794 jsonIterator* class_itr = jsonNewIterator( selhash );
4795 while( (snode = jsonIteratorNext( class_itr )) ) {
4797 const char* cname = class_itr->key;
4798 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4802 if( strcmp(core_class,class_itr->key )) {
4806 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4807 if( !found->size ) {
4808 jsonObjectFree( found );
4812 jsonObjectFree( found );
4815 jsonIterator* select_itr = jsonNewIterator( snode );
4816 while( (node = jsonIteratorNext( select_itr )) ) {
4817 const char* item_str = jsonObjectGetString( node );
4818 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4819 char* fname = osrfHashGet( field, "name" );
4827 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4832 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4833 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4836 i18n = osrfHashGet( field, "i18n" );
4838 if( str_is_true( i18n ) ) {
4839 char* pkey = osrfHashGet( idlClass, "primarykey" );
4840 char* tname = osrfHashGet( idlClass, "tablename" );
4842 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4843 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4844 tname, cname, fname, pkey, cname, pkey, locale, fname );
4846 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4849 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4853 jsonIteratorFree( select_itr );
4856 jsonIteratorFree( class_itr );
4858 char* col_list = buffer_release( select_buf );
4859 char* table = oilsGetRelation( meta );
4861 table = strdup( "(null)" );
4863 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4867 // Clear the query stack (as a fail-safe precaution against possible
4868 // leftover garbage); then push the first query frame onto the stack.
4869 clear_query_stack();
4871 if( add_query_core( NULL, core_class ) ) {
4873 osrfAppSessionStatus(
4875 OSRF_STATUS_INTERNALSERVERERROR,
4876 "osrfMethodException",
4878 "Unable to build query frame for core class"
4884 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4885 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
4886 OSRF_BUFFER_ADD( sql_buf, join_clause );
4887 free( join_clause );
4890 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4891 modulename, OSRF_BUFFER_C_STR( sql_buf ));
4893 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
4895 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4897 osrfAppSessionStatus(
4899 OSRF_STATUS_INTERNALSERVERERROR,
4900 "osrfMethodException",
4902 "Severe query error -- see error log for more details"
4904 buffer_free( sql_buf );
4905 if( defaultselhash )
4906 jsonObjectFree( defaultselhash );
4907 clear_query_stack();
4910 buffer_add( sql_buf, pred );
4915 char* string = NULL;
4916 if( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4918 growing_buffer* order_buf = buffer_init( 128 );
4921 jsonIterator* class_itr = jsonNewIterator( _tmp );
4922 while( (snode = jsonIteratorNext( class_itr )) ) {
4924 if( !jsonObjectGetKeyConst( selhash,class_itr->key ))
4927 if( snode->type == JSON_HASH ) {
4929 jsonIterator* order_itr = jsonNewIterator( snode );
4930 while( (onode = jsonIteratorNext( order_itr )) ) {
4932 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4933 class_itr->key, order_itr->key );
4937 char* direction = NULL;
4938 if( onode->type == JSON_HASH ) {
4939 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4940 string = searchFieldTransform( class_itr->key, field_def, onode );
4942 osrfAppSessionStatus(
4944 OSRF_STATUS_INTERNALSERVERERROR,
4945 "osrfMethodException",
4947 "Severe query error in ORDER BY clause -- "
4948 "see error log for more details"
4950 jsonIteratorFree( order_itr );
4951 jsonIteratorFree( class_itr );
4952 buffer_free( order_buf );
4953 buffer_free( sql_buf );
4954 if( defaultselhash )
4955 jsonObjectFree( defaultselhash );
4956 clear_query_stack();
4960 growing_buffer* field_buf = buffer_init( 16 );
4961 buffer_fadd( field_buf, "\"%s\".%s",
4962 class_itr->key, order_itr->key );
4963 string = buffer_release( field_buf );
4966 if( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4967 const char* dir = jsonObjectGetString( _tmp );
4968 if(!strncasecmp( dir, "d", 1 )) {
4969 direction = " DESC";
4973 string = strdup( order_itr->key );
4974 const char* dir = jsonObjectGetString( onode );
4975 if( !strncasecmp( dir, "d", 1 )) {
4976 direction = " DESC";
4985 buffer_add( order_buf, ", " );
4988 buffer_add( order_buf, string );
4992 buffer_add( order_buf, direction );
4996 jsonIteratorFree( order_itr );
4999 const char* str = jsonObjectGetString( snode );
5000 buffer_add( order_buf, str );
5006 jsonIteratorFree( class_itr );
5008 string = buffer_release( order_buf );
5011 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5012 OSRF_BUFFER_ADD( sql_buf, string );
5018 if( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ) {
5019 const char* str = jsonObjectGetString( _tmp );
5027 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
5029 const char* str = jsonObjectGetString( _tmp );
5038 if( defaultselhash )
5039 jsonObjectFree( defaultselhash );
5040 clear_query_stack();
5042 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5043 return buffer_release( sql_buf );
5046 int doJSONSearch ( osrfMethodContext* ctx ) {
5047 if(osrfMethodVerifyContext( ctx )) {
5048 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5052 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5057 dbhandle = writehandle;
5059 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5063 if( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
5064 flags |= SELECT_DISTINCT;
5066 if( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
5067 flags |= DISABLE_I18N;
5069 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5070 clear_query_stack(); // a possibly needless precaution
5071 char* sql = buildQuery( ctx, hash, flags );
5072 clear_query_stack();
5079 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5080 dbi_result result = dbi_conn_query( dbhandle, sql );
5083 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5085 if( dbi_result_first_row( result )) {
5086 /* JSONify the result */
5087 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5090 jsonObject* return_val = oilsMakeJSONFromResult( result );
5091 osrfAppRespond( ctx, return_val );
5092 jsonObjectFree( return_val );
5093 } while( dbi_result_next_row( result ));
5096 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5099 osrfAppRespondComplete( ctx, NULL );
5101 /* clean up the query */
5102 dbi_result_free( result );
5106 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]", modulename, sql );
5107 osrfAppSessionStatus(
5109 OSRF_STATUS_INTERNALSERVERERROR,
5110 "osrfMethodException",
5112 "Severe query error -- see error log for more details"
5120 // The last parameter, err, is used to report an error condition by updating an int owned by
5121 // the calling code.
5123 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5124 // It is the responsibility of the calling code to initialize *err before the
5125 // call, so that it will be able to make sense of the result.
5127 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5128 // redundant anyway.
5129 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5130 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5133 dbhandle = writehandle;
5135 char* core_class = osrfHashGet( class_meta, "classname" );
5136 char* pkey = osrfHashGet( class_meta, "primarykey" );
5138 const jsonObject* _tmp;
5140 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5142 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5147 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5149 dbi_result result = dbi_conn_query( dbhandle, sql );
5150 if( NULL == result ) {
5151 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
5152 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql );
5153 osrfAppSessionStatus(
5155 OSRF_STATUS_INTERNALSERVERERROR,
5156 "osrfMethodException",
5158 "Severe query error -- see error log for more details"
5165 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5168 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5169 jsonObject* row_obj = NULL;
5171 if( dbi_result_first_row( result )) {
5173 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5174 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5175 // eliminate the duplicates.
5176 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5177 osrfHash* dedup = osrfNewHash();
5179 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5180 char* pkey_val = oilsFMGetString( row_obj, pkey );
5181 if( osrfHashGet( dedup, pkey_val ) ) {
5182 jsonObjectFree( row_obj );
5185 osrfHashSet( dedup, pkey_val, pkey_val );
5186 jsonObjectPush( res_list, row_obj );
5188 } while( dbi_result_next_row( result ));
5189 osrfHashFree( dedup );
5192 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5196 /* clean up the query */
5197 dbi_result_free( result );
5200 // If we're asked to flesh, and there's anything to flesh, then flesh.
5201 if( res_list->size && query_hash ) {
5202 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5204 // Get the flesh depth
5205 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5206 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5207 flesh_depth = max_flesh_depth;
5209 // We need a non-zero flesh depth, and a list of fields to flesh
5210 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5211 if( temp_blob && flesh_depth > 0 ) {
5213 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5214 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5216 osrfStringArray* link_fields = NULL;
5217 osrfHash* links = osrfHashGet( class_meta, "links" );
5219 // Make an osrfStringArray of the names of fields to be fleshed
5220 if( flesh_fields ) {
5221 if( flesh_fields->size == 1 ) {
5222 const char* _t = jsonObjectGetString(
5223 jsonObjectGetIndex( flesh_fields, 0 ) );
5224 if( !strcmp( _t, "*" ))
5225 link_fields = osrfHashKeys( links );
5228 if( !link_fields ) {
5230 link_fields = osrfNewStringArray( 1 );
5231 jsonIterator* _i = jsonNewIterator( flesh_fields );
5232 while ((_f = jsonIteratorNext( _i ))) {
5233 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5235 jsonIteratorFree( _i );
5239 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5241 // Iterate over the JSON_ARRAY of rows
5243 unsigned long res_idx = 0;
5244 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5247 const char* link_field;
5249 // Iterate over the list of fleshable fields
5250 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5252 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5254 osrfHash* kid_link = osrfHashGet( links, link_field );
5256 continue; // Not a link field; skip it
5258 osrfHash* field = osrfHashGet( fields, link_field );
5260 continue; // Not a field at all; skip it (IDL is ill-formed)
5262 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5263 osrfHashGet( kid_link, "class" ));
5265 continue; // The class it links to doesn't exist; skip it
5267 const char* reltype = osrfHashGet( kid_link, "reltype" );
5269 continue; // No reltype; skip it (IDL is ill-formed)
5271 osrfHash* value_field = field;
5273 if( !strcmp( reltype, "has_many" )
5274 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5275 value_field = osrfHashGet(
5276 fields, osrfHashGet( class_meta, "primarykey" ) );
5279 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5281 if( link_map->size > 0 ) {
5282 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5285 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5290 osrfHashGet( kid_link, "class" ),
5297 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5298 osrfHashGet( kid_link, "field" ),
5299 osrfHashGet( kid_link, "class" ),
5300 osrfHashGet( kid_link, "key" ),
5301 osrfHashGet( kid_link, "reltype" )
5304 const char* search_key = jsonObjectGetString(
5305 jsonObjectGetIndex( cur,
5306 atoi( osrfHashGet( value_field, "array_position" ) )
5311 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5315 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5317 // construct WHERE clause
5318 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5321 osrfHashGet( kid_link, "key" ),
5322 jsonNewObject( search_key )
5325 // construct the rest of the query, mostly
5326 // by copying pieces of the previous level of query
5327 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5328 jsonObjectSetKey( rest_of_query, "flesh",
5329 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5333 jsonObjectSetKey( rest_of_query, "flesh_fields",
5334 jsonObjectClone( flesh_blob ));
5336 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5337 jsonObjectSetKey( rest_of_query, "order_by",
5338 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5342 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5343 jsonObjectSetKey( rest_of_query, "select",
5344 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5348 // do the query, recursively, to expand the fleshable field
5349 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5350 where_clause, rest_of_query, err );
5352 jsonObjectFree( where_clause );
5353 jsonObjectFree( rest_of_query );
5356 osrfStringArrayFree( link_fields );
5357 jsonObjectFree( res_list );
5358 jsonObjectFree( flesh_blob );
5362 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5363 osrfHashGet( kid_link, "class" ), kids->size );
5365 // Traverse the result set
5366 jsonObject* X = NULL;
5367 if( link_map->size > 0 && kids->size > 0 ) {
5369 kids = jsonNewObjectType( JSON_ARRAY );
5371 jsonObject* _k_node;
5372 unsigned long res_idx = 0;
5373 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5379 (unsigned long) atoi(
5385 osrfHashGet( kid_link, "class" )
5389 osrfStringArrayGetString( link_map, 0 )
5397 } // end while loop traversing X
5400 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5401 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5402 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5403 osrfHashGet( kid_link, "field" ));
5406 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5407 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5411 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5413 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5414 osrfHashGet( kid_link, "field" ) );
5417 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5418 jsonObjectClone( kids )
5423 jsonObjectFree( kids );
5427 jsonObjectFree( kids );
5429 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5430 osrfHashGet( kid_link, "field" ) );
5431 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5433 } // end while loop traversing list of fleshable fields
5434 } // end while loop traversing res_list
5435 jsonObjectFree( flesh_blob );
5436 osrfStringArrayFree( link_fields );
5445 int doUpdate( osrfMethodContext* ctx ) {
5446 if( osrfMethodVerifyContext( ctx )) {
5447 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5452 timeout_needs_resetting = 1;
5454 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5456 jsonObject* target = NULL;
5458 target = jsonObjectGetIndex( ctx->params, 1 );
5460 target = jsonObjectGetIndex( ctx->params, 0 );
5462 if(!verifyObjectClass( ctx, target )) {
5463 osrfAppRespondComplete( ctx, NULL );
5467 if( getXactId( ctx ) == NULL ) {
5468 osrfAppSessionStatus(
5470 OSRF_STATUS_BADREQUEST,
5471 "osrfMethodException",
5473 "No active transaction -- required for UPDATE"
5475 osrfAppRespondComplete( ctx, NULL );
5479 // The following test is harmless but redundant. If a class is
5480 // readonly, we don't register an update method for it.
5481 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5482 osrfAppSessionStatus(
5484 OSRF_STATUS_BADREQUEST,
5485 "osrfMethodException",
5487 "Cannot UPDATE readonly class"
5489 osrfAppRespondComplete( ctx, NULL );
5493 dbhandle = writehandle;
5494 const char* trans_id = getXactId( ctx );
5496 // Set the last_xact_id
5497 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5499 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5500 trans_id, target->classname, index );
5501 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5504 char* pkey = osrfHashGet( meta, "primarykey" );
5505 osrfHash* fields = osrfHashGet( meta, "fields" );
5507 char* id = oilsFMGetString( target, pkey );
5511 "%s updating %s object with %s = %s",
5513 osrfHashGet( meta, "fieldmapper" ),
5518 growing_buffer* sql = buffer_init( 128 );
5519 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5522 osrfHash* field_def = NULL;
5523 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5524 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5526 // Skip virtual fields, and the primary key
5527 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5530 const char* field_name = osrfHashIteratorKey( field_itr );
5531 if( ! strcmp( field_name, pkey ) )
5534 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5536 int value_is_numeric = 0; // boolean
5538 if( field_object && field_object->classname ) {
5539 value = oilsFMGetString(
5541 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5543 } else if( field_object && JSON_BOOL == field_object->type ) {
5544 if( jsonBoolIsTrue( field_object ) )
5545 value = strdup( "t" );
5547 value = strdup( "f" );
5549 value = jsonObjectToSimpleString( field_object );
5550 if( field_object && JSON_NUMBER == field_object->type )
5551 value_is_numeric = 1;
5554 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5555 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5557 if( !field_object || field_object->type == JSON_NULL ) {
5558 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5559 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5563 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5564 buffer_fadd( sql, " %s = NULL", field_name );
5567 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5571 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5573 const char* numtype = get_datatype( field_def );
5574 if( !strncmp( numtype, "INT", 3 ) ) {
5575 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5576 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5577 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5579 // Must really be intended as a string, so quote it
5580 if( dbi_conn_quote_string( dbhandle, &value )) {
5581 buffer_fadd( sql, " %s = %s", field_name, value );
5583 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5584 modulename, value );
5585 osrfAppSessionStatus(
5587 OSRF_STATUS_INTERNALSERVERERROR,
5588 "osrfMethodException",
5590 "Error quoting string -- please see the error log for more details"
5594 osrfHashIteratorFree( field_itr );
5596 osrfAppRespondComplete( ctx, NULL );
5601 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5604 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5608 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5609 buffer_fadd( sql, " %s = %s", field_name, value );
5611 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5612 osrfAppSessionStatus(
5614 OSRF_STATUS_INTERNALSERVERERROR,
5615 "osrfMethodException",
5617 "Error quoting string -- please see the error log for more details"
5621 osrfHashIteratorFree( field_itr );
5623 osrfAppRespondComplete( ctx, NULL );
5632 osrfHashIteratorFree( field_itr );
5634 jsonObject* obj = jsonNewObject( id );
5636 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5637 dbi_conn_quote_string( dbhandle, &id );
5639 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5641 char* query = buffer_release( sql );
5642 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5644 dbi_result result = dbi_conn_query( dbhandle, query );
5648 jsonObjectFree( obj );
5649 obj = jsonNewObject( NULL );
5652 "%s ERROR updating %s object with %s = %s",
5654 osrfHashGet( meta, "fieldmapper" ),
5661 osrfAppRespondComplete( ctx, obj );
5662 jsonObjectFree( obj );
5666 int doDelete( osrfMethodContext* ctx ) {
5667 if( osrfMethodVerifyContext( ctx )) {
5668 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5673 timeout_needs_resetting = 1;
5675 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5677 if( getXactId( ctx ) == NULL ) {
5678 osrfAppSessionStatus(
5680 OSRF_STATUS_BADREQUEST,
5681 "osrfMethodException",
5683 "No active transaction -- required for DELETE"
5685 osrfAppRespondComplete( ctx, NULL );
5689 // The following test is harmless but redundant. If a class is
5690 // readonly, we don't register a delete method for it.
5691 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5692 osrfAppSessionStatus(
5694 OSRF_STATUS_BADREQUEST,
5695 "osrfMethodException",
5697 "Cannot DELETE readonly class"
5699 osrfAppRespondComplete( ctx, NULL );
5703 dbhandle = writehandle;
5705 char* pkey = osrfHashGet( meta, "primarykey" );
5712 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5713 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5714 osrfAppRespondComplete( ctx, NULL );
5718 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5720 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5721 osrfAppRespondComplete( ctx, NULL );
5724 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5729 "%s deleting %s object with %s = %s",
5731 osrfHashGet( meta, "fieldmapper" ),
5736 jsonObject* obj = jsonNewObject( id );
5738 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5739 dbi_conn_quote_string( writehandle, &id );
5741 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
5742 osrfHashGet( meta, "tablename" ), pkey, id );
5745 jsonObjectFree( obj );
5746 obj = jsonNewObject( NULL );
5749 "%s ERROR deleting %s object with %s = %s",
5751 osrfHashGet( meta, "fieldmapper" ),
5759 osrfAppRespondComplete( ctx, obj );
5760 jsonObjectFree( obj );
5765 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5766 @param result An iterator for a result set; we only look at the current row.
5767 @param @meta Pointer to the class metadata for the core class.
5768 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5770 If a column is not defined in the IDL, or if it has no array_position defined for it in
5771 the IDL, or if it is defined as virtual, ignore it.
5773 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5774 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5775 array_position in the IDL.
5777 A field defined in the IDL but not represented in the returned row will leave a hole
5778 in the JSON_ARRAY. In effect it will be treated as a null value.
5780 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5781 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5782 classname corresponding to the @a meta argument.
5784 The calling code is responsible for freeing the the resulting jsonObject by calling
5787 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5788 if( !( result && meta )) return NULL;
5790 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5791 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
5792 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
5794 osrfHash* fields = osrfHashGet( meta, "fields" );
5796 int columnIndex = 1;
5797 const char* columnName;
5799 /* cycle through the columns in the row returned from the database */
5800 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
5802 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5804 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5806 /* determine the field type and storage attributes */
5807 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
5808 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5810 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5811 // or if it has no sequence number there, or if it's virtual, skip it.
5812 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
5815 if( str_is_true( osrfHashGet( _f, "virtual" )))
5816 continue; // skip this column: IDL says it's virtual
5818 const char* pos = (char*) osrfHashGet( _f, "array_position" );
5819 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
5820 continue; // since we assign sequence numbers dynamically as we load the IDL.
5822 fmIndex = atoi( pos );
5823 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
5825 continue; // This field is not defined in the IDL
5828 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
5829 // sequence number from the IDL (which is likely to be different from the sequence
5830 // of columns in the SELECT clause).
5831 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5832 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
5837 case DBI_TYPE_INTEGER :
5839 if( attr & DBI_INTEGER_SIZE8 )
5840 jsonObjectSetIndex( object, fmIndex,
5841 jsonNewNumberObject(
5842 dbi_result_get_longlong_idx( result, columnIndex )));
5844 jsonObjectSetIndex( object, fmIndex,
5845 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
5849 case DBI_TYPE_DECIMAL :
5850 jsonObjectSetIndex( object, fmIndex,
5851 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
5854 case DBI_TYPE_STRING :
5859 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
5864 case DBI_TYPE_DATETIME : {
5866 char dt_string[ 256 ] = "";
5869 // Fetch the date column as a time_t
5870 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
5872 // Translate the time_t to a human-readable string
5873 if( !( attr & DBI_DATETIME_DATE )) {
5874 gmtime_r( &_tmp_dt, &gmdt );
5875 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
5876 } else if( !( attr & DBI_DATETIME_TIME )) {
5877 localtime_r( &_tmp_dt, &gmdt );
5878 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
5880 localtime_r( &_tmp_dt, &gmdt );
5881 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
5884 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
5888 case DBI_TYPE_BINARY :
5889 osrfLogError( OSRF_LOG_MARK,
5890 "Can't do binary at column %s : index %d", columnName, columnIndex );
5899 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5900 if( !result ) return NULL;
5902 jsonObject* object = jsonNewObject( NULL );
5905 char dt_string[ 256 ];
5909 int columnIndex = 1;
5911 unsigned short type;
5912 const char* columnName;
5914 /* cycle through the column list */
5915 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
5917 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5919 fmIndex = -1; // reset the position
5921 /* determine the field type and storage attributes */
5922 type = dbi_result_get_field_type_idx( result, columnIndex );
5923 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5925 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5926 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
5931 case DBI_TYPE_INTEGER :
5933 if( attr & DBI_INTEGER_SIZE8 )
5934 jsonObjectSetKey( object, columnName,
5935 jsonNewNumberObject( dbi_result_get_longlong_idx(
5936 result, columnIndex )) );
5938 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
5939 dbi_result_get_int_idx( result, columnIndex )) );
5942 case DBI_TYPE_DECIMAL :
5943 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
5944 dbi_result_get_double_idx( result, columnIndex )) );
5947 case DBI_TYPE_STRING :
5948 jsonObjectSetKey( object, columnName,
5949 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
5952 case DBI_TYPE_DATETIME :
5954 memset( dt_string, '\0', sizeof( dt_string ));
5955 memset( &gmdt, '\0', sizeof( gmdt ));
5957 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
5959 if( !( attr & DBI_DATETIME_DATE )) {
5960 gmtime_r( &_tmp_dt, &gmdt );
5961 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
5962 } else if( !( attr & DBI_DATETIME_TIME )) {
5963 localtime_r( &_tmp_dt, &gmdt );
5964 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
5966 localtime_r( &_tmp_dt, &gmdt );
5967 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
5970 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
5973 case DBI_TYPE_BINARY :
5974 osrfLogError( OSRF_LOG_MARK,
5975 "Can't do binary at column %s : index %d", columnName, columnIndex );
5979 } // end while loop traversing result
5984 // Interpret a string as true or false
5985 int str_is_true( const char* str ) {
5986 if( NULL == str || strcasecmp( str, "true" ) )
5992 // Interpret a jsonObject as true or false
5993 static int obj_is_true( const jsonObject* obj ) {
5996 else switch( obj->type )
6004 if( strcasecmp( obj->value.s, "true" ) )
6008 case JSON_NUMBER : // Support 1/0 for perl's sake
6009 if( jsonObjectGetNumber( obj ) == 1.0 )
6018 // Translate a numeric code into a text string identifying a type of
6019 // jsonObject. To be used for building error messages.
6020 static const char* json_type( int code ) {
6026 return "JSON_ARRAY";
6028 return "JSON_STRING";
6030 return "JSON_NUMBER";
6036 return "(unrecognized)";
6040 // Extract the "primitive" attribute from an IDL field definition.
6041 // If we haven't initialized the app, then we must be running in
6042 // some kind of testbed. In that case, default to "string".
6043 static const char* get_primitive( osrfHash* field ) {
6044 const char* s = osrfHashGet( field, "primitive" );
6046 if( child_initialized )
6049 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6051 osrfHashGet( field, "name" )
6059 // Extract the "datatype" attribute from an IDL field definition.
6060 // If we haven't initialized the app, then we must be running in
6061 // some kind of testbed. In that case, default to to NUMERIC,
6062 // since we look at the datatype only for numbers.
6063 static const char* get_datatype( osrfHash* field ) {
6064 const char* s = osrfHashGet( field, "datatype" );
6066 if( child_initialized )
6069 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6071 osrfHashGet( field, "name" )
6080 @brief Determine whether a string is potentially a valid SQL identifier.
6081 @param s The identifier to be tested.
6082 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6084 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6085 need to follow all the rules exactly, such as requiring that the first character not
6088 We allow leading and trailing white space. In between, we do not allow punctuation
6089 (except for underscores and dollar signs), control characters, or embedded white space.
6091 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6092 for the foreseeable future such quoted identifiers are not likely to be an issue.
6094 static int is_identifier( const char* s) {
6098 // Skip leading white space
6099 while( isspace( (unsigned char) *s ) )
6103 return 0; // Nothing but white space? Not okay.
6105 // Check each character until we reach white space or
6106 // end-of-string. Letters, digits, underscores, and
6107 // dollar signs are okay. With the exception of periods
6108 // (as in schema.identifier), control characters and other
6109 // punctuation characters are not okay. Anything else
6110 // is okay -- it could for example be part of a multibyte
6111 // UTF8 character such as a letter with diacritical marks,
6112 // and those are allowed.
6114 if( isalnum( (unsigned char) *s )
6118 ; // Fine; keep going
6119 else if( ispunct( (unsigned char) *s )
6120 || iscntrl( (unsigned char) *s ) )
6123 } while( *s && ! isspace( (unsigned char) *s ) );
6125 // If we found any white space in the above loop,
6126 // the rest had better be all white space.
6128 while( isspace( (unsigned char) *s ) )
6132 return 0; // White space was embedded within non-white space
6138 @brief Determine whether to accept a character string as a comparison operator.
6139 @param op The candidate comparison operator.
6140 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6142 We don't validate the operator for real. We just make sure that it doesn't contain
6143 any semicolons or white space (with special exceptions for a few specific operators).
6144 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6145 space but it's still not a valid operator, then the database will complain.
6147 Another approach would be to compare the string against a short list of approved operators.
6148 We don't do that because we want to allow custom operators like ">100*", which at this
6149 writing would be difficult or impossible to express otherwise in a JSON query.
6151 static int is_good_operator( const char* op ) {
6152 if( !op ) return 0; // Sanity check
6156 if( isspace( (unsigned char) *s ) ) {
6157 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6158 // and IS NOT DISTINCT FROM.
6159 if( !strcasecmp( op, "similar to" ) )
6161 else if( !strcasecmp( op, "is distinct from" ) )
6163 else if( !strcasecmp( op, "is not distinct from" ) )
6168 else if( ';' == *s )
6176 @name Query Frame Management
6178 The following machinery supports a stack of query frames for use by SELECT().
6180 A query frame caches information about one level of a SELECT query. When we enter
6181 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6183 The query frame stores information about the core class, and about any joined classes
6186 The main purpose is to map table aliases to classes and tables, so that a query can
6187 join to the same table more than once. A secondary goal is to reduce the number of
6188 lookups in the IDL by caching the results.
6192 #define STATIC_CLASS_INFO_COUNT 3
6194 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6197 @brief Allocate a ClassInfo as raw memory.
6198 @return Pointer to the newly allocated ClassInfo.
6200 Except for the in_use flag, which is used only by the allocation and deallocation
6201 logic, we don't initialize the ClassInfo here.
6203 static ClassInfo* allocate_class_info( void ) {
6204 // In order to reduce the number of mallocs and frees, we return a static
6205 // instance of ClassInfo, if we can find one that we're not already using.
6206 // We rely on the fact that the compiler will implicitly initialize the
6207 // static instances so that in_use == 0.
6210 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6211 if( ! static_class_info[ i ].in_use ) {
6212 static_class_info[ i ].in_use = 1;
6213 return static_class_info + i;
6217 // The static ones are all in use. Malloc one.
6219 return safe_malloc( sizeof( ClassInfo ) );
6223 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6224 @param info Pointer to the ClassInfo to be cleared.
6226 static void clear_class_info( ClassInfo* info ) {
6231 // Free any malloc'd strings
6233 if( info->alias != info->alias_store )
6234 free( info->alias );
6236 if( info->class_name != info->class_name_store )
6237 free( info->class_name );
6239 free( info->source_def );
6241 info->alias = info->class_name = info->source_def = NULL;
6246 @brief Free a ClassInfo and everything it owns.
6247 @param info Pointer to the ClassInfo to be freed.
6249 static void free_class_info( ClassInfo* info ) {
6254 clear_class_info( info );
6256 // If it's one of the static instances, just mark it as not in use
6259 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6260 if( info == static_class_info + i ) {
6261 static_class_info[ i ].in_use = 0;
6266 // Otherwise it must have been malloc'd, so free it
6272 @brief Populate an already-allocated ClassInfo.
6273 @param info Pointer to the ClassInfo to be populated.
6274 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6276 @param class Name of the class.
6277 @return Zero if successful, or 1 if not.
6279 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6280 the relevant portions of the IDL for the specified class.
6282 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6285 osrfLogError( OSRF_LOG_MARK,
6286 "%s ERROR: No ClassInfo available to populate", modulename );
6287 info->alias = info->class_name = info->source_def = NULL;
6288 info->class_def = info->fields = info->links = NULL;
6293 osrfLogError( OSRF_LOG_MARK,
6294 "%s ERROR: No class name provided for lookup", modulename );
6295 info->alias = info->class_name = info->source_def = NULL;
6296 info->class_def = info->fields = info->links = NULL;
6300 // Alias defaults to class name if not supplied
6301 if( ! alias || ! alias[ 0 ] )
6304 // Look up class info in the IDL
6305 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6307 osrfLogError( OSRF_LOG_MARK,
6308 "%s ERROR: Class %s not defined in IDL", modulename, class );
6309 info->alias = info->class_name = info->source_def = NULL;
6310 info->class_def = info->fields = info->links = NULL;
6312 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6313 osrfLogError( OSRF_LOG_MARK,
6314 "%s ERROR: Class %s is defined as virtual", modulename, class );
6315 info->alias = info->class_name = info->source_def = NULL;
6316 info->class_def = info->fields = info->links = NULL;
6320 osrfHash* links = osrfHashGet( class_def, "links" );
6322 osrfLogError( OSRF_LOG_MARK,
6323 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6324 info->alias = info->class_name = info->source_def = NULL;
6325 info->class_def = info->fields = info->links = NULL;
6329 osrfHash* fields = osrfHashGet( class_def, "fields" );
6331 osrfLogError( OSRF_LOG_MARK,
6332 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6333 info->alias = info->class_name = info->source_def = NULL;
6334 info->class_def = info->fields = info->links = NULL;
6338 char* source_def = oilsGetRelation( class_def );
6342 // We got everything we need, so populate the ClassInfo
6343 if( strlen( alias ) > ALIAS_STORE_SIZE )
6344 info->alias = strdup( alias );
6346 strcpy( info->alias_store, alias );
6347 info->alias = info->alias_store;
6350 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6351 info->class_name = strdup( class );
6353 strcpy( info->class_name_store, class );
6354 info->class_name = info->class_name_store;
6357 info->source_def = source_def;
6359 info->class_def = class_def;
6360 info->links = links;
6361 info->fields = fields;
6366 #define STATIC_FRAME_COUNT 3
6368 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6371 @brief Allocate a QueryFrame as raw memory.
6372 @return Pointer to the newly allocated QueryFrame.
6374 Except for the in_use flag, which is used only by the allocation and deallocation
6375 logic, we don't initialize the QueryFrame here.
6377 static QueryFrame* allocate_frame( void ) {
6378 // In order to reduce the number of mallocs and frees, we return a static
6379 // instance of QueryFrame, if we can find one that we're not already using.
6380 // We rely on the fact that the compiler will implicitly initialize the
6381 // static instances so that in_use == 0.
6384 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6385 if( ! static_frame[ i ].in_use ) {
6386 static_frame[ i ].in_use = 1;
6387 return static_frame + i;
6391 // The static ones are all in use. Malloc one.
6393 return safe_malloc( sizeof( QueryFrame ) );
6397 @brief Free a QueryFrame, and all the memory it owns.
6398 @param frame Pointer to the QueryFrame to be freed.
6400 static void free_query_frame( QueryFrame* frame ) {
6405 clear_class_info( &frame->core );
6407 // Free the join list
6409 ClassInfo* info = frame->join_list;
6412 free_class_info( info );
6416 frame->join_list = NULL;
6419 // If the frame is a static instance, just mark it as unused
6421 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6422 if( frame == static_frame + i ) {
6423 static_frame[ i ].in_use = 0;
6428 // Otherwise it must have been malloc'd, so free it
6434 @brief Search a given QueryFrame for a specified alias.
6435 @param frame Pointer to the QueryFrame to be searched.
6436 @param target The alias for which to search.
6437 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6439 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6440 if( ! frame || ! target ) {
6444 ClassInfo* found_class = NULL;
6446 if( !strcmp( target, frame->core.alias ) )
6447 return &(frame->core);
6449 ClassInfo* curr_class = frame->join_list;
6450 while( curr_class ) {
6451 if( strcmp( target, curr_class->alias ) )
6452 curr_class = curr_class->next;
6454 found_class = curr_class;
6464 @brief Push a new (blank) QueryFrame onto the stack.
6466 static void push_query_frame( void ) {
6467 QueryFrame* frame = allocate_frame();
6468 frame->join_list = NULL;
6469 frame->next = curr_query;
6471 // Initialize the ClassInfo for the core class
6472 ClassInfo* core = &frame->core;
6473 core->alias = core->class_name = core->source_def = NULL;
6474 core->class_def = core->fields = core->links = NULL;
6480 @brief Pop a QueryFrame off the stack and destroy it.
6482 static void pop_query_frame( void ) {
6487 QueryFrame* popped = curr_query;
6488 curr_query = popped->next;
6490 free_query_frame( popped );
6494 @brief Populate the ClassInfo for the core class.
6495 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6496 class name as an alias.
6497 @param class_name Name of the core class.
6498 @return Zero if successful, or 1 if not.
6500 Populate the ClassInfo of the core class with copies of the alias and class name, and
6501 with pointers to the relevant portions of the IDL for the core class.
6503 static int add_query_core( const char* alias, const char* class_name ) {
6506 if( ! curr_query ) {
6507 osrfLogError( OSRF_LOG_MARK,
6508 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6510 } else if( curr_query->core.alias ) {
6511 osrfLogError( OSRF_LOG_MARK,
6512 "%s ERROR: Core class %s already populated as %s",
6513 modulename, curr_query->core.class_name, curr_query->core.alias );
6517 build_class_info( &curr_query->core, alias, class_name );
6518 if( curr_query->core.alias )
6521 osrfLogError( OSRF_LOG_MARK,
6522 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6528 @brief Search the current QueryFrame for a specified alias.
6529 @param target The alias for which to search.
6530 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6532 static inline ClassInfo* search_alias( const char* target ) {
6533 return search_alias_in_frame( curr_query, target );
6537 @brief Search all levels of query for a specified alias, starting with the current query.
6538 @param target The alias for which to search.
6539 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6541 static ClassInfo* search_all_alias( const char* target ) {
6542 ClassInfo* found_class = NULL;
6543 QueryFrame* curr_frame = curr_query;
6545 while( curr_frame ) {
6546 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6549 curr_frame = curr_frame->next;
6556 @brief Add a class to the list of classes joined to the current query.
6557 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6558 the class name as an alias.
6559 @param classname The name of the class to be added.
6560 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6562 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6564 if( ! classname || ! *classname ) { // sanity check
6565 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6572 const ClassInfo* conflict = search_alias( alias );
6574 osrfLogError( OSRF_LOG_MARK,
6575 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6576 modulename, alias, conflict->class_name );
6580 ClassInfo* info = allocate_class_info();
6582 if( build_class_info( info, alias, classname ) ) {
6583 free_class_info( info );
6587 // Add the new ClassInfo to the join list of the current QueryFrame
6588 info->next = curr_query->join_list;
6589 curr_query->join_list = info;
6595 @brief Destroy all nodes on the query stack.
6597 static void clear_query_stack( void ) {