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 except for inserts, meaning that for local or foreign
1271 // contexts we will do another lookup of the current row, even if we already have a
1272 // previously fetched row image, because the row image in hand may not include the
1273 // foreign key(s) that we need.
1275 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1276 // but they aren't implemented yet.
1279 if( *method_type == 's' || *method_type == 'i' ) {
1280 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1282 } else if( *method_type == 'u' || *method_type == 'd' ) {
1283 fetch = 1; // MUST go to the db for the object for update and delete
1286 // Get the appropriate permacrud entry from the IDL, depending on method type
1287 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1289 // No permacrud for this method type on this class
1291 growing_buffer* msg = buffer_init( 128 );
1294 "%s: %s on class %s has no permacrud IDL entry",
1296 osrfHashGet( method_metadata, "methodtype" ),
1297 osrfHashGet( class, "classname" )
1300 char* m = buffer_release( msg );
1301 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1302 "osrfMethodException", ctx->request, m );
1309 // Get the user id, and make sure the user is logged in
1310 const jsonObject* user = verifyUserPCRUD( ctx );
1312 return 0; // Not logged in? No access.
1314 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1316 // Get a list of permissions from the permacrud entry.
1317 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1319 // Build a list of org units that own the row. This is fairly convoluted because there
1320 // are several different ways that an org unit may own the row, as defined by the
1323 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1324 // identifying an owning org_unit..
1325 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1327 // Foreign context adds a layer of indirection. The row points to some other row that
1328 // an org unit may own. The "jump" attribute, if present, adds another layer of
1330 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1332 // The following string array stores the list of org units. (We don't have a thingie
1333 // for storing lists of integers, so we fake it with a list of strings.)
1334 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1337 const char* pkey_value = NULL;
1338 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1339 // If the global_required attribute is present and true, then the only owning
1340 // org unit is the root org unit, i.e. the one with no parent.
1341 osrfLogDebug( OSRF_LOG_MARK,
1342 "global-level permissions required, fetching top of the org tree" );
1344 // check for perm at top of org tree
1345 const char* org_tree_root_id = org_tree_root( ctx );
1346 if( org_tree_root_id ) {
1347 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1348 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1350 osrfStringArrayFree( context_org_array );
1355 // If the global_required attribute is absent or false, then we look for
1356 // local and/or foreign context. In order to find the relevant foreign
1357 // keys, we must either read the relevant row from the database, or look at
1358 // the image of the row that we already have in memory.
1360 // (Herein lies a bug. Even if we have an image of the row in memory, that
1361 // image may not include the foreign key column(s) that we need.)
1363 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1364 "fetching context org ids" );
1365 const char* pkey = osrfHashGet( class, "primarykey" );
1366 jsonObject *param = NULL;
1368 if( obj->classname ) {
1369 pkey_value = oilsFMGetStringConst( obj, pkey );
1371 param = jsonObjectClone( obj );
1372 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1375 pkey_value = jsonObjectGetString( obj );
1377 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1378 "of %s and retrieving from the database", pkey_value );
1382 // Fetch the row so that we can look at the foreign key(s)
1383 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1384 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1385 jsonObjectFree( _tmp_params );
1387 param = jsonObjectExtractIndex( _list, 0 );
1388 jsonObjectFree( _list );
1392 // The row doesn't exist. Complain, and deny access.
1393 osrfLogDebug( OSRF_LOG_MARK,
1394 "Object not found in the database with primary key %s of %s",
1397 growing_buffer* msg = buffer_init( 128 );
1400 "%s: no object found with primary key %s of %s",
1406 char* m = buffer_release( msg );
1407 osrfAppSessionStatus(
1409 OSRF_STATUS_INTERNALSERVERERROR,
1410 "osrfMethodException",
1419 if( local_context && local_context->size > 0 ) {
1420 // The IDL provides a list of column names for the foreign keys denoting
1421 // local context. Look up the value of each one, and if it isn't null,
1422 // add it to the list of org units.
1423 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1424 local_context->size );
1426 const char* lcontext = NULL;
1427 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1428 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1429 if( fkey_value ) { // if not null
1430 osrfStringArrayAdd( context_org_array, fkey_value );
1433 "adding class-local field %s (value: %s) to the context org list",
1435 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1441 if( foreign_context ) {
1442 unsigned long class_count = osrfHashGetCount( foreign_context );
1443 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1445 if( class_count > 0 ) {
1447 // The IDL provides a list of foreign key columns pointing to rows that
1448 // an org unit may own. Follow each link, identify the owning org unit,
1449 // and add it to the list.
1450 osrfHash* fcontext = NULL;
1451 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1452 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1453 // For each class to which a foreign key points:
1454 const char* class_name = osrfHashIteratorKey( class_itr );
1455 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1459 "%d foreign context fields(s) specified for class %s",
1460 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1464 // Get the name of the key field in the foreign table
1465 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1467 // Get the value of the foreign key pointing to the foreign table
1468 char* foreign_pkey_value =
1469 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1470 if( !foreign_pkey_value )
1471 continue; // Foreign key value is null; skip it
1473 // Look up the row to which the foreign key points
1474 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1475 jsonObject* _list = doFieldmapperSearch(
1476 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1478 jsonObject* _fparam = NULL;
1479 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1480 _fparam = jsonObjectExtractIndex( _list, 0 );
1482 jsonObjectFree( _tmp_params );
1483 jsonObjectFree( _list );
1485 // At this point _fparam either points to the row identified by the
1486 // foreign key, or it's NULL (no such row found).
1488 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1490 const char* bad_class = NULL; // For noting failed lookups
1492 bad_class = class_name; // Referenced row not found
1493 else if( jump_list ) {
1494 // Follow a chain of rows, linked by foreign keys, to find an owner
1495 const char* flink = NULL;
1497 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1498 // For each entry in the jump list. Each entry (i.e. flink) is
1499 // the name of a foreign key column in the current row.
1501 // From the IDL, get the linkage information for the next jump
1502 osrfHash* foreign_link_hash =
1503 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1505 // Get the class metadata for the class
1506 // to which the foreign key points
1507 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1508 osrfHashGet( foreign_link_hash, "class" ));
1510 // Get the name of the referenced key of that class
1511 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1513 // Get the value of the foreign key pointing to that class
1514 free( foreign_pkey_value );
1515 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1516 if( !foreign_pkey_value )
1517 break; // Foreign key is null; quit looking
1519 // Build a WHERE clause for the lookup
1520 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1523 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1524 _tmp_params, NULL, &err );
1526 // Get the resulting row
1527 jsonObjectFree( _fparam );
1528 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1529 _fparam = jsonObjectExtractIndex( _list, 0 );
1531 // Referenced row not found
1533 bad_class = osrfHashGet( foreign_link_hash, "class" );
1536 jsonObjectFree( _tmp_params );
1537 jsonObjectFree( _list );
1543 // We had a foreign key pointing to such-and-such a row, but then
1544 // we couldn't fetch that row. The data in the database are in an
1545 // inconsistent state; the database itself may even be corrupted.
1546 growing_buffer* msg = buffer_init( 128 );
1549 "%s: no object of class %s found with primary key %s of %s",
1553 foreign_pkey_value ? foreign_pkey_value : "(null)"
1556 char* m = buffer_release( msg );
1557 osrfAppSessionStatus(
1559 OSRF_STATUS_INTERNALSERVERERROR,
1560 "osrfMethodException",
1566 osrfHashIteratorFree( class_itr );
1567 free( foreign_pkey_value );
1568 jsonObjectFree( param );
1573 free( foreign_pkey_value );
1576 // Examine each context column of the foreign row,
1577 // and add its value to the list of org units.
1579 const char* foreign_field = NULL;
1580 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1581 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1582 osrfStringArrayAdd( context_org_array,
1583 oilsFMGetStringConst( _fparam, foreign_field ));
1584 osrfLogDebug( OSRF_LOG_MARK,
1585 "adding foreign class %s field %s (value: %s) "
1586 "to the context org list",
1589 osrfStringArrayGetString(
1590 context_org_array, context_org_array->size - 1 )
1594 jsonObjectFree( _fparam );
1598 osrfHashIteratorFree( class_itr );
1602 jsonObjectFree( param );
1605 const char* context_org = NULL;
1606 const char* perm = NULL;
1609 if( permission->size == 0 ) {
1610 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1614 // For every combination of permission and context org unit: call a stored procedure
1615 // to determine if the user has this permission in the context of this org unit.
1616 // If the answer is yes at any point, then we're done, and the user has permission.
1617 // In other words permissions are additive.
1619 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1621 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1627 "Checking object permission [%s] for user %d "
1628 "on object %s (class %s) at org %d",
1632 osrfHashGet( class, "classname" ),
1636 result = dbi_conn_queryf(
1638 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1641 osrfHashGet( class, "classname" ),
1649 "Received a result for object permission [%s] "
1650 "for user %d on object %s (class %s) at org %d",
1654 osrfHashGet( class, "classname" ),
1658 if( dbi_result_first_row( result )) {
1659 jsonObject* return_val = oilsMakeJSONFromResult( result );
1660 const char* has_perm = jsonObjectGetString(
1661 jsonObjectGetKeyConst( return_val, "has_perm" ));
1665 "Status of object permission [%s] for user %d "
1666 "on object %s (class %s) at org %d is %s",
1670 osrfHashGet(class, "classname"),
1675 if( *has_perm == 't' )
1677 jsonObjectFree( return_val );
1680 dbi_result_free( result );
1686 osrfLogDebug( OSRF_LOG_MARK,
1687 "Checking non-object permission [%s] for user %d at org %d",
1688 perm, userid, atoi(context_org) );
1689 result = dbi_conn_queryf(
1691 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1698 osrfLogDebug( OSRF_LOG_MARK,
1699 "Received a result for permission [%s] for user %d at org %d",
1700 perm, userid, atoi( context_org ));
1701 if( dbi_result_first_row( result )) {
1702 jsonObject* return_val = oilsMakeJSONFromResult( result );
1703 const char* has_perm = jsonObjectGetString(
1704 jsonObjectGetKeyConst( return_val, "has_perm" ));
1705 osrfLogDebug( OSRF_LOG_MARK,
1706 "Status of permission [%s] for user %d at org %d is [%s]",
1707 perm, userid, atoi( context_org ), has_perm );
1708 if( *has_perm == 't' )
1710 jsonObjectFree( return_val );
1713 dbi_result_free( result );
1723 osrfStringArrayFree( context_org_array );
1729 @brief Look up the root of the org_unit tree.
1730 @param ctx Pointer to the method context.
1731 @return The id of the root org unit, as a character string.
1733 Query actor.org_unit where parent_ou is null, and return the id as a string.
1735 This function assumes that there is only one root org unit, i.e. that we
1736 have a single tree, not a forest.
1738 The calling code is responsible for freeing the returned string.
1740 static const char* org_tree_root( osrfMethodContext* ctx ) {
1742 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1743 static time_t last_lookup_time = 0;
1744 time_t current_time = time( NULL );
1746 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1747 // We successfully looked this up less than an hour ago.
1748 // It's not likely to have changed since then.
1749 return strdup( cached_root_id );
1751 last_lookup_time = current_time;
1754 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1755 jsonObject* result = doFieldmapperSearch(
1756 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1757 jsonObjectFree( where_clause );
1759 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1762 jsonObjectFree( result );
1764 growing_buffer* msg = buffer_init( 128 );
1765 OSRF_BUFFER_ADD( msg, modulename );
1766 OSRF_BUFFER_ADD( msg,
1767 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1769 char* m = buffer_release( msg );
1770 osrfAppSessionStatus( ctx->session,
1771 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1774 cached_root_id[ 0 ] = '\0';
1778 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
1779 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1781 strcpy( cached_root_id, root_org_unit_id );
1782 jsonObjectFree( result );
1783 return cached_root_id;
1787 @brief Create a JSON_HASH with a single key/value pair.
1788 @param key The key of the key/value pair.
1789 @param value the value of the key/value pair.
1790 @return Pointer to a newly created jsonObject of type JSON_HASH.
1792 The value of the key/value is either a string or (if @a value is NULL) a null.
1794 static jsonObject* single_hash( const char* key, const char* value ) {
1796 if( ! key ) key = "";
1798 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1799 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1804 int doCreate( osrfMethodContext* ctx ) {
1805 if(osrfMethodVerifyContext( ctx )) {
1806 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1811 timeout_needs_resetting = 1;
1813 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1814 jsonObject* target = NULL;
1815 jsonObject* options = NULL;
1817 if( enforce_pcrud ) {
1818 target = jsonObjectGetIndex( ctx->params, 1 );
1819 options = jsonObjectGetIndex( ctx->params, 2 );
1821 target = jsonObjectGetIndex( ctx->params, 0 );
1822 options = jsonObjectGetIndex( ctx->params, 1 );
1825 if( !verifyObjectClass( ctx, target )) {
1826 osrfAppRespondComplete( ctx, NULL );
1830 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1832 const char* trans_id = getXactId( ctx );
1834 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1836 osrfAppSessionStatus(
1838 OSRF_STATUS_BADREQUEST,
1839 "osrfMethodException",
1841 "No active transaction -- required for CREATE"
1843 osrfAppRespondComplete( ctx, NULL );
1847 // The following test is harmless but redundant. If a class is
1848 // readonly, we don't register a create method for it.
1849 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1850 osrfAppSessionStatus(
1852 OSRF_STATUS_BADREQUEST,
1853 "osrfMethodException",
1855 "Cannot INSERT readonly class"
1857 osrfAppRespondComplete( ctx, NULL );
1861 // Set the last_xact_id
1862 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1864 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1865 trans_id, target->classname, index);
1866 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1869 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1871 dbhandle = writehandle;
1873 osrfHash* fields = osrfHashGet( meta, "fields" );
1874 char* pkey = osrfHashGet( meta, "primarykey" );
1875 char* seq = osrfHashGet( meta, "sequence" );
1877 growing_buffer* table_buf = buffer_init( 128 );
1878 growing_buffer* col_buf = buffer_init( 128 );
1879 growing_buffer* val_buf = buffer_init( 128 );
1881 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1882 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1883 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1884 buffer_add( val_buf,"VALUES (" );
1888 osrfHash* field = NULL;
1889 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1890 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1892 const char* field_name = osrfHashIteratorKey( field_itr );
1894 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1897 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1900 if( field_object && field_object->classname ) {
1901 value = oilsFMGetString(
1903 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
1905 } else if( field_object && JSON_BOOL == field_object->type ) {
1906 if( jsonBoolIsTrue( field_object ) )
1907 value = strdup( "t" );
1909 value = strdup( "f" );
1911 value = jsonObjectToSimpleString( field_object );
1917 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1918 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1921 buffer_add( col_buf, field_name );
1923 if( !field_object || field_object->type == JSON_NULL ) {
1924 buffer_add( val_buf, "DEFAULT" );
1926 } else if( !strcmp( get_primitive( field ), "number" )) {
1927 const char* numtype = get_datatype( field );
1928 if( !strcmp( numtype, "INT8" )) {
1929 buffer_fadd( val_buf, "%lld", atoll( value ));
1931 } else if( !strcmp( numtype, "INT" )) {
1932 buffer_fadd( val_buf, "%d", atoi( value ));
1934 } else if( !strcmp( numtype, "NUMERIC" )) {
1935 buffer_fadd( val_buf, "%f", atof( value ));
1938 if( dbi_conn_quote_string( writehandle, &value )) {
1939 OSRF_BUFFER_ADD( val_buf, value );
1942 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
1943 osrfAppSessionStatus(
1945 OSRF_STATUS_INTERNALSERVERERROR,
1946 "osrfMethodException",
1948 "Error quoting string -- please see the error log for more details"
1951 buffer_free( table_buf );
1952 buffer_free( col_buf );
1953 buffer_free( val_buf );
1954 osrfAppRespondComplete( ctx, NULL );
1962 osrfHashIteratorFree( field_itr );
1964 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1965 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1967 char* table_str = buffer_release( table_buf );
1968 char* col_str = buffer_release( col_buf );
1969 char* val_str = buffer_release( val_buf );
1970 growing_buffer* sql = buffer_init( 128 );
1971 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1976 char* query = buffer_release( sql );
1978 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
1980 jsonObject* obj = NULL;
1983 dbi_result result = dbi_conn_query( writehandle, query );
1985 obj = jsonNewObject( NULL );
1988 "%s ERROR inserting %s object using query [%s]",
1990 osrfHashGet(meta, "fieldmapper"),
1993 osrfAppSessionStatus(
1995 OSRF_STATUS_INTERNALSERVERERROR,
1996 "osrfMethodException",
1998 "INSERT error -- please see the error log for more details"
2003 char* id = oilsFMGetString( target, pkey );
2005 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2006 growing_buffer* _id = buffer_init( 10 );
2007 buffer_fadd( _id, "%lld", new_id );
2008 id = buffer_release( _id );
2011 // Find quietness specification, if present
2012 const char* quiet_str = NULL;
2014 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2016 quiet_str = jsonObjectGetString( quiet_obj );
2019 if( str_is_true( quiet_str )) { // if quietness is specified
2020 obj = jsonNewObject( id );
2024 // Fetch the row that we just inserted, so that we can return it to the client
2025 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2026 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2029 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2033 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2035 jsonObjectFree( list );
2036 jsonObjectFree( where_clause );
2043 osrfAppRespondComplete( ctx, obj );
2044 jsonObjectFree( obj );
2049 @brief Implement the retrieve method.
2050 @param ctx Pointer to the method context.
2051 @param err Pointer through which to return an error code.
2052 @return If successful, a pointer to the result to be returned to the client;
2055 From the method's class, fetch a row with a specified value in the primary key. This
2056 method relies on the database design convention that a primary key consists of a single
2060 - authkey (PCRUD only)
2061 - value of the primary key for the desired row, for building the WHERE clause
2062 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2064 Return to client: One row from the query.
2066 int doRetrieve( osrfMethodContext* ctx ) {
2067 if(osrfMethodVerifyContext( ctx )) {
2068 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2073 timeout_needs_resetting = 1;
2078 if( enforce_pcrud ) {
2083 // Get the class metadata
2084 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2086 // Get the value of the primary key, from a method parameter
2087 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2091 "%s retrieving %s object with primary key value of %s",
2093 osrfHashGet( class_def, "fieldmapper" ),
2094 jsonObjectGetString( id_obj )
2097 // Build a WHERE clause based on the key value
2098 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2101 osrfHashGet( class_def, "primarykey" ), // name of key column
2102 jsonObjectClone( id_obj ) // value of key column
2105 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2109 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2111 jsonObjectFree( where_clause );
2113 osrfAppRespondComplete( ctx, NULL );
2117 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2118 jsonObjectFree( list );
2120 if( enforce_pcrud ) {
2121 if(!verifyObjectPCRUD( ctx, obj )) {
2122 jsonObjectFree( obj );
2124 growing_buffer* msg = buffer_init( 128 );
2125 OSRF_BUFFER_ADD( msg, modulename );
2126 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2128 char* m = buffer_release( msg );
2129 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2133 osrfAppRespondComplete( ctx, NULL );
2138 osrfAppRespondComplete( ctx, obj );
2139 jsonObjectFree( obj );
2144 @brief Translate a numeric value to a string representation for the database.
2145 @param field Pointer to the IDL field definition.
2146 @param value Pointer to a jsonObject holding the value of a field.
2147 @return Pointer to a newly allocated string.
2149 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2150 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2151 or (what is worse) valid SQL that is wrong.
2153 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2155 The calling code is responsible for freeing the resulting string by calling free().
2157 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2158 growing_buffer* val_buf = buffer_init( 32 );
2159 const char* numtype = get_datatype( field );
2161 // For historical reasons the following contains cruft that could be cleaned up.
2162 if( !strncmp( numtype, "INT", 3 ) ) {
2163 if( value->type == JSON_NUMBER )
2164 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2165 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2167 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2170 } else if( !strcmp( numtype, "NUMERIC" )) {
2171 if( value->type == JSON_NUMBER )
2172 buffer_fadd( val_buf, jsonObjectGetString( value ));
2174 buffer_fadd( val_buf, jsonObjectGetString( value ));
2178 // Presumably this was really intended to be a string, so quote it
2179 char* str = jsonObjectToSimpleString( value );
2180 if( dbi_conn_quote_string( dbhandle, &str )) {
2181 OSRF_BUFFER_ADD( val_buf, str );
2184 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2186 buffer_free( val_buf );
2191 return buffer_release( val_buf );
2194 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2195 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2196 growing_buffer* sql_buf = buffer_init( 32 );
2202 osrfHashGet( field, "name" )
2206 buffer_add( sql_buf, "IN (" );
2207 } else if( !strcasecmp( op,"not in" )) {
2208 buffer_add( sql_buf, "NOT IN (" );
2210 buffer_add( sql_buf, "IN (" );
2213 if( node->type == JSON_HASH ) {
2214 // subquery predicate
2215 char* subpred = buildQuery( ctx, node, SUBSELECT );
2217 buffer_free( sql_buf );
2221 buffer_add( sql_buf, subpred );
2224 } else if( node->type == JSON_ARRAY ) {
2225 // literal value list
2226 int in_item_index = 0;
2227 int in_item_first = 1;
2228 const jsonObject* in_item;
2229 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2234 buffer_add( sql_buf, ", " );
2237 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2238 osrfLogError( OSRF_LOG_MARK,
2239 "%s: Expected string or number within IN list; found %s",
2240 modulename, json_type( in_item->type ) );
2241 buffer_free( sql_buf );
2245 // Append the literal value -- quoted if not a number
2246 if( JSON_NUMBER == in_item->type ) {
2247 char* val = jsonNumberToDBString( field, in_item );
2248 OSRF_BUFFER_ADD( sql_buf, val );
2251 } else if( !strcmp( get_primitive( field ), "number" )) {
2252 char* val = jsonNumberToDBString( field, in_item );
2253 OSRF_BUFFER_ADD( sql_buf, val );
2257 char* key_string = jsonObjectToSimpleString( in_item );
2258 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2259 OSRF_BUFFER_ADD( sql_buf, key_string );
2262 osrfLogError( OSRF_LOG_MARK,
2263 "%s: Error quoting key string [%s]", modulename, key_string );
2265 buffer_free( sql_buf );
2271 if( in_item_first ) {
2272 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2273 buffer_free( sql_buf );
2277 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2278 modulename, json_type( node->type ));
2279 buffer_free( sql_buf );
2283 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2285 return buffer_release( sql_buf );
2288 // Receive a JSON_ARRAY representing a function call. The first
2289 // entry in the array is the function name. The rest are parameters.
2290 static char* searchValueTransform( const jsonObject* array ) {
2292 if( array->size < 1 ) {
2293 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2297 // Get the function name
2298 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2299 if( func_item->type != JSON_STRING ) {
2300 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2301 modulename, json_type( func_item->type ));
2305 growing_buffer* sql_buf = buffer_init( 32 );
2307 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2308 OSRF_BUFFER_ADD( sql_buf, "( " );
2310 // Get the parameters
2311 int func_item_index = 1; // We already grabbed the zeroth entry
2312 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2314 // Add a separator comma, if we need one
2315 if( func_item_index > 2 )
2316 buffer_add( sql_buf, ", " );
2318 // Add the current parameter
2319 if( func_item->type == JSON_NULL ) {
2320 buffer_add( sql_buf, "NULL" );
2322 char* val = jsonObjectToSimpleString( func_item );
2323 if( dbi_conn_quote_string( dbhandle, &val )) {
2324 OSRF_BUFFER_ADD( sql_buf, val );
2327 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2329 buffer_free( sql_buf );
2336 buffer_add( sql_buf, " )" );
2338 return buffer_release( sql_buf );
2341 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2342 const jsonObject* node, const char* op ) {
2344 if( ! is_good_operator( op ) ) {
2345 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2349 char* val = searchValueTransform( node );
2353 growing_buffer* sql_buf = buffer_init( 32 );
2358 osrfHashGet( field, "name" ),
2365 return buffer_release( sql_buf );
2368 // class_alias is a class name or other table alias
2369 // field is a field definition as stored in the IDL
2370 // node comes from the method parameter, and may represent an entry in the SELECT list
2371 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2372 const jsonObject* node ) {
2373 growing_buffer* sql_buf = buffer_init( 32 );
2375 const char* field_transform = jsonObjectGetString(
2376 jsonObjectGetKeyConst( node, "transform" ) );
2377 const char* transform_subcolumn = jsonObjectGetString(
2378 jsonObjectGetKeyConst( node, "result_field" ) );
2380 if( transform_subcolumn ) {
2381 if( ! is_identifier( transform_subcolumn ) ) {
2382 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2383 modulename, transform_subcolumn );
2384 buffer_free( sql_buf );
2387 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2390 if( field_transform ) {
2392 if( ! is_identifier( field_transform ) ) {
2393 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2394 modulename, field_transform );
2395 buffer_free( sql_buf );
2399 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2400 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2401 field_transform, class_alias, osrfHashGet( field, "name" ));
2403 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2404 field_transform, class_alias, osrfHashGet( field, "name" ));
2407 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2410 if( array->type != JSON_ARRAY ) {
2411 osrfLogError( OSRF_LOG_MARK,
2412 "%s: Expected JSON_ARRAY for function params; found %s",
2413 modulename, json_type( array->type ) );
2414 buffer_free( sql_buf );
2417 int func_item_index = 0;
2418 jsonObject* func_item;
2419 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2421 char* val = jsonObjectToSimpleString( func_item );
2424 buffer_add( sql_buf, ",NULL" );
2425 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2426 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2427 OSRF_BUFFER_ADD( sql_buf, val );
2429 osrfLogError( OSRF_LOG_MARK,
2430 "%s: Error quoting key string [%s]", modulename, val );
2432 buffer_free( sql_buf );
2439 buffer_add( sql_buf, " )" );
2442 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2445 if( transform_subcolumn )
2446 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2448 return buffer_release( sql_buf );
2451 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2452 const jsonObject* node, const char* op ) {
2454 if( ! is_good_operator( op ) ) {
2455 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2459 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2460 if( ! field_transform )
2463 int extra_parens = 0; // boolean
2465 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2467 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2469 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2471 free( field_transform );
2475 } else if( value_obj->type == JSON_ARRAY ) {
2476 value = searchValueTransform( value_obj );
2478 osrfLogError( OSRF_LOG_MARK,
2479 "%s: Error building value transform for field transform", modulename );
2480 free( field_transform );
2483 } else if( value_obj->type == JSON_HASH ) {
2484 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2486 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2488 free( field_transform );
2492 } else if( value_obj->type == JSON_NUMBER ) {
2493 value = jsonNumberToDBString( field, value_obj );
2494 } else if( value_obj->type == JSON_NULL ) {
2495 osrfLogError( OSRF_LOG_MARK,
2496 "%s: Error building predicate for field transform: null value", modulename );
2497 free( field_transform );
2499 } else if( value_obj->type == JSON_BOOL ) {
2500 osrfLogError( OSRF_LOG_MARK,
2501 "%s: Error building predicate for field transform: boolean value", modulename );
2502 free( field_transform );
2505 if( !strcmp( get_primitive( field ), "number") ) {
2506 value = jsonNumberToDBString( field, value_obj );
2508 value = jsonObjectToSimpleString( value_obj );
2509 if( !dbi_conn_quote_string( dbhandle, &value )) {
2510 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2511 modulename, value );
2513 free( field_transform );
2519 const char* left_parens = "";
2520 const char* right_parens = "";
2522 if( extra_parens ) {
2527 growing_buffer* sql_buf = buffer_init( 32 );
2531 "%s%s %s %s %s %s%s",
2542 free( field_transform );
2544 return buffer_release( sql_buf );
2547 static char* searchSimplePredicate( const char* op, const char* class_alias,
2548 osrfHash* field, const jsonObject* node ) {
2550 if( ! is_good_operator( op ) ) {
2551 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2557 // Get the value to which we are comparing the specified column
2558 if( node->type != JSON_NULL ) {
2559 if( node->type == JSON_NUMBER ) {
2560 val = jsonNumberToDBString( field, node );
2561 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2562 val = jsonNumberToDBString( field, node );
2564 val = jsonObjectToSimpleString( node );
2569 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2570 // Value is not numeric; enclose it in quotes
2571 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2572 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2579 // Compare to a null value
2580 val = strdup( "NULL" );
2581 if( strcmp( op, "=" ))
2587 growing_buffer* sql_buf = buffer_init( 32 );
2588 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2589 char* pred = buffer_release( sql_buf );
2596 static char* searchBETWEENPredicate( const char* class_alias,
2597 osrfHash* field, const jsonObject* node ) {
2599 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2600 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2602 if( NULL == y_node ) {
2603 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2606 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2607 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2614 if( !strcmp( get_primitive( field ), "number") ) {
2615 x_string = jsonNumberToDBString( field, x_node );
2616 y_string = jsonNumberToDBString( field, y_node );
2619 x_string = jsonObjectToSimpleString( x_node );
2620 y_string = jsonObjectToSimpleString( y_node );
2621 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2622 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2623 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2624 modulename, x_string, y_string );
2631 growing_buffer* sql_buf = buffer_init( 32 );
2632 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2633 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2637 return buffer_release( sql_buf );
2640 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2641 jsonObject* node, osrfMethodContext* ctx ) {
2644 if( node->type == JSON_ARRAY ) { // equality IN search
2645 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2646 } else if( node->type == JSON_HASH ) { // other search
2647 jsonIterator* pred_itr = jsonNewIterator( node );
2648 if( !jsonIteratorHasNext( pred_itr ) ) {
2649 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2650 modulename, osrfHashGet(field, "name" ));
2652 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2654 // Verify that there are no additional predicates
2655 if( jsonIteratorHasNext( pred_itr ) ) {
2656 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2657 modulename, osrfHashGet(field, "name" ));
2658 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2659 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2660 else if( !(strcasecmp( pred_itr->key,"in" ))
2661 || !(strcasecmp( pred_itr->key,"not in" )) )
2662 pred = searchINPredicate(
2663 class_info->alias, field, pred_node, pred_itr->key, ctx );
2664 else if( pred_node->type == JSON_ARRAY )
2665 pred = searchFunctionPredicate(
2666 class_info->alias, field, pred_node, pred_itr->key );
2667 else if( pred_node->type == JSON_HASH )
2668 pred = searchFieldTransformPredicate(
2669 class_info, field, pred_node, pred_itr->key );
2671 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2673 jsonIteratorFree( pred_itr );
2675 } else if( node->type == JSON_NULL ) { // IS NULL search
2676 growing_buffer* _p = buffer_init( 64 );
2679 "\"%s\".%s IS NULL",
2680 class_info->class_name,
2681 osrfHashGet( field, "name" )
2683 pred = buffer_release( _p );
2684 } else { // equality search
2685 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2704 field : call_number,
2720 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2722 const jsonObject* working_hash;
2723 jsonObject* freeable_hash = NULL;
2725 if( join_hash->type == JSON_HASH ) {
2726 working_hash = join_hash;
2727 } else if( join_hash->type == JSON_STRING ) {
2728 // turn it into a JSON_HASH by creating a wrapper
2729 // around a copy of the original
2730 const char* _tmp = jsonObjectGetString( join_hash );
2731 freeable_hash = jsonNewObjectType( JSON_HASH );
2732 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2733 working_hash = freeable_hash;
2737 "%s: JOIN failed; expected JSON object type not found",
2743 growing_buffer* join_buf = buffer_init( 128 );
2744 const char* leftclass = left_info->class_name;
2746 jsonObject* snode = NULL;
2747 jsonIterator* search_itr = jsonNewIterator( working_hash );
2749 while ( (snode = jsonIteratorNext( search_itr )) ) {
2750 const char* right_alias = search_itr->key;
2752 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2754 class = right_alias;
2756 const ClassInfo* right_info = add_joined_class( right_alias, class );
2760 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2764 jsonIteratorFree( search_itr );
2765 buffer_free( join_buf );
2767 jsonObjectFree( freeable_hash );
2770 osrfHash* links = right_info->links;
2771 const char* table = right_info->source_def;
2773 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2774 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2776 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* idl_link = (osrfHash*) osrfHashGet( links, field );
2781 const char* reltype = NULL;
2782 const char* other_class = NULL;
2783 reltype = osrfHashGet( idl_link, "reltype" );
2784 if( reltype && strcmp( reltype, "has_many" ) )
2785 other_class = osrfHashGet( idl_link, "class" );
2786 if( other_class && !strcmp( other_class, leftclass ) )
2787 fkey = osrfHashGet( idl_link, "key" );
2791 "%s: JOIN failed. No link defined from %s.%s to %s",
2797 buffer_free( join_buf );
2799 jsonObjectFree( freeable_hash );
2800 jsonIteratorFree( search_itr );
2804 } else if( !field && fkey ) {
2805 // Look up the corresponding join column in the IDL.
2806 // The link must be defined in the child table,
2807 // and point to the right parent table.
2808 osrfHash* left_links = left_info->links;
2809 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2810 const char* reltype = NULL;
2811 const char* other_class = NULL;
2812 reltype = osrfHashGet( idl_link, "reltype" );
2813 if( reltype && strcmp( reltype, "has_many" ) )
2814 other_class = osrfHashGet( idl_link, "class" );
2815 if( other_class && !strcmp( other_class, class ) )
2816 field = osrfHashGet( idl_link, "key" );
2820 "%s: JOIN failed. No link defined from %s.%s to %s",
2826 buffer_free( join_buf );
2828 jsonObjectFree( freeable_hash );
2829 jsonIteratorFree( search_itr );
2833 } else if( !field && !fkey ) {
2834 osrfHash* left_links = left_info->links;
2836 // For each link defined for the left class:
2837 // see if the link references the joined class
2838 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2839 osrfHash* curr_link = NULL;
2840 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2841 const char* other_class = osrfHashGet( curr_link, "class" );
2842 if( other_class && !strcmp( other_class, class ) ) {
2844 // In the IDL, the parent class doesn't always know then names of the child
2845 // columns that are pointing to it, so don't use that end of the link
2846 const char* reltype = osrfHashGet( curr_link, "reltype" );
2847 if( reltype && strcmp( reltype, "has_many" ) ) {
2848 // Found a link between the classes
2849 fkey = osrfHashIteratorKey( itr );
2850 field = osrfHashGet( curr_link, "key" );
2855 osrfHashIteratorFree( itr );
2857 if( !field || !fkey ) {
2858 // Do another such search, with the classes reversed
2860 // For each link defined for the joined class:
2861 // see if the link references the left class
2862 osrfHashIterator* itr = osrfNewHashIterator( links );
2863 osrfHash* curr_link = NULL;
2864 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2865 const char* other_class = osrfHashGet( curr_link, "class" );
2866 if( other_class && !strcmp( other_class, leftclass ) ) {
2868 // In the IDL, the parent class doesn't know then names of the child
2869 // columns that are pointing to it, so don't use that end of the link
2870 const char* reltype = osrfHashGet( curr_link, "reltype" );
2871 if( reltype && strcmp( reltype, "has_many" ) ) {
2872 // Found a link between the classes
2873 field = osrfHashIteratorKey( itr );
2874 fkey = osrfHashGet( curr_link, "key" );
2879 osrfHashIteratorFree( itr );
2882 if( !field || !fkey ) {
2885 "%s: JOIN failed. No link defined between %s and %s",
2890 buffer_free( join_buf );
2892 jsonObjectFree( freeable_hash );
2893 jsonIteratorFree( search_itr );
2898 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2900 if( !strcasecmp( type,"left" )) {
2901 buffer_add( join_buf, " LEFT JOIN" );
2902 } else if( !strcasecmp( type,"right" )) {
2903 buffer_add( join_buf, " RIGHT JOIN" );
2904 } else if( !strcasecmp( type,"full" )) {
2905 buffer_add( join_buf, " FULL JOIN" );
2907 buffer_add( join_buf, " INNER JOIN" );
2910 buffer_add( join_buf, " INNER JOIN" );
2913 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2914 table, right_alias, right_alias, field, left_info->alias, fkey );
2916 // Add any other join conditions as specified by "filter"
2917 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2919 const char* filter_op = jsonObjectGetString(
2920 jsonObjectGetKeyConst( snode, "filter_op" ) );
2921 if( filter_op && !strcasecmp( "or",filter_op )) {
2922 buffer_add( join_buf, " OR " );
2924 buffer_add( join_buf, " AND " );
2927 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2929 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2930 OSRF_BUFFER_ADD( join_buf, jpred );
2935 "%s: JOIN failed. Invalid conditional expression.",
2938 jsonIteratorFree( search_itr );
2939 buffer_free( join_buf );
2941 jsonObjectFree( freeable_hash );
2946 buffer_add( join_buf, " ) " );
2948 // Recursively add a nested join, if one is present
2949 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2951 char* jpred = searchJOIN( join_filter, right_info );
2953 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2954 OSRF_BUFFER_ADD( join_buf, jpred );
2957 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
2958 jsonIteratorFree( search_itr );
2959 buffer_free( join_buf );
2961 jsonObjectFree( freeable_hash );
2968 jsonObjectFree( freeable_hash );
2969 jsonIteratorFree( search_itr );
2971 return buffer_release( join_buf );
2976 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2977 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2978 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2980 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2982 search_hash is the JSON expression of the conditions.
2983 meta is the class definition from the IDL, for the relevant table.
2984 opjoin_type indicates whether multiple conditions, if present, should be
2985 connected by AND or OR.
2986 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2987 to pass it to other functions -- and all they do with it is to use the session
2988 and request members to send error messages back to the client.
2992 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
2993 int opjoin_type, osrfMethodContext* ctx ) {
2997 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
2998 "opjoin_type = %d, ctx addr = %p",
3001 class_info->class_def,
3006 growing_buffer* sql_buf = buffer_init( 128 );
3008 jsonObject* node = NULL;
3011 if( search_hash->type == JSON_ARRAY ) {
3012 osrfLogDebug( OSRF_LOG_MARK,
3013 "%s: In WHERE clause, condition type is JSON_ARRAY", modulename );
3014 if( 0 == search_hash->size ) {
3017 "%s: Invalid predicate structure: empty JSON array",
3020 buffer_free( sql_buf );
3024 unsigned long i = 0;
3025 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3029 if( opjoin_type == OR_OP_JOIN )
3030 buffer_add( sql_buf, " OR " );
3032 buffer_add( sql_buf, " AND " );
3035 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3037 buffer_free( sql_buf );
3041 buffer_fadd( sql_buf, "( %s )", subpred );
3045 } else if( search_hash->type == JSON_HASH ) {
3046 osrfLogDebug( OSRF_LOG_MARK,
3047 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3048 jsonIterator* search_itr = jsonNewIterator( search_hash );
3049 if( !jsonIteratorHasNext( search_itr ) ) {
3052 "%s: Invalid predicate structure: empty JSON object",
3055 jsonIteratorFree( search_itr );
3056 buffer_free( sql_buf );
3060 while( (node = jsonIteratorNext( search_itr )) ) {
3065 if( opjoin_type == OR_OP_JOIN )
3066 buffer_add( sql_buf, " OR " );
3068 buffer_add( sql_buf, " AND " );
3071 if( '+' == search_itr->key[ 0 ] ) {
3073 // This plus sign prefixes a class name or other table alias;
3074 // make sure the table alias is in scope
3075 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3076 if( ! alias_info ) {
3079 "%s: Invalid table alias \"%s\" in WHERE clause",
3083 jsonIteratorFree( search_itr );
3084 buffer_free( sql_buf );
3088 if( node->type == JSON_STRING ) {
3089 // It's the name of a column; make sure it belongs to the class
3090 const char* fieldname = jsonObjectGetString( node );
3091 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3094 "%s: Invalid column name \"%s\" in WHERE clause "
3095 "for table alias \"%s\"",
3100 jsonIteratorFree( search_itr );
3101 buffer_free( sql_buf );
3105 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3107 // It's something more complicated
3108 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3110 jsonIteratorFree( search_itr );
3111 buffer_free( sql_buf );
3115 buffer_fadd( sql_buf, "( %s )", subpred );
3118 } else if( '-' == search_itr->key[ 0 ] ) {
3119 if( !strcasecmp( "-or", search_itr->key )) {
3120 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3122 jsonIteratorFree( search_itr );
3123 buffer_free( sql_buf );
3127 buffer_fadd( sql_buf, "( %s )", subpred );
3129 } else if( !strcasecmp( "-and", search_itr->key )) {
3130 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3132 jsonIteratorFree( search_itr );
3133 buffer_free( sql_buf );
3137 buffer_fadd( sql_buf, "( %s )", subpred );
3139 } else if( !strcasecmp("-not",search_itr->key) ) {
3140 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3142 jsonIteratorFree( search_itr );
3143 buffer_free( sql_buf );
3147 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3149 } else if( !strcasecmp( "-exists", search_itr->key )) {
3150 char* subpred = buildQuery( ctx, node, SUBSELECT );
3152 jsonIteratorFree( search_itr );
3153 buffer_free( sql_buf );
3157 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3159 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3160 char* subpred = buildQuery( ctx, node, SUBSELECT );
3162 jsonIteratorFree( search_itr );
3163 buffer_free( sql_buf );
3167 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3169 } else { // Invalid "minus" operator
3172 "%s: Invalid operator \"%s\" in WHERE clause",
3176 jsonIteratorFree( search_itr );
3177 buffer_free( sql_buf );
3183 const char* class = class_info->class_name;
3184 osrfHash* fields = class_info->fields;
3185 osrfHash* field = osrfHashGet( fields, search_itr->key );
3188 const char* table = class_info->source_def;
3191 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3194 table ? table : "?",
3197 jsonIteratorFree( search_itr );
3198 buffer_free( sql_buf );
3202 char* subpred = searchPredicate( class_info, field, node, ctx );
3204 buffer_free( sql_buf );
3205 jsonIteratorFree( search_itr );
3209 buffer_add( sql_buf, subpred );
3213 jsonIteratorFree( search_itr );
3216 // ERROR ... only hash and array allowed at this level
3217 char* predicate_string = jsonObjectToJSON( search_hash );
3220 "%s: Invalid predicate structure: %s",
3224 buffer_free( sql_buf );
3225 free( predicate_string );
3229 return buffer_release( sql_buf );
3232 /* Build a JSON_ARRAY of field names for a given table alias
3234 static jsonObject* defaultSelectList( const char* table_alias ) {
3239 ClassInfo* class_info = search_all_alias( table_alias );
3240 if( ! class_info ) {
3243 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3250 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3251 osrfHash* field_def = NULL;
3252 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3253 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3254 const char* field_name = osrfHashIteratorKey( field_itr );
3255 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3256 jsonObjectPush( array, jsonNewObject( field_name ) );
3259 osrfHashIteratorFree( field_itr );
3264 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3265 // The jsonObject must be a JSON_HASH with an single entry for "union",
3266 // "intersect", or "except". The data associated with this key must be an
3267 // array of hashes, each hash being a query.
3268 // Also allowed but currently ignored: entries for "order_by" and "alias".
3269 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3271 if( ! combo || combo->type != JSON_HASH )
3272 return NULL; // should be impossible; validated by caller
3274 const jsonObject* query_array = NULL; // array of subordinate queries
3275 const char* op = NULL; // name of operator, e.g. UNION
3276 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3277 int op_count = 0; // for detecting conflicting operators
3278 int excepting = 0; // boolean
3279 int all = 0; // boolean
3280 jsonObject* order_obj = NULL;
3282 // Identify the elements in the hash
3283 jsonIterator* query_itr = jsonNewIterator( combo );
3284 jsonObject* curr_obj = NULL;
3285 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3286 if( ! strcmp( "union", query_itr->key ) ) {
3289 query_array = curr_obj;
3290 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3293 query_array = curr_obj;
3294 } else if( ! strcmp( "except", query_itr->key ) ) {
3298 query_array = curr_obj;
3299 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3302 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3305 order_obj = curr_obj;
3306 } else if( ! strcmp( "alias", query_itr->key ) ) {
3307 if( curr_obj->type != JSON_STRING ) {
3308 jsonIteratorFree( query_itr );
3311 alias = jsonObjectGetString( curr_obj );
3312 } else if( ! strcmp( "all", query_itr->key ) ) {
3313 if( obj_is_true( curr_obj ) )
3317 osrfAppSessionStatus(
3319 OSRF_STATUS_INTERNALSERVERERROR,
3320 "osrfMethodException",
3322 "Malformed query; unexpected entry in query object"
3326 "%s: Unexpected entry for \"%s\" in%squery",
3331 jsonIteratorFree( query_itr );
3335 jsonIteratorFree( query_itr );
3337 // More sanity checks
3338 if( ! query_array ) {
3340 osrfAppSessionStatus(
3342 OSRF_STATUS_INTERNALSERVERERROR,
3343 "osrfMethodException",
3345 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3349 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3352 return NULL; // should be impossible...
3353 } else if( op_count > 1 ) {
3355 osrfAppSessionStatus(
3357 OSRF_STATUS_INTERNALSERVERERROR,
3358 "osrfMethodException",
3360 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3364 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3368 } if( query_array->type != JSON_ARRAY ) {
3370 osrfAppSessionStatus(
3372 OSRF_STATUS_INTERNALSERVERERROR,
3373 "osrfMethodException",
3375 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3379 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3382 json_type( query_array->type )
3385 } if( query_array->size < 2 ) {
3387 osrfAppSessionStatus(
3389 OSRF_STATUS_INTERNALSERVERERROR,
3390 "osrfMethodException",
3392 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3396 "%s:%srequires multiple queries as operands",
3401 } else if( excepting && query_array->size > 2 ) {
3403 osrfAppSessionStatus(
3405 OSRF_STATUS_INTERNALSERVERERROR,
3406 "osrfMethodException",
3408 "EXCEPT operator has too many queries as operands"
3412 "%s:EXCEPT operator has too many queries as operands",
3416 } else if( order_obj && ! alias ) {
3418 osrfAppSessionStatus(
3420 OSRF_STATUS_INTERNALSERVERERROR,
3421 "osrfMethodException",
3423 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3427 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3433 // So far so good. Now build the SQL.
3434 growing_buffer* sql = buffer_init( 256 );
3436 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3437 // Add a layer of parentheses
3438 if( flags & SUBCOMBO )
3439 OSRF_BUFFER_ADD( sql, "( " );
3441 // Traverse the query array. Each entry should be a hash.
3442 int first = 1; // boolean
3444 jsonObject* query = NULL;
3445 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3446 if( query->type != JSON_HASH ) {
3448 osrfAppSessionStatus(
3450 OSRF_STATUS_INTERNALSERVERERROR,
3451 "osrfMethodException",
3453 "Malformed query under UNION, INTERSECT or EXCEPT"
3457 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3460 json_type( query->type )
3469 OSRF_BUFFER_ADD( sql, op );
3471 OSRF_BUFFER_ADD( sql, "ALL " );
3474 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3478 "%s: Error building query under%s",
3486 OSRF_BUFFER_ADD( sql, query_str );
3489 if( flags & SUBCOMBO )
3490 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3492 if( !(flags & SUBSELECT) )
3493 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3495 return buffer_release( sql );
3498 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3499 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3500 // or "except" to indicate the type of query.
3501 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3505 osrfAppSessionStatus(
3507 OSRF_STATUS_INTERNALSERVERERROR,
3508 "osrfMethodException",
3510 "Malformed query; no query object"
3512 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3514 } else if( query->type != JSON_HASH ) {
3516 osrfAppSessionStatus(
3518 OSRF_STATUS_INTERNALSERVERERROR,
3519 "osrfMethodException",
3521 "Malformed query object"
3525 "%s: Query object is %s instead of JSON_HASH",
3527 json_type( query->type )
3532 // Determine what kind of query it purports to be, and dispatch accordingly.
3533 if( jsonObjectGetKey( query, "union" ) ||
3534 jsonObjectGetKey( query, "intersect" ) ||
3535 jsonObjectGetKey( query, "except" ) ) {
3536 return doCombo( ctx, query, flags );
3538 // It is presumably a SELECT query
3540 // Push a node onto the stack for the current query. Every level of
3541 // subquery gets its own QueryFrame on the Stack.
3544 // Build an SQL SELECT statement
3547 jsonObjectGetKey( query, "select" ),
3548 jsonObjectGetKey( query, "from" ),
3549 jsonObjectGetKey( query, "where" ),
3550 jsonObjectGetKey( query, "having" ),
3551 jsonObjectGetKey( query, "order_by" ),
3552 jsonObjectGetKey( query, "limit" ),
3553 jsonObjectGetKey( query, "offset" ),
3562 /* method context */ osrfMethodContext* ctx,
3564 /* SELECT */ jsonObject* selhash,
3565 /* FROM */ jsonObject* join_hash,
3566 /* WHERE */ jsonObject* search_hash,
3567 /* HAVING */ jsonObject* having_hash,
3568 /* ORDER BY */ jsonObject* order_hash,
3569 /* LIMIT */ jsonObject* limit,
3570 /* OFFSET */ jsonObject* offset,
3571 /* flags */ int flags
3573 const char* locale = osrf_message_get_last_locale();
3575 // general tmp objects
3576 const jsonObject* tmp_const;
3577 jsonObject* selclass = NULL;
3578 jsonObject* snode = NULL;
3579 jsonObject* onode = NULL;
3581 char* string = NULL;
3582 int from_function = 0;
3587 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3589 // punt if there's no FROM clause
3590 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3593 "%s: FROM clause is missing or empty",
3597 osrfAppSessionStatus(
3599 OSRF_STATUS_INTERNALSERVERERROR,
3600 "osrfMethodException",
3602 "FROM clause is missing or empty in JSON query"
3607 // the core search class
3608 const char* core_class = NULL;
3610 // get the core class -- the only key of the top level FROM clause, or a string
3611 if( join_hash->type == JSON_HASH ) {
3612 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3613 snode = jsonIteratorNext( tmp_itr );
3615 // Populate the current QueryFrame with information
3616 // about the core class
3617 if( add_query_core( NULL, tmp_itr->key ) ) {
3619 osrfAppSessionStatus(
3621 OSRF_STATUS_INTERNALSERVERERROR,
3622 "osrfMethodException",
3624 "Unable to look up core class"
3628 core_class = curr_query->core.class_name;
3631 jsonObject* extra = jsonIteratorNext( tmp_itr );
3633 jsonIteratorFree( tmp_itr );
3636 // There shouldn't be more than one entry in join_hash
3640 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3644 osrfAppSessionStatus(
3646 OSRF_STATUS_INTERNALSERVERERROR,
3647 "osrfMethodException",
3649 "Malformed FROM clause in JSON query"
3651 return NULL; // Malformed join_hash; extra entry
3653 } else if( join_hash->type == JSON_ARRAY ) {
3654 // We're selecting from a function, not from a table
3656 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3659 } else if( join_hash->type == JSON_STRING ) {
3660 // Populate the current QueryFrame with information
3661 // about the core class
3662 core_class = jsonObjectGetString( join_hash );
3664 if( add_query_core( NULL, core_class ) ) {
3666 osrfAppSessionStatus(
3668 OSRF_STATUS_INTERNALSERVERERROR,
3669 "osrfMethodException",
3671 "Unable to look up core class"
3679 "%s: FROM clause is unexpected JSON type: %s",
3681 json_type( join_hash->type )
3684 osrfAppSessionStatus(
3686 OSRF_STATUS_INTERNALSERVERERROR,
3687 "osrfMethodException",
3689 "Ill-formed FROM clause in JSON query"
3694 // Build the join clause, if any, while filling out the list
3695 // of joined classes in the current QueryFrame.
3696 char* join_clause = NULL;
3697 if( join_hash && ! from_function ) {
3699 join_clause = searchJOIN( join_hash, &curr_query->core );
3700 if( ! join_clause ) {
3702 osrfAppSessionStatus(
3704 OSRF_STATUS_INTERNALSERVERERROR,
3705 "osrfMethodException",
3707 "Unable to construct JOIN clause(s)"
3713 // For in case we don't get a select list
3714 jsonObject* defaultselhash = NULL;
3716 // if there is no select list, build a default select list ...
3717 if( !selhash && !from_function ) {
3718 jsonObject* default_list = defaultSelectList( core_class );
3719 if( ! default_list ) {
3721 osrfAppSessionStatus(
3723 OSRF_STATUS_INTERNALSERVERERROR,
3724 "osrfMethodException",
3726 "Unable to build default SELECT clause in JSON query"
3728 free( join_clause );
3733 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3734 jsonObjectSetKey( selhash, core_class, default_list );
3737 // The SELECT clause can be encoded only by a hash
3738 if( !from_function && selhash->type != JSON_HASH ) {
3741 "%s: Expected JSON_HASH for SELECT clause; found %s",
3743 json_type( selhash->type )
3747 osrfAppSessionStatus(
3749 OSRF_STATUS_INTERNALSERVERERROR,
3750 "osrfMethodException",
3752 "Malformed SELECT clause in JSON query"
3754 free( join_clause );
3758 // If you see a null or wild card specifier for the core class, or an
3759 // empty array, replace it with a default SELECT list
3760 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3762 int default_needed = 0; // boolean
3763 if( JSON_STRING == tmp_const->type
3764 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3766 else if( JSON_NULL == tmp_const->type )
3769 if( default_needed ) {
3770 // Build a default SELECT list
3771 jsonObject* default_list = defaultSelectList( core_class );
3772 if( ! default_list ) {
3774 osrfAppSessionStatus(
3776 OSRF_STATUS_INTERNALSERVERERROR,
3777 "osrfMethodException",
3779 "Can't build default SELECT clause in JSON query"
3781 free( join_clause );
3786 jsonObjectSetKey( selhash, core_class, default_list );
3790 // temp buffers for the SELECT list and GROUP BY clause
3791 growing_buffer* select_buf = buffer_init( 128 );
3792 growing_buffer* group_buf = buffer_init( 128 );
3794 int aggregate_found = 0; // boolean
3796 // Build a select list
3797 if( from_function ) // From a function we select everything
3798 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3801 // Build the SELECT list as SQL
3805 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3806 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3808 const char* cname = selclass_itr->key;
3810 // Make sure the target relation is in the FROM clause.
3812 // At this point join_hash is a step down from the join_hash we
3813 // received as a parameter. If the original was a JSON_STRING,
3814 // then json_hash is now NULL. If the original was a JSON_HASH,
3815 // then json_hash is now the first (and only) entry in it,
3816 // denoting the core class. We've already excluded the
3817 // possibility that the original was a JSON_ARRAY, because in
3818 // that case from_function would be non-NULL, and we wouldn't
3821 // If the current table alias isn't in scope, bail out
3822 ClassInfo* class_info = search_alias( cname );
3823 if( ! class_info ) {
3826 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3831 osrfAppSessionStatus(
3833 OSRF_STATUS_INTERNALSERVERERROR,
3834 "osrfMethodException",
3836 "Selected class not in FROM clause in JSON query"
3838 jsonIteratorFree( selclass_itr );
3839 buffer_free( select_buf );
3840 buffer_free( group_buf );
3841 if( defaultselhash )
3842 jsonObjectFree( defaultselhash );
3843 free( join_clause );
3847 if( selclass->type != JSON_ARRAY ) {
3850 "%s: Malformed SELECT list for class \"%s\"; not an array",
3855 osrfAppSessionStatus(
3857 OSRF_STATUS_INTERNALSERVERERROR,
3858 "osrfMethodException",
3860 "Selected class not in FROM clause in JSON query"
3863 jsonIteratorFree( selclass_itr );
3864 buffer_free( select_buf );
3865 buffer_free( group_buf );
3866 if( defaultselhash )
3867 jsonObjectFree( defaultselhash );
3868 free( join_clause );
3872 // Look up some attributes of the current class
3873 osrfHash* idlClass = class_info->class_def;
3874 osrfHash* class_field_set = class_info->fields;
3875 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3876 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3878 if( 0 == selclass->size ) {
3881 "%s: No columns selected from \"%s\"",
3887 // stitch together the column list for the current table alias...
3888 unsigned long field_idx = 0;
3889 jsonObject* selfield = NULL;
3890 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3892 // If we need a separator comma, add one
3896 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3899 // if the field specification is a string, add it to the list
3900 if( selfield->type == JSON_STRING ) {
3902 // Look up the field in the IDL
3903 const char* col_name = jsonObjectGetString( selfield );
3904 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3906 // No such field in current class
3909 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3915 osrfAppSessionStatus(
3917 OSRF_STATUS_INTERNALSERVERERROR,
3918 "osrfMethodException",
3920 "Selected column not defined in JSON query"
3922 jsonIteratorFree( selclass_itr );
3923 buffer_free( select_buf );
3924 buffer_free( group_buf );
3925 if( defaultselhash )
3926 jsonObjectFree( defaultselhash );
3927 free( join_clause );
3929 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3930 // Virtual field not allowed
3933 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3939 osrfAppSessionStatus(
3941 OSRF_STATUS_INTERNALSERVERERROR,
3942 "osrfMethodException",
3944 "Selected column may not be virtual in JSON query"
3946 jsonIteratorFree( selclass_itr );
3947 buffer_free( select_buf );
3948 buffer_free( group_buf );
3949 if( defaultselhash )
3950 jsonObjectFree( defaultselhash );
3951 free( join_clause );
3957 if( flags & DISABLE_I18N )
3960 i18n = osrfHashGet( field_def, "i18n" );
3962 if( str_is_true( i18n ) ) {
3963 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
3964 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3965 class_tname, cname, col_name, class_pkey,
3966 cname, class_pkey, locale, col_name );
3968 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3969 cname, col_name, col_name );
3972 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3973 cname, col_name, col_name );
3976 // ... but it could be an object, in which case we check for a Field Transform
3977 } else if( selfield->type == JSON_HASH ) {
3979 const char* col_name = jsonObjectGetString(
3980 jsonObjectGetKeyConst( selfield, "column" ) );
3982 // Get the field definition from the IDL
3983 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3985 // No such field in current class
3988 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3994 osrfAppSessionStatus(
3996 OSRF_STATUS_INTERNALSERVERERROR,
3997 "osrfMethodException",
3999 "Selected column is not defined in JSON query"
4001 jsonIteratorFree( selclass_itr );
4002 buffer_free( select_buf );
4003 buffer_free( group_buf );
4004 if( defaultselhash )
4005 jsonObjectFree( defaultselhash );
4006 free( join_clause );
4008 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4009 // No such field in current class
4012 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4018 osrfAppSessionStatus(
4020 OSRF_STATUS_INTERNALSERVERERROR,
4021 "osrfMethodException",
4023 "Selected column is virtual in JSON query"
4025 jsonIteratorFree( selclass_itr );
4026 buffer_free( select_buf );
4027 buffer_free( group_buf );
4028 if( defaultselhash )
4029 jsonObjectFree( defaultselhash );
4030 free( join_clause );
4034 // Decide what to use as a column alias
4036 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4037 _alias = jsonObjectGetString( tmp_const );
4038 } else { // Use field name as the alias
4042 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4043 char* transform_str = searchFieldTransform(
4044 class_info->alias, field_def, selfield );
4045 if( transform_str ) {
4046 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4047 free( transform_str );
4050 osrfAppSessionStatus(
4052 OSRF_STATUS_INTERNALSERVERERROR,
4053 "osrfMethodException",
4055 "Unable to generate transform function in JSON query"
4057 jsonIteratorFree( selclass_itr );
4058 buffer_free( select_buf );
4059 buffer_free( group_buf );
4060 if( defaultselhash )
4061 jsonObjectFree( defaultselhash );
4062 free( join_clause );
4069 if( flags & DISABLE_I18N )
4072 i18n = osrfHashGet( field_def, "i18n" );
4074 if( str_is_true( i18n ) ) {
4075 buffer_fadd( select_buf,
4076 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4077 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4078 class_tname, cname, col_name, class_pkey, cname,
4079 class_pkey, locale, _alias );
4081 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4082 cname, col_name, _alias );
4085 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4086 cname, col_name, _alias );
4093 "%s: Selected item is unexpected JSON type: %s",
4095 json_type( selfield->type )
4098 osrfAppSessionStatus(
4100 OSRF_STATUS_INTERNALSERVERERROR,
4101 "osrfMethodException",
4103 "Ill-formed SELECT item in JSON query"
4105 jsonIteratorFree( selclass_itr );
4106 buffer_free( select_buf );
4107 buffer_free( group_buf );
4108 if( defaultselhash )
4109 jsonObjectFree( defaultselhash );
4110 free( join_clause );
4114 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
4115 if( obj_is_true( agg_obj ) )
4116 aggregate_found = 1;
4118 // Append a comma (except for the first one)
4119 // and add the column to a GROUP BY clause
4123 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4125 buffer_fadd( group_buf, " %d", sel_pos );
4129 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4131 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4132 if ( ! obj_is_true( aggregate_obj ) ) {
4136 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4139 buffer_fadd(group_buf, " %d", sel_pos);
4142 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4146 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4149 _column = searchFieldTransform(class_info->alias, field, selfield);
4150 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4151 OSRF_BUFFER_ADD(group_buf, _column);
4152 _column = searchFieldTransform(class_info->alias, field, selfield);
4159 } // end while -- iterating across SELECT columns
4161 } // end while -- iterating across classes
4163 jsonIteratorFree( selclass_itr );
4167 char* col_list = buffer_release( select_buf );
4169 // Make sure the SELECT list isn't empty. This can happen, for example,
4170 // if we try to build a default SELECT clause from a non-core table.
4173 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4175 osrfAppSessionStatus(
4177 OSRF_STATUS_INTERNALSERVERERROR,
4178 "osrfMethodException",
4180 "SELECT list is empty"
4183 buffer_free( group_buf );
4184 if( defaultselhash )
4185 jsonObjectFree( defaultselhash );
4186 free( join_clause );
4192 table = searchValueTransform( join_hash );
4194 table = strdup( curr_query->core.source_def );
4198 osrfAppSessionStatus(
4200 OSRF_STATUS_INTERNALSERVERERROR,
4201 "osrfMethodException",
4203 "Unable to identify table for core class"
4206 buffer_free( group_buf );
4207 if( defaultselhash )
4208 jsonObjectFree( defaultselhash );
4209 free( join_clause );
4213 // Put it all together
4214 growing_buffer* sql_buf = buffer_init( 128 );
4215 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4219 // Append the join clause, if any
4221 buffer_add(sql_buf, join_clause );
4222 free( join_clause );
4225 char* order_by_list = NULL;
4226 char* having_buf = NULL;
4228 if( !from_function ) {
4230 // Build a WHERE clause, if there is one
4232 buffer_add( sql_buf, " WHERE " );
4234 // and it's on the WHERE clause
4235 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4238 osrfAppSessionStatus(
4240 OSRF_STATUS_INTERNALSERVERERROR,
4241 "osrfMethodException",
4243 "Severe query error in WHERE predicate -- see error log for more details"
4246 buffer_free( group_buf );
4247 buffer_free( sql_buf );
4248 if( defaultselhash )
4249 jsonObjectFree( defaultselhash );
4253 buffer_add( sql_buf, pred );
4257 // Build a HAVING clause, if there is one
4260 // and it's on the the WHERE clause
4261 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4263 if( ! having_buf ) {
4265 osrfAppSessionStatus(
4267 OSRF_STATUS_INTERNALSERVERERROR,
4268 "osrfMethodException",
4270 "Severe query error in HAVING predicate -- see error log for more details"
4273 buffer_free( group_buf );
4274 buffer_free( sql_buf );
4275 if( defaultselhash )
4276 jsonObjectFree( defaultselhash );
4281 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4283 // Build an ORDER BY clause, if there is one
4284 if( NULL == order_hash )
4285 ; // No ORDER BY? do nothing
4286 else if( JSON_ARRAY == order_hash->type ) {
4287 // Array of field specifications, each specification being a
4288 // hash to define the class, field, and other details
4290 jsonObject* order_spec;
4291 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4293 if( JSON_HASH != order_spec->type ) {
4294 osrfLogError( OSRF_LOG_MARK,
4295 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4296 modulename, json_type( order_spec->type ) );
4298 osrfAppSessionStatus(
4300 OSRF_STATUS_INTERNALSERVERERROR,
4301 "osrfMethodException",
4303 "Malformed ORDER BY clause -- see error log for more details"
4305 buffer_free( order_buf );
4307 buffer_free( group_buf );
4308 buffer_free( sql_buf );
4309 if( defaultselhash )
4310 jsonObjectFree( defaultselhash );
4314 const char* class_alias =
4315 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4317 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4320 OSRF_BUFFER_ADD( order_buf, ", " );
4322 order_buf = buffer_init( 128 );
4324 if( !field || !class_alias ) {
4325 osrfLogError( OSRF_LOG_MARK,
4326 "%s: Missing class or field name in field specification "
4327 "of ORDER BY clause",
4330 osrfAppSessionStatus(
4332 OSRF_STATUS_INTERNALSERVERERROR,
4333 "osrfMethodException",
4335 "Malformed ORDER BY clause -- see error log for more details"
4337 buffer_free( order_buf );
4339 buffer_free( group_buf );
4340 buffer_free( sql_buf );
4341 if( defaultselhash )
4342 jsonObjectFree( defaultselhash );
4346 ClassInfo* order_class_info = search_alias( class_alias );
4347 if( ! order_class_info ) {
4348 osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4349 "not in FROM clause", modulename, class_alias );
4351 osrfAppSessionStatus(
4353 OSRF_STATUS_INTERNALSERVERERROR,
4354 "osrfMethodException",
4356 "Invalid class referenced in ORDER BY clause -- "
4357 "see error log for more details"
4360 buffer_free( group_buf );
4361 buffer_free( sql_buf );
4362 if( defaultselhash )
4363 jsonObjectFree( defaultselhash );
4367 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4369 osrfLogError( OSRF_LOG_MARK,
4370 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4371 modulename, class_alias, field );
4373 osrfAppSessionStatus(
4375 OSRF_STATUS_INTERNALSERVERERROR,
4376 "osrfMethodException",
4378 "Invalid field referenced in ORDER BY clause -- "
4379 "see error log for more details"
4382 buffer_free( group_buf );
4383 buffer_free( sql_buf );
4384 if( defaultselhash )
4385 jsonObjectFree( defaultselhash );
4387 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4388 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4389 modulename, field );
4391 osrfAppSessionStatus(
4393 OSRF_STATUS_INTERNALSERVERERROR,
4394 "osrfMethodException",
4396 "Virtual field in ORDER BY clause -- see error log for more details"
4398 buffer_free( order_buf );
4400 buffer_free( group_buf );
4401 buffer_free( sql_buf );
4402 if( defaultselhash )
4403 jsonObjectFree( defaultselhash );
4407 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4408 char* transform_str = searchFieldTransform(
4409 class_alias, field_def, order_spec );
4410 if( ! transform_str ) {
4412 osrfAppSessionStatus(
4414 OSRF_STATUS_INTERNALSERVERERROR,
4415 "osrfMethodException",
4417 "Severe query error in ORDER BY clause -- "
4418 "see error log for more details"
4420 buffer_free( order_buf );
4422 buffer_free( group_buf );
4423 buffer_free( sql_buf );
4424 if( defaultselhash )
4425 jsonObjectFree( defaultselhash );
4429 OSRF_BUFFER_ADD( order_buf, transform_str );
4430 free( transform_str );
4433 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4435 const char* direction =
4436 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4438 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4439 OSRF_BUFFER_ADD( order_buf, " DESC" );
4441 OSRF_BUFFER_ADD( order_buf, " ASC" );
4444 } else if( JSON_HASH == order_hash->type ) {
4445 // This hash is keyed on class alias. Each class has either
4446 // an array of field names or a hash keyed on field name.
4447 jsonIterator* class_itr = jsonNewIterator( order_hash );
4448 while( (snode = jsonIteratorNext( class_itr )) ) {
4450 ClassInfo* order_class_info = search_alias( class_itr->key );
4451 if( ! order_class_info ) {
4452 osrfLogError( OSRF_LOG_MARK,
4453 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4454 modulename, class_itr->key );
4456 osrfAppSessionStatus(
4458 OSRF_STATUS_INTERNALSERVERERROR,
4459 "osrfMethodException",
4461 "Invalid class referenced in ORDER BY clause -- "
4462 "see error log for more details"
4464 jsonIteratorFree( class_itr );
4465 buffer_free( order_buf );
4467 buffer_free( group_buf );
4468 buffer_free( sql_buf );
4469 if( defaultselhash )
4470 jsonObjectFree( defaultselhash );
4474 osrfHash* field_list_def = order_class_info->fields;
4476 if( snode->type == JSON_HASH ) {
4478 // Hash is keyed on field names from the current class. For each field
4479 // there is another layer of hash to define the sorting details, if any,
4480 // or a string to indicate direction of sorting.
4481 jsonIterator* order_itr = jsonNewIterator( snode );
4482 while( (onode = jsonIteratorNext( order_itr )) ) {
4484 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4486 osrfLogError( OSRF_LOG_MARK,
4487 "%s: Invalid field \"%s\" in ORDER BY clause",
4488 modulename, order_itr->key );
4490 osrfAppSessionStatus(
4492 OSRF_STATUS_INTERNALSERVERERROR,
4493 "osrfMethodException",
4495 "Invalid field in ORDER BY clause -- "
4496 "see error log for more details"
4498 jsonIteratorFree( order_itr );
4499 jsonIteratorFree( class_itr );
4500 buffer_free( order_buf );
4502 buffer_free( group_buf );
4503 buffer_free( sql_buf );
4504 if( defaultselhash )
4505 jsonObjectFree( defaultselhash );
4507 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4508 osrfLogError( OSRF_LOG_MARK,
4509 "%s: Virtual field \"%s\" in ORDER BY clause",
4510 modulename, order_itr->key );
4512 osrfAppSessionStatus(
4514 OSRF_STATUS_INTERNALSERVERERROR,
4515 "osrfMethodException",
4517 "Virtual field in ORDER BY clause -- "
4518 "see error log for more details"
4520 jsonIteratorFree( order_itr );
4521 jsonIteratorFree( class_itr );
4522 buffer_free( order_buf );
4524 buffer_free( group_buf );
4525 buffer_free( sql_buf );
4526 if( defaultselhash )
4527 jsonObjectFree( defaultselhash );
4531 const char* direction = NULL;
4532 if( onode->type == JSON_HASH ) {
4533 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4534 string = searchFieldTransform(
4536 osrfHashGet( field_list_def, order_itr->key ),
4540 if( ctx ) osrfAppSessionStatus(
4542 OSRF_STATUS_INTERNALSERVERERROR,
4543 "osrfMethodException",
4545 "Severe query error in ORDER BY clause -- "
4546 "see error log for more details"
4548 jsonIteratorFree( order_itr );
4549 jsonIteratorFree( class_itr );
4551 buffer_free( group_buf );
4552 buffer_free( order_buf);
4553 buffer_free( sql_buf );
4554 if( defaultselhash )
4555 jsonObjectFree( defaultselhash );
4559 growing_buffer* field_buf = buffer_init( 16 );
4560 buffer_fadd( field_buf, "\"%s\".%s",
4561 class_itr->key, order_itr->key );
4562 string = buffer_release( field_buf );
4565 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4566 const char* dir = jsonObjectGetString( tmp_const );
4567 if(!strncasecmp( dir, "d", 1 )) {
4568 direction = " DESC";
4574 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4575 osrfLogError( OSRF_LOG_MARK,
4576 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4577 modulename, json_type( onode->type ) );
4579 osrfAppSessionStatus(
4581 OSRF_STATUS_INTERNALSERVERERROR,
4582 "osrfMethodException",
4584 "Malformed ORDER BY clause -- see error log for more details"
4586 jsonIteratorFree( order_itr );
4587 jsonIteratorFree( class_itr );
4589 buffer_free( group_buf );
4590 buffer_free( order_buf );
4591 buffer_free( sql_buf );
4592 if( defaultselhash )
4593 jsonObjectFree( defaultselhash );
4597 string = strdup( order_itr->key );
4598 const char* dir = jsonObjectGetString( onode );
4599 if( !strncasecmp( dir, "d", 1 )) {
4600 direction = " DESC";
4607 OSRF_BUFFER_ADD( order_buf, ", " );
4609 order_buf = buffer_init( 128 );
4611 OSRF_BUFFER_ADD( order_buf, string );
4615 OSRF_BUFFER_ADD( order_buf, direction );
4619 jsonIteratorFree( order_itr );
4621 } else if( snode->type == JSON_ARRAY ) {
4623 // Array is a list of fields from the current class
4624 unsigned long order_idx = 0;
4625 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4627 const char* _f = jsonObjectGetString( onode );
4629 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4631 osrfLogError( OSRF_LOG_MARK,
4632 "%s: Invalid field \"%s\" in ORDER BY clause",
4635 osrfAppSessionStatus(
4637 OSRF_STATUS_INTERNALSERVERERROR,
4638 "osrfMethodException",
4640 "Invalid field in ORDER BY clause -- "
4641 "see error log for more details"
4643 jsonIteratorFree( class_itr );
4644 buffer_free( order_buf );
4646 buffer_free( group_buf );
4647 buffer_free( sql_buf );
4648 if( defaultselhash )
4649 jsonObjectFree( defaultselhash );
4651 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4652 osrfLogError( OSRF_LOG_MARK,
4653 "%s: Virtual field \"%s\" in ORDER BY clause",
4656 osrfAppSessionStatus(
4658 OSRF_STATUS_INTERNALSERVERERROR,
4659 "osrfMethodException",
4661 "Virtual field in ORDER BY clause -- "
4662 "see error log for more details"
4664 jsonIteratorFree( class_itr );
4665 buffer_free( order_buf );
4667 buffer_free( group_buf );
4668 buffer_free( sql_buf );
4669 if( defaultselhash )
4670 jsonObjectFree( defaultselhash );
4675 OSRF_BUFFER_ADD( order_buf, ", " );
4677 order_buf = buffer_init( 128 );
4679 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4683 // IT'S THE OOOOOOOOOOOLD STYLE!
4685 osrfLogError( OSRF_LOG_MARK,
4686 "%s: Possible SQL injection attempt; direct order by is not allowed",
4689 osrfAppSessionStatus(
4691 OSRF_STATUS_INTERNALSERVERERROR,
4692 "osrfMethodException",
4694 "Severe query error -- see error log for more details"
4699 buffer_free( group_buf );
4700 buffer_free( order_buf );
4701 buffer_free( sql_buf );
4702 if( defaultselhash )
4703 jsonObjectFree( defaultselhash );
4704 jsonIteratorFree( class_itr );
4708 jsonIteratorFree( class_itr );
4710 osrfLogError( OSRF_LOG_MARK,
4711 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4712 modulename, json_type( order_hash->type ) );
4714 osrfAppSessionStatus(
4716 OSRF_STATUS_INTERNALSERVERERROR,
4717 "osrfMethodException",
4719 "Malformed ORDER BY clause -- see error log for more details"
4721 buffer_free( order_buf );
4723 buffer_free( group_buf );
4724 buffer_free( sql_buf );
4725 if( defaultselhash )
4726 jsonObjectFree( defaultselhash );
4731 order_by_list = buffer_release( order_buf );
4735 string = buffer_release( group_buf );
4737 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4738 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4739 OSRF_BUFFER_ADD( sql_buf, string );
4744 if( having_buf && *having_buf ) {
4745 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4746 OSRF_BUFFER_ADD( sql_buf, having_buf );
4750 if( order_by_list ) {
4752 if( *order_by_list ) {
4753 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4754 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4757 free( order_by_list );
4761 const char* str = jsonObjectGetString( limit );
4762 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4766 const char* str = jsonObjectGetString( offset );
4767 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4770 if( !(flags & SUBSELECT) )
4771 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4773 if( defaultselhash )
4774 jsonObjectFree( defaultselhash );
4776 return buffer_release( sql_buf );
4778 } // end of SELECT()
4780 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4782 const char* locale = osrf_message_get_last_locale();
4784 osrfHash* fields = osrfHashGet( meta, "fields" );
4785 char* core_class = osrfHashGet( meta, "classname" );
4787 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4789 jsonObject* node = NULL;
4790 jsonObject* snode = NULL;
4791 jsonObject* onode = NULL;
4792 const jsonObject* _tmp = NULL;
4793 jsonObject* selhash = NULL;
4794 jsonObject* defaultselhash = NULL;
4796 growing_buffer* sql_buf = buffer_init( 128 );
4797 growing_buffer* select_buf = buffer_init( 128 );
4799 if( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4800 defaultselhash = jsonNewObjectType( JSON_HASH );
4801 selhash = defaultselhash;
4804 // If there's no SELECT list for the core class, build one
4805 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4806 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4808 // Add every non-virtual field to the field list
4809 osrfHash* field_def = NULL;
4810 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4811 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4812 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4813 const char* field = osrfHashIteratorKey( field_itr );
4814 jsonObjectPush( field_list, jsonNewObject( field ) );
4817 osrfHashIteratorFree( field_itr );
4818 jsonObjectSetKey( selhash, core_class, field_list );
4822 jsonIterator* class_itr = jsonNewIterator( selhash );
4823 while( (snode = jsonIteratorNext( class_itr )) ) {
4825 const char* cname = class_itr->key;
4826 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4830 if( strcmp(core_class,class_itr->key )) {
4834 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4835 if( !found->size ) {
4836 jsonObjectFree( found );
4840 jsonObjectFree( found );
4843 jsonIterator* select_itr = jsonNewIterator( snode );
4844 while( (node = jsonIteratorNext( select_itr )) ) {
4845 const char* item_str = jsonObjectGetString( node );
4846 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4847 char* fname = osrfHashGet( field, "name" );
4855 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4860 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4861 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4864 i18n = osrfHashGet( field, "i18n" );
4866 if( str_is_true( i18n ) ) {
4867 char* pkey = osrfHashGet( idlClass, "primarykey" );
4868 char* tname = osrfHashGet( idlClass, "tablename" );
4870 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4871 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4872 tname, cname, fname, pkey, cname, pkey, locale, fname );
4874 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4877 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4881 jsonIteratorFree( select_itr );
4884 jsonIteratorFree( class_itr );
4886 char* col_list = buffer_release( select_buf );
4887 char* table = oilsGetRelation( meta );
4889 table = strdup( "(null)" );
4891 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4895 // Clear the query stack (as a fail-safe precaution against possible
4896 // leftover garbage); then push the first query frame onto the stack.
4897 clear_query_stack();
4899 if( add_query_core( NULL, core_class ) ) {
4901 osrfAppSessionStatus(
4903 OSRF_STATUS_INTERNALSERVERERROR,
4904 "osrfMethodException",
4906 "Unable to build query frame for core class"
4912 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4913 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
4914 OSRF_BUFFER_ADD( sql_buf, join_clause );
4915 free( join_clause );
4918 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4919 modulename, OSRF_BUFFER_C_STR( sql_buf ));
4921 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
4923 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4925 osrfAppSessionStatus(
4927 OSRF_STATUS_INTERNALSERVERERROR,
4928 "osrfMethodException",
4930 "Severe query error -- see error log for more details"
4932 buffer_free( sql_buf );
4933 if( defaultselhash )
4934 jsonObjectFree( defaultselhash );
4935 clear_query_stack();
4938 buffer_add( sql_buf, pred );
4943 char* string = NULL;
4944 if( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4946 growing_buffer* order_buf = buffer_init( 128 );
4949 jsonIterator* class_itr = jsonNewIterator( _tmp );
4950 while( (snode = jsonIteratorNext( class_itr )) ) {
4952 if( !jsonObjectGetKeyConst( selhash,class_itr->key ))
4955 if( snode->type == JSON_HASH ) {
4957 jsonIterator* order_itr = jsonNewIterator( snode );
4958 while( (onode = jsonIteratorNext( order_itr )) ) {
4960 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4961 class_itr->key, order_itr->key );
4965 char* direction = NULL;
4966 if( onode->type == JSON_HASH ) {
4967 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4968 string = searchFieldTransform( class_itr->key, field_def, onode );
4970 osrfAppSessionStatus(
4972 OSRF_STATUS_INTERNALSERVERERROR,
4973 "osrfMethodException",
4975 "Severe query error in ORDER BY clause -- "
4976 "see error log for more details"
4978 jsonIteratorFree( order_itr );
4979 jsonIteratorFree( class_itr );
4980 buffer_free( order_buf );
4981 buffer_free( sql_buf );
4982 if( defaultselhash )
4983 jsonObjectFree( defaultselhash );
4984 clear_query_stack();
4988 growing_buffer* field_buf = buffer_init( 16 );
4989 buffer_fadd( field_buf, "\"%s\".%s",
4990 class_itr->key, order_itr->key );
4991 string = buffer_release( field_buf );
4994 if( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4995 const char* dir = jsonObjectGetString( _tmp );
4996 if(!strncasecmp( dir, "d", 1 )) {
4997 direction = " DESC";
5001 string = strdup( order_itr->key );
5002 const char* dir = jsonObjectGetString( onode );
5003 if( !strncasecmp( dir, "d", 1 )) {
5004 direction = " DESC";
5013 buffer_add( order_buf, ", " );
5016 buffer_add( order_buf, string );
5020 buffer_add( order_buf, direction );
5024 jsonIteratorFree( order_itr );
5027 const char* str = jsonObjectGetString( snode );
5028 buffer_add( order_buf, str );
5034 jsonIteratorFree( class_itr );
5036 string = buffer_release( order_buf );
5039 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5040 OSRF_BUFFER_ADD( sql_buf, string );
5046 if( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ) {
5047 const char* str = jsonObjectGetString( _tmp );
5055 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
5057 const char* str = jsonObjectGetString( _tmp );
5066 if( defaultselhash )
5067 jsonObjectFree( defaultselhash );
5068 clear_query_stack();
5070 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5071 return buffer_release( sql_buf );
5074 int doJSONSearch ( osrfMethodContext* ctx ) {
5075 if(osrfMethodVerifyContext( ctx )) {
5076 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5080 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5085 dbhandle = writehandle;
5087 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5091 if( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
5092 flags |= SELECT_DISTINCT;
5094 if( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
5095 flags |= DISABLE_I18N;
5097 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5098 clear_query_stack(); // a possibly needless precaution
5099 char* sql = buildQuery( ctx, hash, flags );
5100 clear_query_stack();
5107 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5108 dbi_result result = dbi_conn_query( dbhandle, sql );
5111 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5113 if( dbi_result_first_row( result )) {
5114 /* JSONify the result */
5115 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5118 jsonObject* return_val = oilsMakeJSONFromResult( result );
5119 osrfAppRespond( ctx, return_val );
5120 jsonObjectFree( return_val );
5121 } while( dbi_result_next_row( result ));
5124 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5127 osrfAppRespondComplete( ctx, NULL );
5129 /* clean up the query */
5130 dbi_result_free( result );
5134 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]", modulename, sql );
5135 osrfAppSessionStatus(
5137 OSRF_STATUS_INTERNALSERVERERROR,
5138 "osrfMethodException",
5140 "Severe query error -- see error log for more details"
5148 // The last parameter, err, is used to report an error condition by updating an int owned by
5149 // the calling code.
5151 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5152 // It is the responsibility of the calling code to initialize *err before the
5153 // call, so that it will be able to make sense of the result.
5155 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5156 // redundant anyway.
5157 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5158 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5161 dbhandle = writehandle;
5163 char* core_class = osrfHashGet( class_meta, "classname" );
5164 char* pkey = osrfHashGet( class_meta, "primarykey" );
5166 const jsonObject* _tmp;
5168 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5170 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5175 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5177 dbi_result result = dbi_conn_query( dbhandle, sql );
5178 if( NULL == result ) {
5179 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
5180 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql );
5181 osrfAppSessionStatus(
5183 OSRF_STATUS_INTERNALSERVERERROR,
5184 "osrfMethodException",
5186 "Severe query error -- see error log for more details"
5193 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5196 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5197 jsonObject* row_obj = NULL;
5199 if( dbi_result_first_row( result )) {
5201 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5202 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5203 // eliminate the duplicates.
5204 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5205 osrfHash* dedup = osrfNewHash();
5207 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5208 char* pkey_val = oilsFMGetString( row_obj, pkey );
5209 if( osrfHashGet( dedup, pkey_val ) ) {
5210 jsonObjectFree( row_obj );
5213 osrfHashSet( dedup, pkey_val, pkey_val );
5214 jsonObjectPush( res_list, row_obj );
5216 } while( dbi_result_next_row( result ));
5217 osrfHashFree( dedup );
5220 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5224 /* clean up the query */
5225 dbi_result_free( result );
5228 // If we're asked to flesh, and there's anything to flesh, then flesh.
5229 if( res_list->size && query_hash ) {
5230 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5232 // Get the flesh depth
5233 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5234 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5235 flesh_depth = max_flesh_depth;
5237 // We need a non-zero flesh depth, and a list of fields to flesh
5238 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5239 if( temp_blob && flesh_depth > 0 ) {
5241 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5242 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5244 osrfStringArray* link_fields = NULL;
5245 osrfHash* links = osrfHashGet( class_meta, "links" );
5247 // Make an osrfStringArray of the names of fields to be fleshed
5248 if( flesh_fields ) {
5249 if( flesh_fields->size == 1 ) {
5250 const char* _t = jsonObjectGetString(
5251 jsonObjectGetIndex( flesh_fields, 0 ) );
5252 if( !strcmp( _t, "*" ))
5253 link_fields = osrfHashKeys( links );
5256 if( !link_fields ) {
5258 link_fields = osrfNewStringArray( 1 );
5259 jsonIterator* _i = jsonNewIterator( flesh_fields );
5260 while ((_f = jsonIteratorNext( _i ))) {
5261 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5263 jsonIteratorFree( _i );
5267 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5269 // Iterate over the JSON_ARRAY of rows
5271 unsigned long res_idx = 0;
5272 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5275 const char* link_field;
5277 // Iterate over the list of fleshable fields
5278 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5280 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5282 osrfHash* kid_link = osrfHashGet( links, link_field );
5284 continue; // Not a link field; skip it
5286 osrfHash* field = osrfHashGet( fields, link_field );
5288 continue; // Not a field at all; skip it (IDL is ill-formed)
5290 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5291 osrfHashGet( kid_link, "class" ));
5293 continue; // The class it links to doesn't exist; skip it
5295 const char* reltype = osrfHashGet( kid_link, "reltype" );
5297 continue; // No reltype; skip it (IDL is ill-formed)
5299 osrfHash* value_field = field;
5301 if( !strcmp( reltype, "has_many" )
5302 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5303 value_field = osrfHashGet(
5304 fields, osrfHashGet( class_meta, "primarykey" ) );
5307 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5309 if( link_map->size > 0 ) {
5310 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5313 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5318 osrfHashGet( kid_link, "class" ),
5325 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5326 osrfHashGet( kid_link, "field" ),
5327 osrfHashGet( kid_link, "class" ),
5328 osrfHashGet( kid_link, "key" ),
5329 osrfHashGet( kid_link, "reltype" )
5332 const char* search_key = jsonObjectGetString(
5333 jsonObjectGetIndex( cur,
5334 atoi( osrfHashGet( value_field, "array_position" ) )
5339 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5343 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5345 // construct WHERE clause
5346 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5349 osrfHashGet( kid_link, "key" ),
5350 jsonNewObject( search_key )
5353 // construct the rest of the query, mostly
5354 // by copying pieces of the previous level of query
5355 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5356 jsonObjectSetKey( rest_of_query, "flesh",
5357 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5361 jsonObjectSetKey( rest_of_query, "flesh_fields",
5362 jsonObjectClone( flesh_blob ));
5364 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5365 jsonObjectSetKey( rest_of_query, "order_by",
5366 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5370 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5371 jsonObjectSetKey( rest_of_query, "select",
5372 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5376 // do the query, recursively, to expand the fleshable field
5377 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5378 where_clause, rest_of_query, err );
5380 jsonObjectFree( where_clause );
5381 jsonObjectFree( rest_of_query );
5384 osrfStringArrayFree( link_fields );
5385 jsonObjectFree( res_list );
5386 jsonObjectFree( flesh_blob );
5390 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5391 osrfHashGet( kid_link, "class" ), kids->size );
5393 // Traverse the result set
5394 jsonObject* X = NULL;
5395 if( link_map->size > 0 && kids->size > 0 ) {
5397 kids = jsonNewObjectType( JSON_ARRAY );
5399 jsonObject* _k_node;
5400 unsigned long res_idx = 0;
5401 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5407 (unsigned long) atoi(
5413 osrfHashGet( kid_link, "class" )
5417 osrfStringArrayGetString( link_map, 0 )
5425 } // end while loop traversing X
5428 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5429 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5430 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5431 osrfHashGet( kid_link, "field" ));
5434 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5435 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5439 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5441 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5442 osrfHashGet( kid_link, "field" ) );
5445 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5446 jsonObjectClone( kids )
5451 jsonObjectFree( kids );
5455 jsonObjectFree( kids );
5457 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5458 osrfHashGet( kid_link, "field" ) );
5459 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5461 } // end while loop traversing list of fleshable fields
5462 } // end while loop traversing res_list
5463 jsonObjectFree( flesh_blob );
5464 osrfStringArrayFree( link_fields );
5473 int doUpdate( osrfMethodContext* ctx ) {
5474 if( osrfMethodVerifyContext( ctx )) {
5475 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5480 timeout_needs_resetting = 1;
5482 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5484 jsonObject* target = NULL;
5486 target = jsonObjectGetIndex( ctx->params, 1 );
5488 target = jsonObjectGetIndex( ctx->params, 0 );
5490 if(!verifyObjectClass( ctx, target )) {
5491 osrfAppRespondComplete( ctx, NULL );
5495 if( getXactId( ctx ) == NULL ) {
5496 osrfAppSessionStatus(
5498 OSRF_STATUS_BADREQUEST,
5499 "osrfMethodException",
5501 "No active transaction -- required for UPDATE"
5503 osrfAppRespondComplete( ctx, NULL );
5507 // The following test is harmless but redundant. If a class is
5508 // readonly, we don't register an update method for it.
5509 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5510 osrfAppSessionStatus(
5512 OSRF_STATUS_BADREQUEST,
5513 "osrfMethodException",
5515 "Cannot UPDATE readonly class"
5517 osrfAppRespondComplete( ctx, NULL );
5521 dbhandle = writehandle;
5522 const char* trans_id = getXactId( ctx );
5524 // Set the last_xact_id
5525 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5527 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5528 trans_id, target->classname, index );
5529 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5532 char* pkey = osrfHashGet( meta, "primarykey" );
5533 osrfHash* fields = osrfHashGet( meta, "fields" );
5535 char* id = oilsFMGetString( target, pkey );
5539 "%s updating %s object with %s = %s",
5541 osrfHashGet( meta, "fieldmapper" ),
5546 growing_buffer* sql = buffer_init( 128 );
5547 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5550 osrfHash* field_def = NULL;
5551 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5552 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5554 // Skip virtual fields, and the primary key
5555 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5558 const char* field_name = osrfHashIteratorKey( field_itr );
5559 if( ! strcmp( field_name, pkey ) )
5562 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5564 int value_is_numeric = 0; // boolean
5566 if( field_object && field_object->classname ) {
5567 value = oilsFMGetString(
5569 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5571 } else if( field_object && JSON_BOOL == field_object->type ) {
5572 if( jsonBoolIsTrue( field_object ) )
5573 value = strdup( "t" );
5575 value = strdup( "f" );
5577 value = jsonObjectToSimpleString( field_object );
5578 if( field_object && JSON_NUMBER == field_object->type )
5579 value_is_numeric = 1;
5582 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5583 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5585 if( !field_object || field_object->type == JSON_NULL ) {
5586 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5587 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5591 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5592 buffer_fadd( sql, " %s = NULL", field_name );
5595 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5599 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5601 const char* numtype = get_datatype( field_def );
5602 if( !strncmp( numtype, "INT", 3 ) ) {
5603 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5604 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5605 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5607 // Must really be intended as a string, so quote it
5608 if( dbi_conn_quote_string( dbhandle, &value )) {
5609 buffer_fadd( sql, " %s = %s", field_name, value );
5611 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5612 modulename, value );
5613 osrfAppSessionStatus(
5615 OSRF_STATUS_INTERNALSERVERERROR,
5616 "osrfMethodException",
5618 "Error quoting string -- please see the error log for more details"
5622 osrfHashIteratorFree( field_itr );
5624 osrfAppRespondComplete( ctx, NULL );
5629 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5632 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5636 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5637 buffer_fadd( sql, " %s = %s", field_name, value );
5639 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5640 osrfAppSessionStatus(
5642 OSRF_STATUS_INTERNALSERVERERROR,
5643 "osrfMethodException",
5645 "Error quoting string -- please see the error log for more details"
5649 osrfHashIteratorFree( field_itr );
5651 osrfAppRespondComplete( ctx, NULL );
5660 osrfHashIteratorFree( field_itr );
5662 jsonObject* obj = jsonNewObject( id );
5664 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5665 dbi_conn_quote_string( dbhandle, &id );
5667 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5669 char* query = buffer_release( sql );
5670 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5672 dbi_result result = dbi_conn_query( dbhandle, query );
5676 jsonObjectFree( obj );
5677 obj = jsonNewObject( NULL );
5680 "%s ERROR updating %s object with %s = %s",
5682 osrfHashGet( meta, "fieldmapper" ),
5689 osrfAppRespondComplete( ctx, obj );
5690 jsonObjectFree( obj );
5694 int doDelete( osrfMethodContext* ctx ) {
5695 if( osrfMethodVerifyContext( ctx )) {
5696 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5701 timeout_needs_resetting = 1;
5703 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5705 if( getXactId( ctx ) == NULL ) {
5706 osrfAppSessionStatus(
5708 OSRF_STATUS_BADREQUEST,
5709 "osrfMethodException",
5711 "No active transaction -- required for DELETE"
5713 osrfAppRespondComplete( ctx, NULL );
5717 // The following test is harmless but redundant. If a class is
5718 // readonly, we don't register a delete method for it.
5719 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5720 osrfAppSessionStatus(
5722 OSRF_STATUS_BADREQUEST,
5723 "osrfMethodException",
5725 "Cannot DELETE readonly class"
5727 osrfAppRespondComplete( ctx, NULL );
5731 dbhandle = writehandle;
5733 char* pkey = osrfHashGet( meta, "primarykey" );
5740 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5741 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5742 osrfAppRespondComplete( ctx, NULL );
5746 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5748 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5749 osrfAppRespondComplete( ctx, NULL );
5752 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5757 "%s deleting %s object with %s = %s",
5759 osrfHashGet( meta, "fieldmapper" ),
5764 jsonObject* obj = jsonNewObject( id );
5766 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5767 dbi_conn_quote_string( writehandle, &id );
5769 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
5770 osrfHashGet( meta, "tablename" ), pkey, id );
5773 jsonObjectFree( obj );
5774 obj = jsonNewObject( NULL );
5777 "%s ERROR deleting %s object with %s = %s",
5779 osrfHashGet( meta, "fieldmapper" ),
5787 osrfAppRespondComplete( ctx, obj );
5788 jsonObjectFree( obj );
5793 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5794 @param result An iterator for a result set; we only look at the current row.
5795 @param @meta Pointer to the class metadata for the core class.
5796 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5798 If a column is not defined in the IDL, or if it has no array_position defined for it in
5799 the IDL, or if it is defined as virtual, ignore it.
5801 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5802 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5803 array_position in the IDL.
5805 A field defined in the IDL but not represented in the returned row will leave a hole
5806 in the JSON_ARRAY. In effect it will be treated as a null value.
5808 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5809 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5810 classname corresponding to the @a meta argument.
5812 The calling code is responsible for freeing the the resulting jsonObject by calling
5815 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5816 if( !( result && meta )) return NULL;
5818 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5819 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
5820 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
5822 osrfHash* fields = osrfHashGet( meta, "fields" );
5824 int columnIndex = 1;
5825 const char* columnName;
5827 /* cycle through the columns in the row returned from the database */
5828 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
5830 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5832 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5834 /* determine the field type and storage attributes */
5835 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
5836 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5838 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5839 // or if it has no sequence number there, or if it's virtual, skip it.
5840 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
5843 if( str_is_true( osrfHashGet( _f, "virtual" )))
5844 continue; // skip this column: IDL says it's virtual
5846 const char* pos = (char*) osrfHashGet( _f, "array_position" );
5847 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
5848 continue; // since we assign sequence numbers dynamically as we load the IDL.
5850 fmIndex = atoi( pos );
5851 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
5853 continue; // This field is not defined in the IDL
5856 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
5857 // sequence number from the IDL (which is likely to be different from the sequence
5858 // of columns in the SELECT clause).
5859 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5860 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
5865 case DBI_TYPE_INTEGER :
5867 if( attr & DBI_INTEGER_SIZE8 )
5868 jsonObjectSetIndex( object, fmIndex,
5869 jsonNewNumberObject(
5870 dbi_result_get_longlong_idx( result, columnIndex )));
5872 jsonObjectSetIndex( object, fmIndex,
5873 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
5877 case DBI_TYPE_DECIMAL :
5878 jsonObjectSetIndex( object, fmIndex,
5879 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
5882 case DBI_TYPE_STRING :
5887 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
5892 case DBI_TYPE_DATETIME : {
5894 char dt_string[ 256 ] = "";
5897 // Fetch the date column as a time_t
5898 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
5900 // Translate the time_t to a human-readable string
5901 if( !( attr & DBI_DATETIME_DATE )) {
5902 gmtime_r( &_tmp_dt, &gmdt );
5903 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
5904 } else if( !( attr & DBI_DATETIME_TIME )) {
5905 localtime_r( &_tmp_dt, &gmdt );
5906 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
5908 localtime_r( &_tmp_dt, &gmdt );
5909 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
5912 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
5916 case DBI_TYPE_BINARY :
5917 osrfLogError( OSRF_LOG_MARK,
5918 "Can't do binary at column %s : index %d", columnName, columnIndex );
5927 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5928 if( !result ) return NULL;
5930 jsonObject* object = jsonNewObject( NULL );
5933 char dt_string[ 256 ];
5937 int columnIndex = 1;
5939 unsigned short type;
5940 const char* columnName;
5942 /* cycle through the column list */
5943 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
5945 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5947 fmIndex = -1; // reset the position
5949 /* determine the field type and storage attributes */
5950 type = dbi_result_get_field_type_idx( result, columnIndex );
5951 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5953 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5954 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
5959 case DBI_TYPE_INTEGER :
5961 if( attr & DBI_INTEGER_SIZE8 )
5962 jsonObjectSetKey( object, columnName,
5963 jsonNewNumberObject( dbi_result_get_longlong_idx(
5964 result, columnIndex )) );
5966 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
5967 dbi_result_get_int_idx( result, columnIndex )) );
5970 case DBI_TYPE_DECIMAL :
5971 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
5972 dbi_result_get_double_idx( result, columnIndex )) );
5975 case DBI_TYPE_STRING :
5976 jsonObjectSetKey( object, columnName,
5977 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
5980 case DBI_TYPE_DATETIME :
5982 memset( dt_string, '\0', sizeof( dt_string ));
5983 memset( &gmdt, '\0', sizeof( gmdt ));
5985 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
5987 if( !( attr & DBI_DATETIME_DATE )) {
5988 gmtime_r( &_tmp_dt, &gmdt );
5989 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
5990 } else if( !( attr & DBI_DATETIME_TIME )) {
5991 localtime_r( &_tmp_dt, &gmdt );
5992 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
5994 localtime_r( &_tmp_dt, &gmdt );
5995 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
5998 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6001 case DBI_TYPE_BINARY :
6002 osrfLogError( OSRF_LOG_MARK,
6003 "Can't do binary at column %s : index %d", columnName, columnIndex );
6007 } // end while loop traversing result
6012 // Interpret a string as true or false
6013 int str_is_true( const char* str ) {
6014 if( NULL == str || strcasecmp( str, "true" ) )
6020 // Interpret a jsonObject as true or false
6021 static int obj_is_true( const jsonObject* obj ) {
6024 else switch( obj->type )
6032 if( strcasecmp( obj->value.s, "true" ) )
6036 case JSON_NUMBER : // Support 1/0 for perl's sake
6037 if( jsonObjectGetNumber( obj ) == 1.0 )
6046 // Translate a numeric code into a text string identifying a type of
6047 // jsonObject. To be used for building error messages.
6048 static const char* json_type( int code ) {
6054 return "JSON_ARRAY";
6056 return "JSON_STRING";
6058 return "JSON_NUMBER";
6064 return "(unrecognized)";
6068 // Extract the "primitive" attribute from an IDL field definition.
6069 // If we haven't initialized the app, then we must be running in
6070 // some kind of testbed. In that case, default to "string".
6071 static const char* get_primitive( osrfHash* field ) {
6072 const char* s = osrfHashGet( field, "primitive" );
6074 if( child_initialized )
6077 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6079 osrfHashGet( field, "name" )
6087 // Extract the "datatype" attribute from an IDL field definition.
6088 // If we haven't initialized the app, then we must be running in
6089 // some kind of testbed. In that case, default to to NUMERIC,
6090 // since we look at the datatype only for numbers.
6091 static const char* get_datatype( osrfHash* field ) {
6092 const char* s = osrfHashGet( field, "datatype" );
6094 if( child_initialized )
6097 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6099 osrfHashGet( field, "name" )
6108 @brief Determine whether a string is potentially a valid SQL identifier.
6109 @param s The identifier to be tested.
6110 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6112 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6113 need to follow all the rules exactly, such as requiring that the first character not
6116 We allow leading and trailing white space. In between, we do not allow punctuation
6117 (except for underscores and dollar signs), control characters, or embedded white space.
6119 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6120 for the foreseeable future such quoted identifiers are not likely to be an issue.
6122 static int is_identifier( const char* s) {
6126 // Skip leading white space
6127 while( isspace( (unsigned char) *s ) )
6131 return 0; // Nothing but white space? Not okay.
6133 // Check each character until we reach white space or
6134 // end-of-string. Letters, digits, underscores, and
6135 // dollar signs are okay. With the exception of periods
6136 // (as in schema.identifier), control characters and other
6137 // punctuation characters are not okay. Anything else
6138 // is okay -- it could for example be part of a multibyte
6139 // UTF8 character such as a letter with diacritical marks,
6140 // and those are allowed.
6142 if( isalnum( (unsigned char) *s )
6146 ; // Fine; keep going
6147 else if( ispunct( (unsigned char) *s )
6148 || iscntrl( (unsigned char) *s ) )
6151 } while( *s && ! isspace( (unsigned char) *s ) );
6153 // If we found any white space in the above loop,
6154 // the rest had better be all white space.
6156 while( isspace( (unsigned char) *s ) )
6160 return 0; // White space was embedded within non-white space
6166 @brief Determine whether to accept a character string as a comparison operator.
6167 @param op The candidate comparison operator.
6168 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6170 We don't validate the operator for real. We just make sure that it doesn't contain
6171 any semicolons or white space (with special exceptions for a few specific operators).
6172 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6173 space but it's still not a valid operator, then the database will complain.
6175 Another approach would be to compare the string against a short list of approved operators.
6176 We don't do that because we want to allow custom operators like ">100*", which at this
6177 writing would be difficult or impossible to express otherwise in a JSON query.
6179 static int is_good_operator( const char* op ) {
6180 if( !op ) return 0; // Sanity check
6184 if( isspace( (unsigned char) *s ) ) {
6185 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6186 // and IS NOT DISTINCT FROM.
6187 if( !strcasecmp( op, "similar to" ) )
6189 else if( !strcasecmp( op, "is distinct from" ) )
6191 else if( !strcasecmp( op, "is not distinct from" ) )
6196 else if( ';' == *s )
6204 @name Query Frame Management
6206 The following machinery supports a stack of query frames for use by SELECT().
6208 A query frame caches information about one level of a SELECT query. When we enter
6209 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6211 The query frame stores information about the core class, and about any joined classes
6214 The main purpose is to map table aliases to classes and tables, so that a query can
6215 join to the same table more than once. A secondary goal is to reduce the number of
6216 lookups in the IDL by caching the results.
6220 #define STATIC_CLASS_INFO_COUNT 3
6222 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6225 @brief Allocate a ClassInfo as raw memory.
6226 @return Pointer to the newly allocated ClassInfo.
6228 Except for the in_use flag, which is used only by the allocation and deallocation
6229 logic, we don't initialize the ClassInfo here.
6231 static ClassInfo* allocate_class_info( void ) {
6232 // In order to reduce the number of mallocs and frees, we return a static
6233 // instance of ClassInfo, if we can find one that we're not already using.
6234 // We rely on the fact that the compiler will implicitly initialize the
6235 // static instances so that in_use == 0.
6238 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6239 if( ! static_class_info[ i ].in_use ) {
6240 static_class_info[ i ].in_use = 1;
6241 return static_class_info + i;
6245 // The static ones are all in use. Malloc one.
6247 return safe_malloc( sizeof( ClassInfo ) );
6251 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6252 @param info Pointer to the ClassInfo to be cleared.
6254 static void clear_class_info( ClassInfo* info ) {
6259 // Free any malloc'd strings
6261 if( info->alias != info->alias_store )
6262 free( info->alias );
6264 if( info->class_name != info->class_name_store )
6265 free( info->class_name );
6267 free( info->source_def );
6269 info->alias = info->class_name = info->source_def = NULL;
6274 @brief Free a ClassInfo and everything it owns.
6275 @param info Pointer to the ClassInfo to be freed.
6277 static void free_class_info( ClassInfo* info ) {
6282 clear_class_info( info );
6284 // If it's one of the static instances, just mark it as not in use
6287 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6288 if( info == static_class_info + i ) {
6289 static_class_info[ i ].in_use = 0;
6294 // Otherwise it must have been malloc'd, so free it
6300 @brief Populate an already-allocated ClassInfo.
6301 @param info Pointer to the ClassInfo to be populated.
6302 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6304 @param class Name of the class.
6305 @return Zero if successful, or 1 if not.
6307 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6308 the relevant portions of the IDL for the specified class.
6310 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6313 osrfLogError( OSRF_LOG_MARK,
6314 "%s ERROR: No ClassInfo available to populate", modulename );
6315 info->alias = info->class_name = info->source_def = NULL;
6316 info->class_def = info->fields = info->links = NULL;
6321 osrfLogError( OSRF_LOG_MARK,
6322 "%s ERROR: No class name provided for lookup", modulename );
6323 info->alias = info->class_name = info->source_def = NULL;
6324 info->class_def = info->fields = info->links = NULL;
6328 // Alias defaults to class name if not supplied
6329 if( ! alias || ! alias[ 0 ] )
6332 // Look up class info in the IDL
6333 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6335 osrfLogError( OSRF_LOG_MARK,
6336 "%s ERROR: Class %s not defined in IDL", modulename, class );
6337 info->alias = info->class_name = info->source_def = NULL;
6338 info->class_def = info->fields = info->links = NULL;
6340 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6341 osrfLogError( OSRF_LOG_MARK,
6342 "%s ERROR: Class %s is defined as virtual", modulename, class );
6343 info->alias = info->class_name = info->source_def = NULL;
6344 info->class_def = info->fields = info->links = NULL;
6348 osrfHash* links = osrfHashGet( class_def, "links" );
6350 osrfLogError( OSRF_LOG_MARK,
6351 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6352 info->alias = info->class_name = info->source_def = NULL;
6353 info->class_def = info->fields = info->links = NULL;
6357 osrfHash* fields = osrfHashGet( class_def, "fields" );
6359 osrfLogError( OSRF_LOG_MARK,
6360 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6361 info->alias = info->class_name = info->source_def = NULL;
6362 info->class_def = info->fields = info->links = NULL;
6366 char* source_def = oilsGetRelation( class_def );
6370 // We got everything we need, so populate the ClassInfo
6371 if( strlen( alias ) > ALIAS_STORE_SIZE )
6372 info->alias = strdup( alias );
6374 strcpy( info->alias_store, alias );
6375 info->alias = info->alias_store;
6378 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6379 info->class_name = strdup( class );
6381 strcpy( info->class_name_store, class );
6382 info->class_name = info->class_name_store;
6385 info->source_def = source_def;
6387 info->class_def = class_def;
6388 info->links = links;
6389 info->fields = fields;
6394 #define STATIC_FRAME_COUNT 3
6396 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6399 @brief Allocate a QueryFrame as raw memory.
6400 @return Pointer to the newly allocated QueryFrame.
6402 Except for the in_use flag, which is used only by the allocation and deallocation
6403 logic, we don't initialize the QueryFrame here.
6405 static QueryFrame* allocate_frame( void ) {
6406 // In order to reduce the number of mallocs and frees, we return a static
6407 // instance of QueryFrame, if we can find one that we're not already using.
6408 // We rely on the fact that the compiler will implicitly initialize the
6409 // static instances so that in_use == 0.
6412 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6413 if( ! static_frame[ i ].in_use ) {
6414 static_frame[ i ].in_use = 1;
6415 return static_frame + i;
6419 // The static ones are all in use. Malloc one.
6421 return safe_malloc( sizeof( QueryFrame ) );
6425 @brief Free a QueryFrame, and all the memory it owns.
6426 @param frame Pointer to the QueryFrame to be freed.
6428 static void free_query_frame( QueryFrame* frame ) {
6433 clear_class_info( &frame->core );
6435 // Free the join list
6437 ClassInfo* info = frame->join_list;
6440 free_class_info( info );
6444 frame->join_list = NULL;
6447 // If the frame is a static instance, just mark it as unused
6449 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6450 if( frame == static_frame + i ) {
6451 static_frame[ i ].in_use = 0;
6456 // Otherwise it must have been malloc'd, so free it
6462 @brief Search a given QueryFrame for a specified alias.
6463 @param frame Pointer to the QueryFrame to be searched.
6464 @param target The alias for which to search.
6465 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6467 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6468 if( ! frame || ! target ) {
6472 ClassInfo* found_class = NULL;
6474 if( !strcmp( target, frame->core.alias ) )
6475 return &(frame->core);
6477 ClassInfo* curr_class = frame->join_list;
6478 while( curr_class ) {
6479 if( strcmp( target, curr_class->alias ) )
6480 curr_class = curr_class->next;
6482 found_class = curr_class;
6492 @brief Push a new (blank) QueryFrame onto the stack.
6494 static void push_query_frame( void ) {
6495 QueryFrame* frame = allocate_frame();
6496 frame->join_list = NULL;
6497 frame->next = curr_query;
6499 // Initialize the ClassInfo for the core class
6500 ClassInfo* core = &frame->core;
6501 core->alias = core->class_name = core->source_def = NULL;
6502 core->class_def = core->fields = core->links = NULL;
6508 @brief Pop a QueryFrame off the stack and destroy it.
6510 static void pop_query_frame( void ) {
6515 QueryFrame* popped = curr_query;
6516 curr_query = popped->next;
6518 free_query_frame( popped );
6522 @brief Populate the ClassInfo for the core class.
6523 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6524 class name as an alias.
6525 @param class_name Name of the core class.
6526 @return Zero if successful, or 1 if not.
6528 Populate the ClassInfo of the core class with copies of the alias and class name, and
6529 with pointers to the relevant portions of the IDL for the core class.
6531 static int add_query_core( const char* alias, const char* class_name ) {
6534 if( ! curr_query ) {
6535 osrfLogError( OSRF_LOG_MARK,
6536 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6538 } else if( curr_query->core.alias ) {
6539 osrfLogError( OSRF_LOG_MARK,
6540 "%s ERROR: Core class %s already populated as %s",
6541 modulename, curr_query->core.class_name, curr_query->core.alias );
6545 build_class_info( &curr_query->core, alias, class_name );
6546 if( curr_query->core.alias )
6549 osrfLogError( OSRF_LOG_MARK,
6550 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6556 @brief Search the current QueryFrame for a specified alias.
6557 @param target The alias for which to search.
6558 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6560 static inline ClassInfo* search_alias( const char* target ) {
6561 return search_alias_in_frame( curr_query, target );
6565 @brief Search all levels of query for a specified alias, starting with the current query.
6566 @param target The alias for which to search.
6567 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6569 static ClassInfo* search_all_alias( const char* target ) {
6570 ClassInfo* found_class = NULL;
6571 QueryFrame* curr_frame = curr_query;
6573 while( curr_frame ) {
6574 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6577 curr_frame = curr_frame->next;
6584 @brief Add a class to the list of classes joined to the current query.
6585 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6586 the class name as an alias.
6587 @param classname The name of the class to be added.
6588 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6590 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6592 if( ! classname || ! *classname ) { // sanity check
6593 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6600 const ClassInfo* conflict = search_alias( alias );
6602 osrfLogError( OSRF_LOG_MARK,
6603 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6604 modulename, alias, conflict->class_name );
6608 ClassInfo* info = allocate_class_info();
6610 if( build_class_info( info, alias, classname ) ) {
6611 free_class_info( info );
6615 // Add the new ClassInfo to the join list of the current QueryFrame
6616 info->next = curr_query->join_list;
6617 curr_query->join_list = info;
6623 @brief Destroy all nodes on the query stack.
6625 static void clear_query_stack( void ) {