3 @brief Utility routines for translating JSON into SQL.
11 #include "opensrf/utils.h"
12 #include "opensrf/log.h"
13 #include "opensrf/osrf_application.h"
14 #include "openils/oils_utils.h"
15 #include "openils/oils_sql.h"
17 // The next four macros are OR'd together as needed to form a set
18 // of bitflags. SUBCOMBO enables an extra pair of parentheses when
19 // nesting one UNION, INTERSECT or EXCEPT inside another.
20 // SUBSELECT tells us we're in a subquery, so don't add the
21 // terminal semicolon yet.
24 #define DISABLE_I18N 2
25 #define SELECT_DISTINCT 1
30 struct ClassInfoStruct;
31 typedef struct ClassInfoStruct ClassInfo;
33 #define ALIAS_STORE_SIZE 16
34 #define CLASS_NAME_STORE_SIZE 16
36 struct ClassInfoStruct {
40 osrfHash* class_def; // Points into IDL
41 osrfHash* fields; // Points into IDL
42 osrfHash* links; // Points into IDL
44 // The remaining members are private and internal. Client code should not
45 // access them directly.
47 ClassInfo* next; // Supports linked list of joined classes
48 int in_use; // boolean
50 // We usually store the alias and class name in the following arrays, and
51 // point the corresponding pointers at them. When the string is too big
52 // for the array (which will probably never happen in practice), we strdup it.
54 char alias_store[ ALIAS_STORE_SIZE + 1 ];
55 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
58 struct QueryFrameStruct;
59 typedef struct QueryFrameStruct QueryFrame;
61 struct QueryFrameStruct {
63 ClassInfo* join_list; // linked list of classes joined to the core class
64 QueryFrame* next; // implements stack as linked list
65 int in_use; // boolean
68 static int timeout_needs_resetting;
69 static time_t time_next_reset;
71 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
73 static void setXactId( osrfMethodContext* ctx );
74 static inline const char* getXactId( osrfMethodContext* ctx );
75 static inline void clearXactId( osrfMethodContext* ctx );
77 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
78 jsonObject* where_hash, jsonObject* query_hash, int* err );
79 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
80 static jsonObject* oilsMakeJSONFromResult( dbi_result );
82 static char* searchSimplePredicate ( const char* op, const char* class_alias,
83 osrfHash* field, const jsonObject* node );
84 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
85 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject* );
86 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*,
88 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
89 static char* searchINPredicate ( const char*, osrfHash*,
90 jsonObject*, const char*, osrfMethodContext* );
91 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
92 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
93 static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
94 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
95 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
97 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
99 void userDataFree( void* );
100 static void sessionDataFree( char*, void* );
101 static int obj_is_true( const jsonObject* obj );
102 static const char* json_type( int code );
103 static const char* get_primitive( osrfHash* field );
104 static const char* get_datatype( osrfHash* field );
105 static int is_identifier( const char* s );
106 static int is_good_operator( const char* op );
107 static void pop_query_frame( void );
108 static void push_query_frame( void );
109 static int add_query_core( const char* alias, const char* class_name );
110 static inline ClassInfo* search_alias( const char* target );
111 static ClassInfo* search_all_alias( const char* target );
112 static ClassInfo* add_joined_class( const char* alias, const char* classname );
113 static void clear_query_stack( void );
115 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
116 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
117 static const char* org_tree_root( osrfMethodContext* ctx );
118 static jsonObject* single_hash( const char* key, const char* value );
120 static int child_initialized = 0; /* boolean */
122 static dbi_conn writehandle; /* our MASTER db connection */
123 static dbi_conn dbhandle; /* our CURRENT db connection */
124 //static osrfHash * readHandles;
126 // The following points to the top of a stack of QueryFrames. It's a little
127 // confusing because the top level of the query is at the bottom of the stack.
128 static QueryFrame* curr_query = NULL;
130 static dbi_conn writehandle; /* our MASTER db connection */
131 static dbi_conn dbhandle; /* our CURRENT db connection */
132 //static osrfHash * readHandles;
134 static int max_flesh_depth = 100;
136 static int enforce_pcrud = 0; // Boolean
137 static char* modulename = NULL;
140 @brief Connect to the database.
141 @return A database connection if successful, or NULL if not.
143 dbi_conn oilsConnectDB( const char* mod_name ) {
145 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
146 if( dbi_initialize( NULL ) == -1 ) {
147 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
150 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
152 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
153 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
154 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
155 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
156 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
157 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
159 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
160 dbi_conn handle = dbi_conn_new( driver );
163 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
166 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
168 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
169 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
171 if( host ) dbi_conn_set_option( handle, "host", host );
172 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
173 if( user ) dbi_conn_set_option( handle, "username", user );
174 if( pw ) dbi_conn_set_option( handle, "password", pw );
175 if( db ) dbi_conn_set_option( handle, "dbname", db );
183 if( dbi_conn_connect( handle ) < 0 ) {
185 if( dbi_conn_connect( handle ) < 0 ) {
187 dbi_conn_error( handle, &errmsg );
188 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", errmsg );
193 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
199 @brief Select some options.
200 @param module_name: Name of the server.
201 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
203 This source file is used (at this writing) to implement three different servers:
204 - open-ils.reporter-store
208 These servers behave mostly the same, but they implement different combinations of
209 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
211 Here we use the server name in messages to identify which kind of server issued them.
212 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
214 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
216 module_name = "open-ils.cstore"; // bulletproofing with a default
221 modulename = strdup( module_name );
222 enforce_pcrud = do_pcrud;
223 max_flesh_depth = flesh_depth;
227 @brief Install a database connection.
228 @param conn Pointer to a database connection.
230 In some contexts, @a conn may merely provide a driver so that we can process strings
231 properly, without providing an open database connection.
233 void oilsSetDBConnection( dbi_conn conn ) {
234 dbhandle = writehandle = conn;
238 @brief Get a table name, view name, or subquery for use in a FROM clause.
239 @param class Pointer to the IDL class entry.
240 @return A table name, a view name, or a subquery in parentheses.
242 In some cases the IDL defines a class, not with a table name or a view name, but with
243 a SELECT statement, which may be used as a subquery.
245 char* oilsGetRelation( osrfHash* classdef ) {
247 char* source_def = NULL;
248 const char* tabledef = osrfHashGet( classdef, "tablename" );
251 source_def = strdup( tabledef ); // Return the name of a table or view
253 tabledef = osrfHashGet( classdef, "source_definition" );
255 // Return a subquery, enclosed in parentheses
256 source_def = safe_malloc( strlen( tabledef ) + 3 );
257 source_def[ 0 ] = '(';
258 strcpy( source_def + 1, tabledef );
259 strcat( source_def, ")" );
261 // Not found: return an error
262 const char* classname = osrfHashGet( classdef, "classname" );
267 "%s ERROR No tablename or source_definition for class \"%s\"",
278 @brief Add datatypes from the database to the fields in the IDL.
279 @param handle Handle for a database connection
280 @return Zero if successful, or 1 upon error.
282 For each relevant class in the IDL: ask the database for the datatype of every field.
283 In particular, determine which fields are text fields and which fields are numeric
284 fields, so that we know whether to enclose their values in quotes.
286 int oilsExtendIDL( dbi_conn handle ) {
287 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
288 osrfHash* class = NULL;
289 growing_buffer* query_buf = buffer_init( 64 );
290 int results_found = 0; // boolean
292 // For each class in the IDL...
293 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
294 const char* classname = osrfHashIteratorKey( class_itr );
295 osrfHash* fields = osrfHashGet( class, "fields" );
297 // If the class is virtual, ignore it
298 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
299 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
303 char* tabledef = oilsGetRelation( class );
305 continue; // No such relation -- a query of it would be doomed to failure
307 buffer_reset( query_buf );
308 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
312 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
313 modulename, OSRF_BUFFER_C_STR( query_buf ) );
315 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
320 const char* columnName;
321 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
323 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
326 /* fetch the fieldmapper index */
327 osrfHash* _f = osrfHashGet(fields, columnName);
330 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
332 /* determine the field type and storage attributes */
334 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
336 case DBI_TYPE_INTEGER : {
338 if( !osrfHashGet(_f, "primitive") )
339 osrfHashSet(_f, "number", "primitive");
341 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
342 if( attr & DBI_INTEGER_SIZE8 )
343 osrfHashSet( _f, "INT8", "datatype" );
345 osrfHashSet( _f, "INT", "datatype" );
348 case DBI_TYPE_DECIMAL :
349 if( !osrfHashGet( _f, "primitive" ))
350 osrfHashSet( _f, "number", "primitive" );
352 osrfHashSet( _f, "NUMERIC", "datatype" );
355 case DBI_TYPE_STRING :
356 if( !osrfHashGet( _f, "primitive" ))
357 osrfHashSet( _f, "string", "primitive" );
359 osrfHashSet( _f,"TEXT", "datatype" );
362 case DBI_TYPE_DATETIME :
363 if( !osrfHashGet( _f, "primitive" ))
364 osrfHashSet( _f, "string", "primitive" );
366 osrfHashSet( _f, "TIMESTAMP", "datatype" );
369 case DBI_TYPE_BINARY :
370 if( !osrfHashGet( _f, "primitive" ))
371 osrfHashSet( _f, "string", "primitive" );
373 osrfHashSet( _f, "BYTEA", "datatype" );
378 "Setting [%s] to primitive [%s] and datatype [%s]...",
380 osrfHashGet( _f, "primitive" ),
381 osrfHashGet( _f, "datatype" )
385 } // end while loop for traversing columns of result
386 dbi_result_free( result );
388 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]...", classname );
390 } // end for each class in IDL
392 buffer_free( query_buf );
393 osrfHashIteratorFree( class_itr );
394 child_initialized = 1;
396 if( !results_found ) {
397 osrfLogError( OSRF_LOG_MARK,
398 "No results found for any class -- bad database connection?" );
406 @brief Free an osrfHash that stores a transaction ID.
407 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
409 This function is a callback, to be called by the application session when it ends.
410 The application session stores the osrfHash via an opaque pointer.
412 If the osrfHash contains an entry for the key "xact_id", it means that an
413 uncommitted transaction is pending. Roll it back.
415 void userDataFree( void* blob ) {
416 osrfHash* hash = (osrfHash*) blob;
417 if( osrfHashGet( hash, "xact_id" ) && writehandle )
418 dbi_conn_query( writehandle, "ROLLBACK;" );
420 osrfHashFree( hash );
424 @name Managing session data
425 @brief Maintain data stored via the userData pointer of the application session.
427 Currently, session-level data is stored in an osrfHash. Other arrangements are
428 possible, and some would be more efficient. The application session calls a
429 callback function to free userData before terminating.
431 Currently, the only data we store at the session level is the transaction id. By this
432 means we can ensure that any pending transactions are rolled back before the application
438 @brief Free an item in the application session's userData.
439 @param key The name of a key for an osrfHash.
440 @param item An opaque pointer to the item associated with the key.
442 We store an osrfHash as userData with the application session, and arrange (by
443 installing userDataFree() as a different callback) for the session to free that
444 osrfHash before terminating.
446 This function is a callback for freeing items in the osrfHash. Currently we store
448 - Transaction id of a pending transaction; a character string. Key: "xact_id".
449 - Authkey; a character string. Key: "authkey".
450 - User object from the authentication server; a jsonObject. Key: "user_login".
452 If we ever store anything else in userData, we will need to revisit this function so
453 that it will free whatever else needs freeing.
455 static void sessionDataFree( char* key, void* item ) {
456 if( !strcmp( key, "xact_id" )
457 || !strcmp( key, "authkey" ) ) {
459 } else if( !strcmp( key, "user_login" ) )
460 jsonObjectFree( (jsonObject*) item );
464 @brief Save a transaction id.
465 @param ctx Pointer to the method context.
467 Save the session_id of the current application session as a transaction id.
469 static void setXactId( osrfMethodContext* ctx ) {
470 if( ctx && ctx->session ) {
471 osrfAppSession* session = ctx->session;
473 osrfHash* cache = session->userData;
475 // If the session doesn't already have a hash, create one. Make sure
476 // that the application session frees the hash when it terminates.
477 if( NULL == cache ) {
478 session->userData = cache = osrfNewHash();
479 osrfHashSetCallback( cache, &sessionDataFree );
480 ctx->session->userDataFree = &userDataFree;
483 // Save the transaction id in the hash, with the key "xact_id"
484 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
489 @brief Get the transaction ID for the current transaction, if any.
490 @param ctx Pointer to the method context.
491 @return Pointer to the transaction ID.
493 The return value points to an internal buffer, and will become invalid upon issuing
494 a commit or rollback.
496 static inline const char* getXactId( osrfMethodContext* ctx ) {
497 if( ctx && ctx->session && ctx->session->userData )
498 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
504 @brief Clear the current transaction id.
505 @param ctx Pointer to the method context.
507 static inline void clearXactId( osrfMethodContext* ctx ) {
508 if( ctx && ctx->session && ctx->session->userData )
509 osrfHashRemove( ctx->session->userData, "xact_id" );
514 @brief Save the user's login in the userData for the current application session.
515 @param ctx Pointer to the method context.
516 @param user_login Pointer to the user login object to be cached (we cache the original,
519 If @a user_login is NULL, remove the user login if one is already cached.
521 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
522 if( ctx && ctx->session ) {
523 osrfAppSession* session = ctx->session;
525 osrfHash* cache = session->userData;
527 // If the session doesn't already have a hash, create one. Make sure
528 // that the application session frees the hash when it terminates.
529 if( NULL == cache ) {
530 session->userData = cache = osrfNewHash();
531 osrfHashSetCallback( cache, &sessionDataFree );
532 ctx->session->userDataFree = &userDataFree;
536 osrfHashSet( cache, user_login, "user_login" );
538 osrfHashRemove( cache, "user_login" );
543 @brief Get the user login object for the current application session, if any.
544 @param ctx Pointer to the method context.
545 @return Pointer to the user login object if found; otherwise NULL.
547 The user login object was returned from the authentication server, and then cached so
548 we don't have to call the authentication server again for the same user.
550 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
551 if( ctx && ctx->session && ctx->session->userData )
552 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
558 @brief Save a copy of an authkey in the userData of the current application session.
559 @param ctx Pointer to the method context.
560 @param authkey The authkey to be saved.
562 If @a authkey is NULL, remove the authkey if one is already cached.
564 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
565 if( ctx && ctx->session && authkey ) {
566 osrfAppSession* session = ctx->session;
567 osrfHash* cache = session->userData;
569 // If the session doesn't already have a hash, create one. Make sure
570 // that the application session frees the hash when it terminates.
571 if( NULL == cache ) {
572 session->userData = cache = osrfNewHash();
573 osrfHashSetCallback( cache, &sessionDataFree );
574 ctx->session->userDataFree = &userDataFree;
577 // Save the transaction id in the hash, with the key "xact_id"
578 if( authkey && *authkey )
579 osrfHashSet( cache, strdup( authkey ), "authkey" );
581 osrfHashRemove( cache, "authkey" );
586 @brief Reset the login timeout.
587 @param authkey The authentication key for the current login session.
588 @param now The current time.
589 @return Zero if successful, or 1 if not.
591 Tell the authentication server to reset the timeout so that the login session won't
592 expire for a while longer.
594 We could dispense with the @a now parameter by calling time(). But we just called
595 time() in order to decide whether to reset the timeout, so we might as well reuse
596 the result instead of calling time() again.
598 static int reset_timeout( const char* authkey, time_t now ) {
599 jsonObject* auth_object = jsonNewObject( authkey );
601 // Ask the authentication server to reset the timeout. It returns an event
602 // indicating success or failure.
603 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
604 "open-ils.auth.session.reset_timeout", auth_object );
605 jsonObjectFree( auth_object );
607 if( !result || result->type != JSON_HASH ) {
608 osrfLogError( OSRF_LOG_MARK,
609 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
610 jsonObjectFree( result );
611 return 1; // Not the right sort of object returned
614 const jsonObject* ilsevent = jsonObjectGetKey( result, "ilsevent" );
615 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
616 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
617 jsonObjectFree( result );
618 return 1; // Return code from method not available
621 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
622 const char* desc = jsonObjectGetString( jsonObjectGetKey( result, "desc" ) );
624 desc = "(No reason available)"; // failsafe; shouldn't happen
625 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
626 jsonObjectFree( result );
630 // Revise our local proxy for the timeout deadline
631 // by a smallish fraction of the timeout interval
632 const char* timeout = jsonObjectGetString( jsonObjectGetKey( result, "payload" ) );
634 timeout = "1"; // failsafe; shouldn't happen
635 time_next_reset = now + atoi( timeout ) / 15;
637 jsonObjectFree( result );
638 return 0; // Successfully reset timeout
642 @brief Get the authkey string for the current application session, if any.
643 @param ctx Pointer to the method context.
644 @return Pointer to the cached authkey if found; otherwise NULL.
646 If present, the authkey string was cached from a previous method call.
648 static const char* getAuthkey( osrfMethodContext* ctx ) {
649 if( ctx && ctx->session && ctx->session->userData ) {
650 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
652 // Possibly reset the authentication timeout to keep the login alive. We do so
653 // no more than once per method call, and not at all if it has been only a short
654 // time since the last reset.
656 // Here we reset explicitly, if at all. We also implicitly reset the timeout
657 // whenever we call the "open-ils.auth.session.retrieve" method.
658 if( timeout_needs_resetting ) {
659 time_t now = time( NULL );
660 if( now >= time_next_reset && reset_timeout( authkey, now ) )
661 authkey = NULL; // timeout has apparently expired already
664 timeout_needs_resetting = 0;
672 @brief Implement the transaction.begin method.
673 @param ctx Pointer to the method context.
674 @return Zero if successful, or -1 upon error.
676 Start a transaction. Save a transaction ID for future reference.
679 - authkey (PCRUD only)
681 Return to client: Transaction ID
683 int beginTransaction( osrfMethodContext* ctx ) {
684 if(osrfMethodVerifyContext( ctx )) {
685 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
689 if( enforce_pcrud ) {
690 timeout_needs_resetting = 1;
691 const jsonObject* user = verifyUserPCRUD( ctx );
696 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
698 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction", modulename );
699 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
700 "osrfMethodException", ctx->request, "Error starting transaction" );
704 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
705 osrfAppRespondComplete( ctx, ret );
706 jsonObjectFree( ret );
712 @brief Implement the savepoint.set method.
713 @param ctx Pointer to the method context.
714 @return Zero if successful, or -1 if not.
716 Issue a SAVEPOINT to the database server.
719 - authkey (PCRUD only)
722 Return to client: Savepoint name
724 int setSavepoint( osrfMethodContext* ctx ) {
725 if(osrfMethodVerifyContext( ctx )) {
726 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
731 if( enforce_pcrud ) {
733 timeout_needs_resetting = 1;
734 const jsonObject* user = verifyUserPCRUD( ctx );
739 // Verify that a transaction is pending
740 const char* trans_id = getXactId( ctx );
741 if( NULL == trans_id ) {
742 osrfAppSessionStatus(
744 OSRF_STATUS_INTERNALSERVERERROR,
745 "osrfMethodException",
747 "No active transaction -- required for savepoints"
752 // Get the savepoint name from the method params
753 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
755 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
759 "%s: Error creating savepoint %s in transaction %s",
764 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
765 "osrfMethodException", ctx->request, "Error creating savepoint" );
768 jsonObject* ret = jsonNewObject( spName );
769 osrfAppRespondComplete( ctx, ret );
770 jsonObjectFree( ret );
776 @brief Implement the savepoint.release method.
777 @param ctx Pointer to the method context.
778 @return Zero if successful, or -1 if not.
780 Issue a RELEASE SAVEPOINT to the database server.
783 - authkey (PCRUD only)
786 Return to client: Savepoint name
788 int releaseSavepoint( osrfMethodContext* ctx ) {
789 if(osrfMethodVerifyContext( ctx )) {
790 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
795 if( enforce_pcrud ) {
797 timeout_needs_resetting = 1;
798 const jsonObject* user = verifyUserPCRUD( ctx );
803 // Verify that a transaction is pending
804 const char* trans_id = getXactId( ctx );
805 if( NULL == trans_id ) {
806 osrfAppSessionStatus(
808 OSRF_STATUS_INTERNALSERVERERROR,
809 "osrfMethodException",
811 "No active transaction -- required for savepoints"
816 // Get the savepoint name from the method params
817 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
819 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
823 "%s: Error releasing savepoint %s in transaction %s",
828 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
829 "osrfMethodException", ctx->request, "Error releasing savepoint" );
832 jsonObject* ret = jsonNewObject( spName );
833 osrfAppRespondComplete( ctx, ret );
834 jsonObjectFree( ret );
840 @brief Implement the savepoint.rollback method.
841 @param ctx Pointer to the method context.
842 @return Zero if successful, or -1 if not.
844 Issue a ROLLBACK TO SAVEPOINT to the database server.
847 - authkey (PCRUD only)
850 Return to client: Savepoint name
852 int rollbackSavepoint( osrfMethodContext* ctx ) {
853 if(osrfMethodVerifyContext( ctx )) {
854 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
859 if( enforce_pcrud ) {
861 timeout_needs_resetting = 1;
862 const jsonObject* user = verifyUserPCRUD( ctx );
867 // Verify that a transaction is pending
868 const char* trans_id = getXactId( ctx );
869 if( NULL == trans_id ) {
870 osrfAppSessionStatus(
872 OSRF_STATUS_INTERNALSERVERERROR,
873 "osrfMethodException",
875 "No active transaction -- required for savepoints"
880 // Get the savepoint name from the method params
881 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
883 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
887 "%s: Error rolling back savepoint %s in transaction %s",
892 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
893 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
896 jsonObject* ret = jsonNewObject( spName );
897 osrfAppRespondComplete( ctx, ret );
898 jsonObjectFree( ret );
904 @brief Implement the transaction.commit method.
905 @param ctx Pointer to the method context.
906 @return Zero if successful, or -1 if not.
908 Issue a COMMIT to the database server.
911 - authkey (PCRUD only)
913 Return to client: Transaction ID.
915 int commitTransaction( osrfMethodContext* ctx ) {
916 if(osrfMethodVerifyContext( ctx )) {
917 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
921 if( enforce_pcrud ) {
922 timeout_needs_resetting = 1;
923 const jsonObject* user = verifyUserPCRUD( ctx );
928 // Verify that a transaction is pending
929 const char* trans_id = getXactId( ctx );
930 if( NULL == trans_id ) {
931 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
932 "osrfMethodException", ctx->request, "No active transaction to commit" );
936 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
938 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction", modulename );
939 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
940 "osrfMethodException", ctx->request, "Error committing transaction" );
943 jsonObject* ret = jsonNewObject( trans_id );
944 osrfAppRespondComplete( ctx, ret );
945 jsonObjectFree( ret );
952 @brief Implement the transaction.rollback method.
953 @param ctx Pointer to the method context.
954 @return Zero if successful, or -1 if not.
956 Issue a ROLLBACK to the database server.
959 - authkey (PCRUD only)
961 Return to client: Transaction ID
963 int rollbackTransaction( osrfMethodContext* ctx ) {
964 if( osrfMethodVerifyContext( ctx )) {
965 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
969 if( enforce_pcrud ) {
970 timeout_needs_resetting = 1;
971 const jsonObject* user = verifyUserPCRUD( ctx );
976 // Verify that a transaction is pending
977 const char* trans_id = getXactId( ctx );
978 if( NULL == trans_id ) {
979 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
980 "osrfMethodException", ctx->request, "No active transaction to roll back" );
984 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
986 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction", modulename );
987 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
988 "osrfMethodException", ctx->request, "Error rolling back transaction" );
991 jsonObject* ret = jsonNewObject( trans_id );
992 osrfAppRespondComplete( ctx, ret );
993 jsonObjectFree( ret );
1000 @brief Implement the "search" method.
1001 @param ctx Pointer to the method context.
1002 @return Zero if successful, or -1 if not.
1005 - authkey (PCRUD only)
1006 - WHERE clause, as jsonObject
1007 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1009 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1010 Optionally flesh linked fields.
1012 int doSearch( osrfMethodContext* ctx ) {
1013 if( osrfMethodVerifyContext( ctx )) {
1014 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1019 timeout_needs_resetting = 1;
1021 jsonObject* where_clause;
1022 jsonObject* rest_of_query;
1024 if( enforce_pcrud ) {
1025 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1026 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1028 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1029 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1032 // Get the class metadata
1033 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1034 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1038 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1040 osrfAppRespondComplete( ctx, NULL );
1044 // Return each row to the client (except that some may be suppressed by PCRUD)
1045 jsonObject* cur = 0;
1046 unsigned long res_idx = 0;
1047 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1048 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1050 osrfAppRespond( ctx, cur );
1052 jsonObjectFree( obj );
1054 osrfAppRespondComplete( ctx, NULL );
1059 @brief Implement the "id_list" method.
1060 @param ctx Pointer to the method context.
1061 @param err Pointer through which to return an error code.
1062 @return Zero if successful, or -1 if not.
1065 - authkey (PCRUD only)
1066 - WHERE clause, as jsonObject
1067 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1069 Return to client: The primary key values for all rows of the relevant class that
1070 satisfy a specified WHERE clause.
1072 This method relies on the assumption that every class has a primary key consisting of
1075 int doIdList( osrfMethodContext* ctx ) {
1076 if( osrfMethodVerifyContext( ctx )) {
1077 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1082 timeout_needs_resetting = 1;
1084 jsonObject* where_clause;
1085 jsonObject* rest_of_query;
1087 // We use the where clause without change. But we need to massage the rest of the
1088 // query, so we work with a copy of it instead of modifying the original.
1090 if( enforce_pcrud ) {
1091 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1092 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1094 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1095 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1098 // Eliminate certain SQL clauses, if present.
1099 if( rest_of_query ) {
1100 jsonObjectRemoveKey( rest_of_query, "select" );
1101 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1102 jsonObjectRemoveKey( rest_of_query, "flesh" );
1103 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
1105 rest_of_query = jsonNewObjectType( JSON_HASH );
1108 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1110 // Get the class metadata
1111 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1112 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1114 // Build a SELECT list containing just the primary key,
1115 // i.e. like { "classname":["keyname"] }
1116 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1118 // Load array with name of primary key
1119 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1120 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1121 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1123 jsonObjectSetKey( rest_of_query, "select", select_clause );
1128 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1130 jsonObjectFree( rest_of_query );
1132 osrfAppRespondComplete( ctx, NULL );
1136 // Return each primary key value to the client
1138 unsigned long res_idx = 0;
1139 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1140 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1141 continue; // Suppress due to lack of permission
1143 osrfAppRespond( ctx,
1144 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1147 jsonObjectFree( obj );
1148 osrfAppRespondComplete( ctx, NULL );
1153 @brief Verify that we have a valid class reference.
1154 @param ctx Pointer to the method context.
1155 @param param Pointer to the method parameters.
1156 @return 1 if the class reference is valid, or zero if it isn't.
1158 The class of the method params must match the class to which the method id devoted.
1159 For PCRUD there are additional restrictions.
1161 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1163 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1164 osrfHash* class = osrfHashGet( method_meta, "class" );
1166 // Compare the method's class to the parameters' class
1167 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1169 // Oops -- they don't match. Complain.
1170 growing_buffer* msg = buffer_init( 128 );
1173 "%s: %s method for type %s was passed a %s",
1175 osrfHashGet( method_meta, "methodtype" ),
1176 osrfHashGet( class, "classname" ),
1177 param->classname ? param->classname : "(null)"
1180 char* m = buffer_release( msg );
1181 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1189 return verifyObjectPCRUD( ctx, param );
1195 @brief (PCRUD only) Verify that the user is properly logged in.
1196 @param ctx Pointer to the method context.
1197 @return If the user is logged in, a pointer to the user object from the authentication
1198 server; otherwise NULL.
1200 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1202 // Get the authkey (the first method parameter)
1203 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1205 // See if we have the same authkey, and a user object,
1206 // locally cached from a previous call
1207 const char* cached_authkey = getAuthkey( ctx );
1208 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1209 const jsonObject* cached_user = getUserLogin( ctx );
1214 // We have no matching authentication data in the cache. Authenticate from scratch.
1215 jsonObject* auth_object = jsonNewObject( auth );
1217 // Fetch the user object from the authentication server
1218 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1220 jsonObjectFree( auth_object );
1222 if( !user->classname || strcmp(user->classname, "au" )) {
1224 growing_buffer* msg = buffer_init( 128 );
1227 "%s: permacrud received a bad auth token: %s",
1232 char* m = buffer_release( msg );
1233 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1237 jsonObjectFree( user );
1241 setUserLogin( ctx, user );
1242 setAuthkey( ctx, auth );
1244 // Allow ourselves up to a second before we have to reset the login timeout.
1245 // It would be nice to use some fraction of the timeout interval enforced by the
1246 // authentication server, but that value is not readily available at this point.
1247 // Instead, we use a conservative default interval.
1248 time_next_reset = time( NULL ) + 1;
1254 @brief For PCRUD: Determine whether the current user may access the current row.
1255 @param ctx Pointer to the method context.
1256 @param obj Pointer to the row being potentially accessed.
1257 @return 1 if access is permitted, or 0 if it isn't.
1259 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1261 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1263 dbhandle = writehandle;
1265 // Figure out what class and method are involved
1266 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1267 osrfHash* class = osrfHashGet( method_metadata, "class" );
1268 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1270 // Set fetch to 1 in all cases, meaning that for local or foreign contexts we will
1271 // always do another lookup of the current row, even if we already have a row image,
1272 // because the row image in hand may not include the foreign key(s) that we need.
1274 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1275 // but they aren't implemented yet.
1278 if( *method_type == 's' || *method_type == 'i' ) {
1279 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1280 } else if( *method_type == 'u' || *method_type == 'd' ) {
1281 fetch = 1; // MUST go to the db for the object for update and delete
1284 // Get the appropriate permacrud entry from the IDL, depending on method type
1285 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1287 // No permacrud for this method type on this class
1289 growing_buffer* msg = buffer_init( 128 );
1292 "%s: %s on class %s has no permacrud IDL entry",
1294 osrfHashGet( method_metadata, "methodtype" ),
1295 osrfHashGet( class, "classname" )
1298 char* m = buffer_release( msg );
1299 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1300 "osrfMethodException", ctx->request, m );
1307 // Get the user id, and make sure the user is logged in
1308 const jsonObject* user = verifyUserPCRUD( ctx );
1310 return 0; // Not logged in? No access.
1312 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1314 // Get a list of permissions from the permacrud entry.
1315 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1317 // Build a list of org units that own the row. This is fairly convoluted because there
1318 // are several different ways that an org unit may own the row, as defined by the
1321 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1322 // identifying an owning org_unit..
1323 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1325 // Foreign context adds a layer of indirection. The row points to some other row that
1326 // an org unit may own. The "jump" attribute, if present, adds another layer of
1328 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1330 // The following string array stores the list of org units. (We don't have a thingie
1331 // for storing lists of integers, so we fake it with a list of strings.)
1332 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1335 const char* pkey_value = NULL;
1336 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1337 // If the global_required attribute is present and true, then the only owning
1338 // org unit is the root org unit, i.e. the one with no parent.
1339 osrfLogDebug( OSRF_LOG_MARK,
1340 "global-level permissions required, fetching top of the org tree" );
1342 // check for perm at top of org tree
1343 const char* org_tree_root_id = org_tree_root( ctx );
1344 if( org_tree_root_id ) {
1345 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1346 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1348 osrfStringArrayFree( context_org_array );
1353 // If the global_required attribute is absent or false, then we look for
1354 // local and/or foreign context. In order to find the relevant foreign
1355 // keys, we must either read the relevant row from the database, or look at
1356 // the image of the row that we already have in memory.
1358 // (Herein lies a bug. Even if we have an image of the row in memory, that
1359 // image may not include the foreign key column(s) that we need.)
1361 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1362 "fetching context org ids" );
1363 const char* pkey = osrfHashGet( class, "primarykey" );
1364 jsonObject *param = NULL;
1366 if( obj->classname ) {
1367 pkey_value = oilsFMGetStringConst( obj, pkey );
1369 param = jsonObjectClone( obj );
1370 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1373 pkey_value = jsonObjectGetString( obj );
1375 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1376 "of %s and retrieving from the database", pkey_value );
1380 // Fetch the row so that we can look at the foreign key(s)
1381 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1382 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1383 jsonObjectFree( _tmp_params );
1385 param = jsonObjectExtractIndex( _list, 0 );
1386 jsonObjectFree( _list );
1390 // The row doesn't exist. Complain, and deny access.
1391 osrfLogDebug( OSRF_LOG_MARK,
1392 "Object not found in the database with primary key %s of %s",
1395 growing_buffer* msg = buffer_init( 128 );
1398 "%s: no object found with primary key %s of %s",
1404 char* m = buffer_release( msg );
1405 osrfAppSessionStatus(
1407 OSRF_STATUS_INTERNALSERVERERROR,
1408 "osrfMethodException",
1417 if( local_context && local_context->size > 0 ) {
1418 // The IDL provides a list of column names for the foreign keys denoting
1419 // local context. Look up the value of each one, and if it isn't null,
1420 // add it to the list of org units.
1421 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1422 local_context->size );
1424 const char* lcontext = NULL;
1425 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1426 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1427 if( fkey_value ) { // if not null
1428 osrfStringArrayAdd( context_org_array, fkey_value );
1431 "adding class-local field %s (value: %s) to the context org list",
1433 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1439 if( foreign_context ) {
1440 unsigned long class_count = osrfHashGetCount( foreign_context );
1441 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1443 if( class_count > 0 ) {
1445 // The IDL provides a list of foreign key columns pointing to rows that
1446 // an org unit may own. Follow each link, identify the owning org unit,
1447 // and add it to the list.
1448 osrfHash* fcontext = NULL;
1449 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1450 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1451 // For each class to which a foreign key points:
1452 const char* class_name = osrfHashIteratorKey( class_itr );
1453 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1457 "%d foreign context fields(s) specified for class %s",
1458 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1462 // Get the name of the key field in the foreign table
1463 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1465 // Get the value of the foreign key pointing to the foreign table
1466 char* foreign_pkey_value =
1467 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1468 if( !foreign_pkey_value )
1469 continue; // Foreign key value is null; skip it
1471 // Look up the row to which the foreign key points
1472 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1473 jsonObject* _list = doFieldmapperSearch(
1474 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1476 jsonObject* _fparam = NULL;
1477 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1478 _fparam = jsonObjectExtractIndex( _list, 0 );
1480 jsonObjectFree( _tmp_params );
1481 jsonObjectFree( _list );
1483 // At this point _fparam either points to the row identified by the
1484 // foreign key, or it's NULL (no such row found).
1486 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1488 const char* bad_class = NULL; // For noting failed lookups
1490 bad_class = class_name; // Referenced row not found
1491 else if( jump_list ) {
1492 // Follow a chain of rows, linked by foreign keys, to find an owner
1493 const char* flink = NULL;
1495 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1496 // For each entry in the jump list. Each entry (i.e. flink) is
1497 // the name of a foreign key column in the current row.
1499 // From the IDL, get the linkage information for the next jump
1500 osrfHash* foreign_link_hash =
1501 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1503 // Get the class metadata for the class
1504 // to which the foreign key points
1505 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1506 osrfHashGet( foreign_link_hash, "class" ));
1508 // Get the name of the referenced key of that class
1509 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1511 // Get the value of the foreign key pointing to that class
1512 free( foreign_pkey_value );
1513 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1514 if( !foreign_pkey_value )
1515 break; // Foreign key is null; quit looking
1517 // Build a WHERE clause for the lookup
1518 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1521 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1522 _tmp_params, NULL, &err );
1524 // Get the resulting row
1525 jsonObjectFree( _fparam );
1526 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1527 _fparam = jsonObjectExtractIndex( _list, 0 );
1529 // Referenced row not found
1531 bad_class = osrfHashGet( foreign_link_hash, "class" );
1534 jsonObjectFree( _tmp_params );
1535 jsonObjectFree( _list );
1541 // We had a foreign key pointing to such-and-such a row, but then
1542 // we couldn't fetch that row. The data in the database are in an
1543 // inconsistent state; the database itself may even be corrupted.
1544 growing_buffer* msg = buffer_init( 128 );
1547 "%s: no object of class %s found with primary key %s of %s",
1551 foreign_pkey_value ? foreign_pkey_value : "(null)"
1554 char* m = buffer_release( msg );
1555 osrfAppSessionStatus(
1557 OSRF_STATUS_INTERNALSERVERERROR,
1558 "osrfMethodException",
1564 osrfHashIteratorFree( class_itr );
1565 free( foreign_pkey_value );
1566 jsonObjectFree( param );
1571 free( foreign_pkey_value );
1574 // Examine each context column of the foreign row,
1575 // and add its value to the list of org units.
1577 const char* foreign_field = NULL;
1578 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1579 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1580 osrfStringArrayAdd( context_org_array,
1581 oilsFMGetStringConst( _fparam, foreign_field ));
1582 osrfLogDebug( OSRF_LOG_MARK,
1583 "adding foreign class %s field %s (value: %s) "
1584 "to the context org list",
1587 osrfStringArrayGetString(
1588 context_org_array, context_org_array->size - 1 )
1592 jsonObjectFree( _fparam );
1596 osrfHashIteratorFree( class_itr );
1600 jsonObjectFree( param );
1603 const char* context_org = NULL;
1604 const char* perm = NULL;
1607 if( permission->size == 0 ) {
1608 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1612 // For every combination of permission and context org unit: call a stored procedure
1613 // to determine if the user has this permission in the context of this org unit.
1614 // If the answer is yes at any point, then we're done, and the user has permission.
1615 // In other words permissions are additive.
1617 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1619 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1625 "Checking object permission [%s] for user %d "
1626 "on object %s (class %s) at org %d",
1630 osrfHashGet( class, "classname" ),
1634 result = dbi_conn_queryf(
1636 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1639 osrfHashGet( class, "classname" ),
1647 "Received a result for object permission [%s] "
1648 "for user %d on object %s (class %s) at org %d",
1652 osrfHashGet( class, "classname" ),
1656 if( dbi_result_first_row( result )) {
1657 jsonObject* return_val = oilsMakeJSONFromResult( result );
1658 const char* has_perm = jsonObjectGetString(
1659 jsonObjectGetKeyConst( return_val, "has_perm" ));
1663 "Status of object permission [%s] for user %d "
1664 "on object %s (class %s) at org %d is %s",
1668 osrfHashGet(class, "classname"),
1673 if( *has_perm == 't' )
1675 jsonObjectFree( return_val );
1678 dbi_result_free( result );
1684 osrfLogDebug( OSRF_LOG_MARK,
1685 "Checking non-object permission [%s] for user %d at org %d",
1686 perm, userid, atoi(context_org) );
1687 result = dbi_conn_queryf(
1689 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1696 osrfLogDebug( OSRF_LOG_MARK,
1697 "Received a result for permission [%s] for user %d at org %d",
1698 perm, userid, atoi( context_org ));
1699 if( dbi_result_first_row( result )) {
1700 jsonObject* return_val = oilsMakeJSONFromResult( result );
1701 const char* has_perm = jsonObjectGetString(
1702 jsonObjectGetKeyConst( return_val, "has_perm" ));
1703 osrfLogDebug( OSRF_LOG_MARK,
1704 "Status of permission [%s] for user %d at org %d is [%s]",
1705 perm, userid, atoi( context_org ), has_perm );
1706 if( *has_perm == 't' )
1708 jsonObjectFree( return_val );
1711 dbi_result_free( result );
1721 osrfStringArrayFree( context_org_array );
1727 @brief Look up the root of the org_unit tree.
1728 @param ctx Pointer to the method context.
1729 @return The id of the root org unit, as a character string.
1731 Query actor.org_unit where parent_ou is null, and return the id as a string.
1733 This function assumes that there is only one root org unit, i.e. that we
1734 have a single tree, not a forest.
1736 The calling code is responsible for freeing the returned string.
1738 static const char* org_tree_root( osrfMethodContext* ctx ) {
1740 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1741 static time_t last_lookup_time = 0;
1742 time_t current_time = time( NULL );
1744 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1745 // We successfully looked this up less than an hour ago.
1746 // It's not likely to have changed since then.
1747 return strdup( cached_root_id );
1749 last_lookup_time = current_time;
1752 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1753 jsonObject* result = doFieldmapperSearch(
1754 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1755 jsonObjectFree( where_clause );
1757 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1760 jsonObjectFree( result );
1762 growing_buffer* msg = buffer_init( 128 );
1763 OSRF_BUFFER_ADD( msg, modulename );
1764 OSRF_BUFFER_ADD( msg,
1765 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1767 char* m = buffer_release( msg );
1768 osrfAppSessionStatus( ctx->session,
1769 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1772 cached_root_id[ 0 ] = '\0';
1776 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
1777 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1779 strcpy( cached_root_id, root_org_unit_id );
1780 jsonObjectFree( result );
1781 return cached_root_id;
1785 @brief Create a JSON_HASH with a single key/value pair.
1786 @param key The key of the key/value pair.
1787 @param value the value of the key/value pair.
1788 @return Pointer to a newly created jsonObject of type JSON_HASH.
1790 The value of the key/value is either a string or (if @a value is NULL) a null.
1792 static jsonObject* single_hash( const char* key, const char* value ) {
1794 if( ! key ) key = "";
1796 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1797 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1802 int doCreate( osrfMethodContext* ctx ) {
1803 if(osrfMethodVerifyContext( ctx )) {
1804 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1809 timeout_needs_resetting = 1;
1811 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1812 jsonObject* target = NULL;
1813 jsonObject* options = NULL;
1815 if( enforce_pcrud ) {
1816 target = jsonObjectGetIndex( ctx->params, 1 );
1817 options = jsonObjectGetIndex( ctx->params, 2 );
1819 target = jsonObjectGetIndex( ctx->params, 0 );
1820 options = jsonObjectGetIndex( ctx->params, 1 );
1823 if( !verifyObjectClass( ctx, target )) {
1824 osrfAppRespondComplete( ctx, NULL );
1828 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1830 const char* trans_id = getXactId( ctx );
1832 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1834 osrfAppSessionStatus(
1836 OSRF_STATUS_BADREQUEST,
1837 "osrfMethodException",
1839 "No active transaction -- required for CREATE"
1841 osrfAppRespondComplete( ctx, NULL );
1845 // The following test is harmless but redundant. If a class is
1846 // readonly, we don't register a create method for it.
1847 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1848 osrfAppSessionStatus(
1850 OSRF_STATUS_BADREQUEST,
1851 "osrfMethodException",
1853 "Cannot INSERT readonly class"
1855 osrfAppRespondComplete( ctx, NULL );
1859 // Set the last_xact_id
1860 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1862 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1863 trans_id, target->classname, index);
1864 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1867 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1869 dbhandle = writehandle;
1871 osrfHash* fields = osrfHashGet( meta, "fields" );
1872 char* pkey = osrfHashGet( meta, "primarykey" );
1873 char* seq = osrfHashGet( meta, "sequence" );
1875 growing_buffer* table_buf = buffer_init( 128 );
1876 growing_buffer* col_buf = buffer_init( 128 );
1877 growing_buffer* val_buf = buffer_init( 128 );
1879 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1880 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1881 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1882 buffer_add( val_buf,"VALUES (" );
1886 osrfHash* field = NULL;
1887 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1888 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1890 const char* field_name = osrfHashIteratorKey( field_itr );
1892 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1895 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1898 if( field_object && field_object->classname ) {
1899 value = oilsFMGetString(
1901 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
1903 } else if( field_object && JSON_BOOL == field_object->type ) {
1904 if( jsonBoolIsTrue( field_object ) )
1905 value = strdup( "t" );
1907 value = strdup( "f" );
1909 value = jsonObjectToSimpleString( field_object );
1915 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1916 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1919 buffer_add( col_buf, field_name );
1921 if( !field_object || field_object->type == JSON_NULL ) {
1922 buffer_add( val_buf, "DEFAULT" );
1924 } else if( !strcmp( get_primitive( field ), "number" )) {
1925 const char* numtype = get_datatype( field );
1926 if( !strcmp( numtype, "INT8" )) {
1927 buffer_fadd( val_buf, "%lld", atoll( value ));
1929 } else if( !strcmp( numtype, "INT" )) {
1930 buffer_fadd( val_buf, "%d", atoi( value ));
1932 } else if( !strcmp( numtype, "NUMERIC" )) {
1933 buffer_fadd( val_buf, "%f", atof( value ));
1936 if( dbi_conn_quote_string( writehandle, &value )) {
1937 OSRF_BUFFER_ADD( val_buf, value );
1940 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
1941 osrfAppSessionStatus(
1943 OSRF_STATUS_INTERNALSERVERERROR,
1944 "osrfMethodException",
1946 "Error quoting string -- please see the error log for more details"
1949 buffer_free( table_buf );
1950 buffer_free( col_buf );
1951 buffer_free( val_buf );
1952 osrfAppRespondComplete( ctx, NULL );
1960 osrfHashIteratorFree( field_itr );
1962 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1963 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1965 char* table_str = buffer_release( table_buf );
1966 char* col_str = buffer_release( col_buf );
1967 char* val_str = buffer_release( val_buf );
1968 growing_buffer* sql = buffer_init( 128 );
1969 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1974 char* query = buffer_release( sql );
1976 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
1978 jsonObject* obj = NULL;
1981 dbi_result result = dbi_conn_query( writehandle, query );
1983 obj = jsonNewObject( NULL );
1986 "%s ERROR inserting %s object using query [%s]",
1988 osrfHashGet(meta, "fieldmapper"),
1991 osrfAppSessionStatus(
1993 OSRF_STATUS_INTERNALSERVERERROR,
1994 "osrfMethodException",
1996 "INSERT error -- please see the error log for more details"
2001 char* id = oilsFMGetString( target, pkey );
2003 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2004 growing_buffer* _id = buffer_init( 10 );
2005 buffer_fadd( _id, "%lld", new_id );
2006 id = buffer_release( _id );
2009 // Find quietness specification, if present
2010 const char* quiet_str = NULL;
2012 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2014 quiet_str = jsonObjectGetString( quiet_obj );
2017 if( str_is_true( quiet_str )) { // if quietness is specified
2018 obj = jsonNewObject( id );
2022 // Fetch the row that we just inserted, so that we can return it to the client
2023 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2024 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2027 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2031 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2033 jsonObjectFree( list );
2034 jsonObjectFree( where_clause );
2041 osrfAppRespondComplete( ctx, obj );
2042 jsonObjectFree( obj );
2047 @brief Implement the retrieve method.
2048 @param ctx Pointer to the method context.
2049 @param err Pointer through which to return an error code.
2050 @return If successful, a pointer to the result to be returned to the client;
2053 From the method's class, fetch a row with a specified value in the primary key. This
2054 method relies on the database design convention that a primary key consists of a single
2058 - authkey (PCRUD only)
2059 - value of the primary key for the desired row, for building the WHERE clause
2060 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2062 Return to client: One row from the query.
2064 int doRetrieve( osrfMethodContext* ctx ) {
2065 if(osrfMethodVerifyContext( ctx )) {
2066 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2071 timeout_needs_resetting = 1;
2076 if( enforce_pcrud ) {
2081 // Get the class metadata
2082 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2084 // Get the value of the primary key, from a method parameter
2085 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2089 "%s retrieving %s object with primary key value of %s",
2091 osrfHashGet( class_def, "fieldmapper" ),
2092 jsonObjectGetString( id_obj )
2095 // Build a WHERE clause based on the key value
2096 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2099 osrfHashGet( class_def, "primarykey" ), // name of key column
2100 jsonObjectClone( id_obj ) // value of key column
2103 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2107 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2109 jsonObjectFree( where_clause );
2111 osrfAppRespondComplete( ctx, NULL );
2115 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2116 jsonObjectFree( list );
2118 if( enforce_pcrud ) {
2119 if(!verifyObjectPCRUD( ctx, obj )) {
2120 jsonObjectFree( obj );
2122 growing_buffer* msg = buffer_init( 128 );
2123 OSRF_BUFFER_ADD( msg, modulename );
2124 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2126 char* m = buffer_release( msg );
2127 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2131 osrfAppRespondComplete( ctx, NULL );
2136 osrfAppRespondComplete( ctx, obj );
2137 jsonObjectFree( obj );
2142 @brief Translate a numeric value to a string representation for the database.
2143 @param field Pointer to the IDL field definition.
2144 @param value Pointer to a jsonObject holding the value of a field.
2145 @return Pointer to a newly allocated string.
2147 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2148 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2149 or (what is worse) valid SQL that is wrong.
2151 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2153 The calling code is responsible for freeing the resulting string by calling free().
2155 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2156 growing_buffer* val_buf = buffer_init( 32 );
2157 const char* numtype = get_datatype( field );
2159 // For historical reasons the following contains cruft that could be cleaned up.
2160 if( !strncmp( numtype, "INT", 3 ) ) {
2161 if( value->type == JSON_NUMBER )
2162 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2163 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2165 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2168 } else if( !strcmp( numtype, "NUMERIC" )) {
2169 if( value->type == JSON_NUMBER )
2170 buffer_fadd( val_buf, jsonObjectGetString( value ));
2172 buffer_fadd( val_buf, jsonObjectGetString( value ));
2176 // Presumably this was really intended to be a string, so quote it
2177 char* str = jsonObjectToSimpleString( value );
2178 if( dbi_conn_quote_string( dbhandle, &str )) {
2179 OSRF_BUFFER_ADD( val_buf, str );
2182 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2184 buffer_free( val_buf );
2189 return buffer_release( val_buf );
2192 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2193 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2194 growing_buffer* sql_buf = buffer_init( 32 );
2200 osrfHashGet( field, "name" )
2204 buffer_add( sql_buf, "IN (" );
2205 } else if( !strcasecmp( op,"not in" )) {
2206 buffer_add( sql_buf, "NOT IN (" );
2208 buffer_add( sql_buf, "IN (" );
2211 if( node->type == JSON_HASH ) {
2212 // subquery predicate
2213 char* subpred = buildQuery( ctx, node, SUBSELECT );
2215 buffer_free( sql_buf );
2219 buffer_add( sql_buf, subpred );
2222 } else if( node->type == JSON_ARRAY ) {
2223 // literal value list
2224 int in_item_index = 0;
2225 int in_item_first = 1;
2226 const jsonObject* in_item;
2227 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2232 buffer_add( sql_buf, ", " );
2235 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2236 osrfLogError( OSRF_LOG_MARK,
2237 "%s: Expected string or number within IN list; found %s",
2238 modulename, json_type( in_item->type ) );
2239 buffer_free( sql_buf );
2243 // Append the literal value -- quoted if not a number
2244 if( JSON_NUMBER == in_item->type ) {
2245 char* val = jsonNumberToDBString( field, in_item );
2246 OSRF_BUFFER_ADD( sql_buf, val );
2249 } else if( !strcmp( get_primitive( field ), "number" )) {
2250 char* val = jsonNumberToDBString( field, in_item );
2251 OSRF_BUFFER_ADD( sql_buf, val );
2255 char* key_string = jsonObjectToSimpleString( in_item );
2256 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2257 OSRF_BUFFER_ADD( sql_buf, key_string );
2260 osrfLogError( OSRF_LOG_MARK,
2261 "%s: Error quoting key string [%s]", modulename, key_string );
2263 buffer_free( sql_buf );
2269 if( in_item_first ) {
2270 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2271 buffer_free( sql_buf );
2275 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2276 modulename, json_type( node->type ));
2277 buffer_free( sql_buf );
2281 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2283 return buffer_release( sql_buf );
2286 // Receive a JSON_ARRAY representing a function call. The first
2287 // entry in the array is the function name. The rest are parameters.
2288 static char* searchValueTransform( const jsonObject* array ) {
2290 if( array->size < 1 ) {
2291 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2295 // Get the function name
2296 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2297 if( func_item->type != JSON_STRING ) {
2298 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2299 modulename, json_type( func_item->type ));
2303 growing_buffer* sql_buf = buffer_init( 32 );
2305 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2306 OSRF_BUFFER_ADD( sql_buf, "( " );
2308 // Get the parameters
2309 int func_item_index = 1; // We already grabbed the zeroth entry
2310 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2312 // Add a separator comma, if we need one
2313 if( func_item_index > 2 )
2314 buffer_add( sql_buf, ", " );
2316 // Add the current parameter
2317 if( func_item->type == JSON_NULL ) {
2318 buffer_add( sql_buf, "NULL" );
2320 char* val = jsonObjectToSimpleString( func_item );
2321 if( dbi_conn_quote_string( dbhandle, &val )) {
2322 OSRF_BUFFER_ADD( sql_buf, val );
2325 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2327 buffer_free( sql_buf );
2334 buffer_add( sql_buf, " )" );
2336 return buffer_release( sql_buf );
2339 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2340 const jsonObject* node, const char* op ) {
2342 if( ! is_good_operator( op ) ) {
2343 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2347 char* val = searchValueTransform( node );
2351 growing_buffer* sql_buf = buffer_init( 32 );
2356 osrfHashGet( field, "name" ),
2363 return buffer_release( sql_buf );
2366 // class_alias is a class name or other table alias
2367 // field is a field definition as stored in the IDL
2368 // node comes from the method parameter, and may represent an entry in the SELECT list
2369 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2370 const jsonObject* node ) {
2371 growing_buffer* sql_buf = buffer_init( 32 );
2373 const char* field_transform = jsonObjectGetString(
2374 jsonObjectGetKeyConst( node, "transform" ) );
2375 const char* transform_subcolumn = jsonObjectGetString(
2376 jsonObjectGetKeyConst( node, "result_field" ) );
2378 if( transform_subcolumn ) {
2379 if( ! is_identifier( transform_subcolumn ) ) {
2380 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2381 modulename, transform_subcolumn );
2382 buffer_free( sql_buf );
2385 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2388 if( field_transform ) {
2390 if( ! is_identifier( field_transform ) ) {
2391 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2392 modulename, field_transform );
2393 buffer_free( sql_buf );
2397 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2398 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2399 field_transform, class_alias, osrfHashGet( field, "name" ));
2401 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2402 field_transform, class_alias, osrfHashGet( field, "name" ));
2405 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2408 if( array->type != JSON_ARRAY ) {
2409 osrfLogError( OSRF_LOG_MARK,
2410 "%s: Expected JSON_ARRAY for function params; found %s",
2411 modulename, json_type( array->type ) );
2412 buffer_free( sql_buf );
2415 int func_item_index = 0;
2416 jsonObject* func_item;
2417 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2419 char* val = jsonObjectToSimpleString( func_item );
2422 buffer_add( sql_buf, ",NULL" );
2423 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2424 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2425 OSRF_BUFFER_ADD( sql_buf, val );
2427 osrfLogError( OSRF_LOG_MARK,
2428 "%s: Error quoting key string [%s]", modulename, val );
2430 buffer_free( sql_buf );
2437 buffer_add( sql_buf, " )" );
2440 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2443 if( transform_subcolumn )
2444 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2446 return buffer_release( sql_buf );
2449 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2450 const jsonObject* node, const char* op ) {
2452 if( ! is_good_operator( op ) ) {
2453 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2457 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2458 if( ! field_transform )
2461 int extra_parens = 0; // boolean
2463 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2465 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2467 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2469 free( field_transform );
2473 } else if( value_obj->type == JSON_ARRAY ) {
2474 value = searchValueTransform( value_obj );
2476 osrfLogError( OSRF_LOG_MARK,
2477 "%s: Error building value transform for field transform", modulename );
2478 free( field_transform );
2481 } else if( value_obj->type == JSON_HASH ) {
2482 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2484 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2486 free( field_transform );
2490 } else if( value_obj->type == JSON_NUMBER ) {
2491 value = jsonNumberToDBString( field, value_obj );
2492 } else if( value_obj->type == JSON_NULL ) {
2493 osrfLogError( OSRF_LOG_MARK,
2494 "%s: Error building predicate for field transform: null value", modulename );
2495 free( field_transform );
2497 } else if( value_obj->type == JSON_BOOL ) {
2498 osrfLogError( OSRF_LOG_MARK,
2499 "%s: Error building predicate for field transform: boolean value", modulename );
2500 free( field_transform );
2503 if( !strcmp( get_primitive( field ), "number") ) {
2504 value = jsonNumberToDBString( field, value_obj );
2506 value = jsonObjectToSimpleString( value_obj );
2507 if( !dbi_conn_quote_string( dbhandle, &value )) {
2508 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2509 modulename, value );
2511 free( field_transform );
2517 const char* left_parens = "";
2518 const char* right_parens = "";
2520 if( extra_parens ) {
2525 growing_buffer* sql_buf = buffer_init( 32 );
2529 "%s%s %s %s %s %s%s",
2540 free( field_transform );
2542 return buffer_release( sql_buf );
2545 static char* searchSimplePredicate( const char* op, const char* class_alias,
2546 osrfHash* field, const jsonObject* node ) {
2548 if( ! is_good_operator( op ) ) {
2549 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2555 // Get the value to which we are comparing the specified column
2556 if( node->type != JSON_NULL ) {
2557 if( node->type == JSON_NUMBER ) {
2558 val = jsonNumberToDBString( field, node );
2559 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2560 val = jsonNumberToDBString( field, node );
2562 val = jsonObjectToSimpleString( node );
2567 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2568 // Value is not numeric; enclose it in quotes
2569 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2570 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2577 // Compare to a null value
2578 val = strdup( "NULL" );
2579 if( strcmp( op, "=" ))
2585 growing_buffer* sql_buf = buffer_init( 32 );
2586 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2587 char* pred = buffer_release( sql_buf );
2594 static char* searchBETWEENPredicate( const char* class_alias,
2595 osrfHash* field, const jsonObject* node ) {
2597 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2598 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2600 if( NULL == y_node ) {
2601 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2604 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2605 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2612 if( !strcmp( get_primitive( field ), "number") ) {
2613 x_string = jsonNumberToDBString( field, x_node );
2614 y_string = jsonNumberToDBString( field, y_node );
2617 x_string = jsonObjectToSimpleString( x_node );
2618 y_string = jsonObjectToSimpleString( y_node );
2619 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2620 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2621 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2622 modulename, x_string, y_string );
2629 growing_buffer* sql_buf = buffer_init( 32 );
2630 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2631 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2635 return buffer_release( sql_buf );
2638 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2639 jsonObject* node, osrfMethodContext* ctx ) {
2642 if( node->type == JSON_ARRAY ) { // equality IN search
2643 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2644 } else if( node->type == JSON_HASH ) { // other search
2645 jsonIterator* pred_itr = jsonNewIterator( node );
2646 if( !jsonIteratorHasNext( pred_itr ) ) {
2647 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2648 modulename, osrfHashGet(field, "name" ));
2650 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2652 // Verify that there are no additional predicates
2653 if( jsonIteratorHasNext( pred_itr ) ) {
2654 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2655 modulename, osrfHashGet(field, "name" ));
2656 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2657 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2658 else if( !(strcasecmp( pred_itr->key,"in" ))
2659 || !(strcasecmp( pred_itr->key,"not in" )) )
2660 pred = searchINPredicate(
2661 class_info->alias, field, pred_node, pred_itr->key, ctx );
2662 else if( pred_node->type == JSON_ARRAY )
2663 pred = searchFunctionPredicate(
2664 class_info->alias, field, pred_node, pred_itr->key );
2665 else if( pred_node->type == JSON_HASH )
2666 pred = searchFieldTransformPredicate(
2667 class_info, field, pred_node, pred_itr->key );
2669 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2671 jsonIteratorFree( pred_itr );
2673 } else if( node->type == JSON_NULL ) { // IS NULL search
2674 growing_buffer* _p = buffer_init( 64 );
2677 "\"%s\".%s IS NULL",
2678 class_info->class_name,
2679 osrfHashGet( field, "name" )
2681 pred = buffer_release( _p );
2682 } else { // equality search
2683 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2702 field : call_number,
2718 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2720 const jsonObject* working_hash;
2721 jsonObject* freeable_hash = NULL;
2723 if( join_hash->type == JSON_HASH ) {
2724 working_hash = join_hash;
2725 } else if( join_hash->type == JSON_STRING ) {
2726 // turn it into a JSON_HASH by creating a wrapper
2727 // around a copy of the original
2728 const char* _tmp = jsonObjectGetString( join_hash );
2729 freeable_hash = jsonNewObjectType( JSON_HASH );
2730 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2731 working_hash = freeable_hash;
2735 "%s: JOIN failed; expected JSON object type not found",
2741 growing_buffer* join_buf = buffer_init( 128 );
2742 const char* leftclass = left_info->class_name;
2744 jsonObject* snode = NULL;
2745 jsonIterator* search_itr = jsonNewIterator( working_hash );
2747 while ( (snode = jsonIteratorNext( search_itr )) ) {
2748 const char* right_alias = search_itr->key;
2750 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2752 class = right_alias;
2754 const ClassInfo* right_info = add_joined_class( right_alias, class );
2758 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2762 jsonIteratorFree( search_itr );
2763 buffer_free( join_buf );
2765 jsonObjectFree( freeable_hash );
2768 osrfHash* links = right_info->links;
2769 const char* table = right_info->source_def;
2771 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2772 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2774 if( field && !fkey ) {
2775 // Look up the corresponding join column in the IDL.
2776 // The link must be defined in the child table,
2777 // and point to the right parent table.
2778 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2779 const char* reltype = NULL;
2780 const char* other_class = NULL;
2781 reltype = osrfHashGet( idl_link, "reltype" );
2782 if( reltype && strcmp( reltype, "has_many" ) )
2783 other_class = osrfHashGet( idl_link, "class" );
2784 if( other_class && !strcmp( other_class, leftclass ) )
2785 fkey = osrfHashGet( idl_link, "key" );
2789 "%s: JOIN failed. No link defined from %s.%s to %s",
2795 buffer_free( join_buf );
2797 jsonObjectFree( freeable_hash );
2798 jsonIteratorFree( search_itr );
2802 } else if( !field && fkey ) {
2803 // Look up the corresponding join column in the IDL.
2804 // The link must be defined in the child table,
2805 // and point to the right parent table.
2806 osrfHash* left_links = left_info->links;
2807 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2808 const char* reltype = NULL;
2809 const char* other_class = NULL;
2810 reltype = osrfHashGet( idl_link, "reltype" );
2811 if( reltype && strcmp( reltype, "has_many" ) )
2812 other_class = osrfHashGet( idl_link, "class" );
2813 if( other_class && !strcmp( other_class, class ) )
2814 field = osrfHashGet( idl_link, "key" );
2818 "%s: JOIN failed. No link defined from %s.%s to %s",
2824 buffer_free( join_buf );
2826 jsonObjectFree( freeable_hash );
2827 jsonIteratorFree( search_itr );
2831 } else if( !field && !fkey ) {
2832 osrfHash* left_links = left_info->links;
2834 // For each link defined for the left class:
2835 // see if the link references the joined class
2836 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2837 osrfHash* curr_link = NULL;
2838 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2839 const char* other_class = osrfHashGet( curr_link, "class" );
2840 if( other_class && !strcmp( other_class, class ) ) {
2842 // In the IDL, the parent class doesn't always know then names of the child
2843 // columns that are pointing to it, so don't use that end of the link
2844 const char* reltype = osrfHashGet( curr_link, "reltype" );
2845 if( reltype && strcmp( reltype, "has_many" ) ) {
2846 // Found a link between the classes
2847 fkey = osrfHashIteratorKey( itr );
2848 field = osrfHashGet( curr_link, "key" );
2853 osrfHashIteratorFree( itr );
2855 if( !field || !fkey ) {
2856 // Do another such search, with the classes reversed
2858 // For each link defined for the joined class:
2859 // see if the link references the left class
2860 osrfHashIterator* itr = osrfNewHashIterator( links );
2861 osrfHash* curr_link = NULL;
2862 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2863 const char* other_class = osrfHashGet( curr_link, "class" );
2864 if( other_class && !strcmp( other_class, leftclass ) ) {
2866 // In the IDL, the parent class doesn't know then names of the child
2867 // columns that are pointing to it, so don't use that end of the link
2868 const char* reltype = osrfHashGet( curr_link, "reltype" );
2869 if( reltype && strcmp( reltype, "has_many" ) ) {
2870 // Found a link between the classes
2871 field = osrfHashIteratorKey( itr );
2872 fkey = osrfHashGet( curr_link, "key" );
2877 osrfHashIteratorFree( itr );
2880 if( !field || !fkey ) {
2883 "%s: JOIN failed. No link defined between %s and %s",
2888 buffer_free( join_buf );
2890 jsonObjectFree( freeable_hash );
2891 jsonIteratorFree( search_itr );
2896 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2898 if( !strcasecmp( type,"left" )) {
2899 buffer_add( join_buf, " LEFT JOIN" );
2900 } else if( !strcasecmp( type,"right" )) {
2901 buffer_add( join_buf, " RIGHT JOIN" );
2902 } else if( !strcasecmp( type,"full" )) {
2903 buffer_add( join_buf, " FULL JOIN" );
2905 buffer_add( join_buf, " INNER JOIN" );
2908 buffer_add( join_buf, " INNER JOIN" );
2911 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2912 table, right_alias, right_alias, field, left_info->alias, fkey );
2914 // Add any other join conditions as specified by "filter"
2915 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2917 const char* filter_op = jsonObjectGetString(
2918 jsonObjectGetKeyConst( snode, "filter_op" ) );
2919 if( filter_op && !strcasecmp( "or",filter_op )) {
2920 buffer_add( join_buf, " OR " );
2922 buffer_add( join_buf, " AND " );
2925 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2927 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2928 OSRF_BUFFER_ADD( join_buf, jpred );
2933 "%s: JOIN failed. Invalid conditional expression.",
2936 jsonIteratorFree( search_itr );
2937 buffer_free( join_buf );
2939 jsonObjectFree( freeable_hash );
2944 buffer_add( join_buf, " ) " );
2946 // Recursively add a nested join, if one is present
2947 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2949 char* jpred = searchJOIN( join_filter, right_info );
2951 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2952 OSRF_BUFFER_ADD( join_buf, jpred );
2955 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
2956 jsonIteratorFree( search_itr );
2957 buffer_free( join_buf );
2959 jsonObjectFree( freeable_hash );
2966 jsonObjectFree( freeable_hash );
2967 jsonIteratorFree( search_itr );
2969 return buffer_release( join_buf );
2974 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2975 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2976 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2978 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2980 search_hash is the JSON expression of the conditions.
2981 meta is the class definition from the IDL, for the relevant table.
2982 opjoin_type indicates whether multiple conditions, if present, should be
2983 connected by AND or OR.
2984 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2985 to pass it to other functions -- and all they do with it is to use the session
2986 and request members to send error messages back to the client.
2990 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
2991 int opjoin_type, osrfMethodContext* ctx ) {
2995 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
2996 "opjoin_type = %d, ctx addr = %p",
2999 class_info->class_def,
3004 growing_buffer* sql_buf = buffer_init( 128 );
3006 jsonObject* node = NULL;
3009 if( search_hash->type == JSON_ARRAY ) {
3010 osrfLogDebug( OSRF_LOG_MARK,
3011 "%s: In WHERE clause, condition type is JSON_ARRAY", modulename );
3012 if( 0 == search_hash->size ) {
3015 "%s: Invalid predicate structure: empty JSON array",
3018 buffer_free( sql_buf );
3022 unsigned long i = 0;
3023 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3027 if( opjoin_type == OR_OP_JOIN )
3028 buffer_add( sql_buf, " OR " );
3030 buffer_add( sql_buf, " AND " );
3033 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3035 buffer_free( sql_buf );
3039 buffer_fadd( sql_buf, "( %s )", subpred );
3043 } else if( search_hash->type == JSON_HASH ) {
3044 osrfLogDebug( OSRF_LOG_MARK,
3045 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3046 jsonIterator* search_itr = jsonNewIterator( search_hash );
3047 if( !jsonIteratorHasNext( search_itr ) ) {
3050 "%s: Invalid predicate structure: empty JSON object",
3053 jsonIteratorFree( search_itr );
3054 buffer_free( sql_buf );
3058 while( (node = jsonIteratorNext( search_itr )) ) {
3063 if( opjoin_type == OR_OP_JOIN )
3064 buffer_add( sql_buf, " OR " );
3066 buffer_add( sql_buf, " AND " );
3069 if( '+' == search_itr->key[ 0 ] ) {
3071 // This plus sign prefixes a class name or other table alias;
3072 // make sure the table alias is in scope
3073 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3074 if( ! alias_info ) {
3077 "%s: Invalid table alias \"%s\" in WHERE clause",
3081 jsonIteratorFree( search_itr );
3082 buffer_free( sql_buf );
3086 if( node->type == JSON_STRING ) {
3087 // It's the name of a column; make sure it belongs to the class
3088 const char* fieldname = jsonObjectGetString( node );
3089 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3092 "%s: Invalid column name \"%s\" in WHERE clause "
3093 "for table alias \"%s\"",
3098 jsonIteratorFree( search_itr );
3099 buffer_free( sql_buf );
3103 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3105 // It's something more complicated
3106 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3108 jsonIteratorFree( search_itr );
3109 buffer_free( sql_buf );
3113 buffer_fadd( sql_buf, "( %s )", subpred );
3116 } else if( '-' == search_itr->key[ 0 ] ) {
3117 if( !strcasecmp( "-or", search_itr->key )) {
3118 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3120 jsonIteratorFree( search_itr );
3121 buffer_free( sql_buf );
3125 buffer_fadd( sql_buf, "( %s )", subpred );
3127 } else if( !strcasecmp( "-and", search_itr->key )) {
3128 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3130 jsonIteratorFree( search_itr );
3131 buffer_free( sql_buf );
3135 buffer_fadd( sql_buf, "( %s )", subpred );
3137 } else if( !strcasecmp("-not",search_itr->key) ) {
3138 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3140 jsonIteratorFree( search_itr );
3141 buffer_free( sql_buf );
3145 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3147 } else if( !strcasecmp( "-exists", search_itr->key )) {
3148 char* subpred = buildQuery( ctx, node, SUBSELECT );
3150 jsonIteratorFree( search_itr );
3151 buffer_free( sql_buf );
3155 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3157 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3158 char* subpred = buildQuery( ctx, node, SUBSELECT );
3160 jsonIteratorFree( search_itr );
3161 buffer_free( sql_buf );
3165 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3167 } else { // Invalid "minus" operator
3170 "%s: Invalid operator \"%s\" in WHERE clause",
3174 jsonIteratorFree( search_itr );
3175 buffer_free( sql_buf );
3181 const char* class = class_info->class_name;
3182 osrfHash* fields = class_info->fields;
3183 osrfHash* field = osrfHashGet( fields, search_itr->key );
3186 const char* table = class_info->source_def;
3189 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3192 table ? table : "?",
3195 jsonIteratorFree( search_itr );
3196 buffer_free( sql_buf );
3200 char* subpred = searchPredicate( class_info, field, node, ctx );
3202 buffer_free( sql_buf );
3203 jsonIteratorFree( search_itr );
3207 buffer_add( sql_buf, subpred );
3211 jsonIteratorFree( search_itr );
3214 // ERROR ... only hash and array allowed at this level
3215 char* predicate_string = jsonObjectToJSON( search_hash );
3218 "%s: Invalid predicate structure: %s",
3222 buffer_free( sql_buf );
3223 free( predicate_string );
3227 return buffer_release( sql_buf );
3230 /* Build a JSON_ARRAY of field names for a given table alias
3232 static jsonObject* defaultSelectList( const char* table_alias ) {
3237 ClassInfo* class_info = search_all_alias( table_alias );
3238 if( ! class_info ) {
3241 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3248 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3249 osrfHash* field_def = NULL;
3250 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3251 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3252 const char* field_name = osrfHashIteratorKey( field_itr );
3253 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3254 jsonObjectPush( array, jsonNewObject( field_name ) );
3257 osrfHashIteratorFree( field_itr );
3262 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3263 // The jsonObject must be a JSON_HASH with an single entry for "union",
3264 // "intersect", or "except". The data associated with this key must be an
3265 // array of hashes, each hash being a query.
3266 // Also allowed but currently ignored: entries for "order_by" and "alias".
3267 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3269 if( ! combo || combo->type != JSON_HASH )
3270 return NULL; // should be impossible; validated by caller
3272 const jsonObject* query_array = NULL; // array of subordinate queries
3273 const char* op = NULL; // name of operator, e.g. UNION
3274 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3275 int op_count = 0; // for detecting conflicting operators
3276 int excepting = 0; // boolean
3277 int all = 0; // boolean
3278 jsonObject* order_obj = NULL;
3280 // Identify the elements in the hash
3281 jsonIterator* query_itr = jsonNewIterator( combo );
3282 jsonObject* curr_obj = NULL;
3283 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3284 if( ! strcmp( "union", query_itr->key ) ) {
3287 query_array = curr_obj;
3288 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3291 query_array = curr_obj;
3292 } else if( ! strcmp( "except", query_itr->key ) ) {
3296 query_array = curr_obj;
3297 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3300 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3303 order_obj = curr_obj;
3304 } else if( ! strcmp( "alias", query_itr->key ) ) {
3305 if( curr_obj->type != JSON_STRING ) {
3306 jsonIteratorFree( query_itr );
3309 alias = jsonObjectGetString( curr_obj );
3310 } else if( ! strcmp( "all", query_itr->key ) ) {
3311 if( obj_is_true( curr_obj ) )
3315 osrfAppSessionStatus(
3317 OSRF_STATUS_INTERNALSERVERERROR,
3318 "osrfMethodException",
3320 "Malformed query; unexpected entry in query object"
3324 "%s: Unexpected entry for \"%s\" in%squery",
3329 jsonIteratorFree( query_itr );
3333 jsonIteratorFree( query_itr );
3335 // More sanity checks
3336 if( ! query_array ) {
3338 osrfAppSessionStatus(
3340 OSRF_STATUS_INTERNALSERVERERROR,
3341 "osrfMethodException",
3343 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3347 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3350 return NULL; // should be impossible...
3351 } else if( op_count > 1 ) {
3353 osrfAppSessionStatus(
3355 OSRF_STATUS_INTERNALSERVERERROR,
3356 "osrfMethodException",
3358 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3362 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3366 } if( query_array->type != JSON_ARRAY ) {
3368 osrfAppSessionStatus(
3370 OSRF_STATUS_INTERNALSERVERERROR,
3371 "osrfMethodException",
3373 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3377 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3380 json_type( query_array->type )
3383 } if( query_array->size < 2 ) {
3385 osrfAppSessionStatus(
3387 OSRF_STATUS_INTERNALSERVERERROR,
3388 "osrfMethodException",
3390 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3394 "%s:%srequires multiple queries as operands",
3399 } else if( excepting && query_array->size > 2 ) {
3401 osrfAppSessionStatus(
3403 OSRF_STATUS_INTERNALSERVERERROR,
3404 "osrfMethodException",
3406 "EXCEPT operator has too many queries as operands"
3410 "%s:EXCEPT operator has too many queries as operands",
3414 } else if( order_obj && ! alias ) {
3416 osrfAppSessionStatus(
3418 OSRF_STATUS_INTERNALSERVERERROR,
3419 "osrfMethodException",
3421 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3425 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3431 // So far so good. Now build the SQL.
3432 growing_buffer* sql = buffer_init( 256 );
3434 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3435 // Add a layer of parentheses
3436 if( flags & SUBCOMBO )
3437 OSRF_BUFFER_ADD( sql, "( " );
3439 // Traverse the query array. Each entry should be a hash.
3440 int first = 1; // boolean
3442 jsonObject* query = NULL;
3443 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3444 if( query->type != JSON_HASH ) {
3446 osrfAppSessionStatus(
3448 OSRF_STATUS_INTERNALSERVERERROR,
3449 "osrfMethodException",
3451 "Malformed query under UNION, INTERSECT or EXCEPT"
3455 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3458 json_type( query->type )
3467 OSRF_BUFFER_ADD( sql, op );
3469 OSRF_BUFFER_ADD( sql, "ALL " );
3472 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3476 "%s: Error building query under%s",
3484 OSRF_BUFFER_ADD( sql, query_str );
3487 if( flags & SUBCOMBO )
3488 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3490 if( !(flags & SUBSELECT) )
3491 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3493 return buffer_release( sql );
3496 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3497 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3498 // or "except" to indicate the type of query.
3499 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3503 osrfAppSessionStatus(
3505 OSRF_STATUS_INTERNALSERVERERROR,
3506 "osrfMethodException",
3508 "Malformed query; no query object"
3510 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3512 } else if( query->type != JSON_HASH ) {
3514 osrfAppSessionStatus(
3516 OSRF_STATUS_INTERNALSERVERERROR,
3517 "osrfMethodException",
3519 "Malformed query object"
3523 "%s: Query object is %s instead of JSON_HASH",
3525 json_type( query->type )
3530 // Determine what kind of query it purports to be, and dispatch accordingly.
3531 if( jsonObjectGetKey( query, "union" ) ||
3532 jsonObjectGetKey( query, "intersect" ) ||
3533 jsonObjectGetKey( query, "except" ) ) {
3534 return doCombo( ctx, query, flags );
3536 // It is presumably a SELECT query
3538 // Push a node onto the stack for the current query. Every level of
3539 // subquery gets its own QueryFrame on the Stack.
3542 // Build an SQL SELECT statement
3545 jsonObjectGetKey( query, "select" ),
3546 jsonObjectGetKey( query, "from" ),
3547 jsonObjectGetKey( query, "where" ),
3548 jsonObjectGetKey( query, "having" ),
3549 jsonObjectGetKey( query, "order_by" ),
3550 jsonObjectGetKey( query, "limit" ),
3551 jsonObjectGetKey( query, "offset" ),
3560 /* method context */ osrfMethodContext* ctx,
3562 /* SELECT */ jsonObject* selhash,
3563 /* FROM */ jsonObject* join_hash,
3564 /* WHERE */ jsonObject* search_hash,
3565 /* HAVING */ jsonObject* having_hash,
3566 /* ORDER BY */ jsonObject* order_hash,
3567 /* LIMIT */ jsonObject* limit,
3568 /* OFFSET */ jsonObject* offset,
3569 /* flags */ int flags
3571 const char* locale = osrf_message_get_last_locale();
3573 // general tmp objects
3574 const jsonObject* tmp_const;
3575 jsonObject* selclass = NULL;
3576 jsonObject* snode = NULL;
3577 jsonObject* onode = NULL;
3579 char* string = NULL;
3580 int from_function = 0;
3585 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3587 // punt if there's no FROM clause
3588 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3591 "%s: FROM clause is missing or empty",
3595 osrfAppSessionStatus(
3597 OSRF_STATUS_INTERNALSERVERERROR,
3598 "osrfMethodException",
3600 "FROM clause is missing or empty in JSON query"
3605 // the core search class
3606 const char* core_class = NULL;
3608 // get the core class -- the only key of the top level FROM clause, or a string
3609 if( join_hash->type == JSON_HASH ) {
3610 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3611 snode = jsonIteratorNext( tmp_itr );
3613 // Populate the current QueryFrame with information
3614 // about the core class
3615 if( add_query_core( NULL, tmp_itr->key ) ) {
3617 osrfAppSessionStatus(
3619 OSRF_STATUS_INTERNALSERVERERROR,
3620 "osrfMethodException",
3622 "Unable to look up core class"
3626 core_class = curr_query->core.class_name;
3629 jsonObject* extra = jsonIteratorNext( tmp_itr );
3631 jsonIteratorFree( tmp_itr );
3634 // There shouldn't be more than one entry in join_hash
3638 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3642 osrfAppSessionStatus(
3644 OSRF_STATUS_INTERNALSERVERERROR,
3645 "osrfMethodException",
3647 "Malformed FROM clause in JSON query"
3649 return NULL; // Malformed join_hash; extra entry
3651 } else if( join_hash->type == JSON_ARRAY ) {
3652 // We're selecting from a function, not from a table
3654 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3657 } else if( join_hash->type == JSON_STRING ) {
3658 // Populate the current QueryFrame with information
3659 // about the core class
3660 core_class = jsonObjectGetString( join_hash );
3662 if( add_query_core( NULL, core_class ) ) {
3664 osrfAppSessionStatus(
3666 OSRF_STATUS_INTERNALSERVERERROR,
3667 "osrfMethodException",
3669 "Unable to look up core class"
3677 "%s: FROM clause is unexpected JSON type: %s",
3679 json_type( join_hash->type )
3682 osrfAppSessionStatus(
3684 OSRF_STATUS_INTERNALSERVERERROR,
3685 "osrfMethodException",
3687 "Ill-formed FROM clause in JSON query"
3692 // Build the join clause, if any, while filling out the list
3693 // of joined classes in the current QueryFrame.
3694 char* join_clause = NULL;
3695 if( join_hash && ! from_function ) {
3697 join_clause = searchJOIN( join_hash, &curr_query->core );
3698 if( ! join_clause ) {
3700 osrfAppSessionStatus(
3702 OSRF_STATUS_INTERNALSERVERERROR,
3703 "osrfMethodException",
3705 "Unable to construct JOIN clause(s)"
3711 // For in case we don't get a select list
3712 jsonObject* defaultselhash = NULL;
3714 // if there is no select list, build a default select list ...
3715 if( !selhash && !from_function ) {
3716 jsonObject* default_list = defaultSelectList( core_class );
3717 if( ! default_list ) {
3719 osrfAppSessionStatus(
3721 OSRF_STATUS_INTERNALSERVERERROR,
3722 "osrfMethodException",
3724 "Unable to build default SELECT clause in JSON query"
3726 free( join_clause );
3731 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3732 jsonObjectSetKey( selhash, core_class, default_list );
3735 // The SELECT clause can be encoded only by a hash
3736 if( !from_function && selhash->type != JSON_HASH ) {
3739 "%s: Expected JSON_HASH for SELECT clause; found %s",
3741 json_type( selhash->type )
3745 osrfAppSessionStatus(
3747 OSRF_STATUS_INTERNALSERVERERROR,
3748 "osrfMethodException",
3750 "Malformed SELECT clause in JSON query"
3752 free( join_clause );
3756 // If you see a null or wild card specifier for the core class, or an
3757 // empty array, replace it with a default SELECT list
3758 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3760 int default_needed = 0; // boolean
3761 if( JSON_STRING == tmp_const->type
3762 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3764 else if( JSON_NULL == tmp_const->type )
3767 if( default_needed ) {
3768 // Build a default SELECT list
3769 jsonObject* default_list = defaultSelectList( core_class );
3770 if( ! default_list ) {
3772 osrfAppSessionStatus(
3774 OSRF_STATUS_INTERNALSERVERERROR,
3775 "osrfMethodException",
3777 "Can't build default SELECT clause in JSON query"
3779 free( join_clause );
3784 jsonObjectSetKey( selhash, core_class, default_list );
3788 // temp buffers for the SELECT list and GROUP BY clause
3789 growing_buffer* select_buf = buffer_init( 128 );
3790 growing_buffer* group_buf = buffer_init( 128 );
3792 int aggregate_found = 0; // boolean
3794 // Build a select list
3795 if( from_function ) // From a function we select everything
3796 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3799 // Build the SELECT list as SQL
3803 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3804 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3806 const char* cname = selclass_itr->key;
3808 // Make sure the target relation is in the FROM clause.
3810 // At this point join_hash is a step down from the join_hash we
3811 // received as a parameter. If the original was a JSON_STRING,
3812 // then json_hash is now NULL. If the original was a JSON_HASH,
3813 // then json_hash is now the first (and only) entry in it,
3814 // denoting the core class. We've already excluded the
3815 // possibility that the original was a JSON_ARRAY, because in
3816 // that case from_function would be non-NULL, and we wouldn't
3819 // If the current table alias isn't in scope, bail out
3820 ClassInfo* class_info = search_alias( cname );
3821 if( ! class_info ) {
3824 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3829 osrfAppSessionStatus(
3831 OSRF_STATUS_INTERNALSERVERERROR,
3832 "osrfMethodException",
3834 "Selected class not in FROM clause in JSON query"
3836 jsonIteratorFree( selclass_itr );
3837 buffer_free( select_buf );
3838 buffer_free( group_buf );
3839 if( defaultselhash )
3840 jsonObjectFree( defaultselhash );
3841 free( join_clause );
3845 if( selclass->type != JSON_ARRAY ) {
3848 "%s: Malformed SELECT list for class \"%s\"; not an array",
3853 osrfAppSessionStatus(
3855 OSRF_STATUS_INTERNALSERVERERROR,
3856 "osrfMethodException",
3858 "Selected class not in FROM clause in JSON query"
3861 jsonIteratorFree( selclass_itr );
3862 buffer_free( select_buf );
3863 buffer_free( group_buf );
3864 if( defaultselhash )
3865 jsonObjectFree( defaultselhash );
3866 free( join_clause );
3870 // Look up some attributes of the current class
3871 osrfHash* idlClass = class_info->class_def;
3872 osrfHash* class_field_set = class_info->fields;
3873 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3874 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3876 if( 0 == selclass->size ) {
3879 "%s: No columns selected from \"%s\"",
3885 // stitch together the column list for the current table alias...
3886 unsigned long field_idx = 0;
3887 jsonObject* selfield = NULL;
3888 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3890 // If we need a separator comma, add one
3894 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3897 // if the field specification is a string, add it to the list
3898 if( selfield->type == JSON_STRING ) {
3900 // Look up the field in the IDL
3901 const char* col_name = jsonObjectGetString( selfield );
3902 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3904 // No such field in current class
3907 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3913 osrfAppSessionStatus(
3915 OSRF_STATUS_INTERNALSERVERERROR,
3916 "osrfMethodException",
3918 "Selected column not defined in JSON query"
3920 jsonIteratorFree( selclass_itr );
3921 buffer_free( select_buf );
3922 buffer_free( group_buf );
3923 if( defaultselhash )
3924 jsonObjectFree( defaultselhash );
3925 free( join_clause );
3927 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3928 // Virtual field not allowed
3931 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3937 osrfAppSessionStatus(
3939 OSRF_STATUS_INTERNALSERVERERROR,
3940 "osrfMethodException",
3942 "Selected column may not be virtual in JSON query"
3944 jsonIteratorFree( selclass_itr );
3945 buffer_free( select_buf );
3946 buffer_free( group_buf );
3947 if( defaultselhash )
3948 jsonObjectFree( defaultselhash );
3949 free( join_clause );
3955 if( flags & DISABLE_I18N )
3958 i18n = osrfHashGet( field_def, "i18n" );
3960 if( str_is_true( i18n ) ) {
3961 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
3962 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3963 class_tname, cname, col_name, class_pkey,
3964 cname, class_pkey, locale, col_name );
3966 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3967 cname, col_name, col_name );
3970 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
3971 cname, col_name, col_name );
3974 // ... but it could be an object, in which case we check for a Field Transform
3975 } else if( selfield->type == JSON_HASH ) {
3977 const char* col_name = jsonObjectGetString(
3978 jsonObjectGetKeyConst( selfield, "column" ) );
3980 // Get the field definition from the IDL
3981 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3983 // No such field in current class
3986 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3992 osrfAppSessionStatus(
3994 OSRF_STATUS_INTERNALSERVERERROR,
3995 "osrfMethodException",
3997 "Selected column is not defined in JSON query"
3999 jsonIteratorFree( selclass_itr );
4000 buffer_free( select_buf );
4001 buffer_free( group_buf );
4002 if( defaultselhash )
4003 jsonObjectFree( defaultselhash );
4004 free( join_clause );
4006 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4007 // No such field in current class
4010 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4016 osrfAppSessionStatus(
4018 OSRF_STATUS_INTERNALSERVERERROR,
4019 "osrfMethodException",
4021 "Selected column is virtual in JSON query"
4023 jsonIteratorFree( selclass_itr );
4024 buffer_free( select_buf );
4025 buffer_free( group_buf );
4026 if( defaultselhash )
4027 jsonObjectFree( defaultselhash );
4028 free( join_clause );
4032 // Decide what to use as a column alias
4034 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4035 _alias = jsonObjectGetString( tmp_const );
4036 } else { // Use field name as the alias
4040 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4041 char* transform_str = searchFieldTransform(
4042 class_info->alias, field_def, selfield );
4043 if( transform_str ) {
4044 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4045 free( transform_str );
4048 osrfAppSessionStatus(
4050 OSRF_STATUS_INTERNALSERVERERROR,
4051 "osrfMethodException",
4053 "Unable to generate transform function in JSON query"
4055 jsonIteratorFree( selclass_itr );
4056 buffer_free( select_buf );
4057 buffer_free( group_buf );
4058 if( defaultselhash )
4059 jsonObjectFree( defaultselhash );
4060 free( join_clause );
4067 if( flags & DISABLE_I18N )
4070 i18n = osrfHashGet( field_def, "i18n" );
4072 if( str_is_true( i18n ) ) {
4073 buffer_fadd( select_buf,
4074 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4075 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4076 class_tname, cname, col_name, class_pkey, cname,
4077 class_pkey, locale, _alias );
4079 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4080 cname, col_name, _alias );
4083 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4084 cname, col_name, _alias );
4091 "%s: Selected item is unexpected JSON type: %s",
4093 json_type( selfield->type )
4096 osrfAppSessionStatus(
4098 OSRF_STATUS_INTERNALSERVERERROR,
4099 "osrfMethodException",
4101 "Ill-formed SELECT item in JSON query"
4103 jsonIteratorFree( selclass_itr );
4104 buffer_free( select_buf );
4105 buffer_free( group_buf );
4106 if( defaultselhash )
4107 jsonObjectFree( defaultselhash );
4108 free( join_clause );
4112 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
4113 if( obj_is_true( agg_obj ) )
4114 aggregate_found = 1;
4116 // Append a comma (except for the first one)
4117 // and add the column to a GROUP BY clause
4121 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4123 buffer_fadd( group_buf, " %d", sel_pos );
4127 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4129 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4130 if ( ! obj_is_true( aggregate_obj ) ) {
4134 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4137 buffer_fadd(group_buf, " %d", sel_pos);
4140 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4144 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4147 _column = searchFieldTransform(class_info->alias, field, selfield);
4148 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4149 OSRF_BUFFER_ADD(group_buf, _column);
4150 _column = searchFieldTransform(class_info->alias, field, selfield);
4157 } // end while -- iterating across SELECT columns
4159 } // end while -- iterating across classes
4161 jsonIteratorFree( selclass_itr );
4165 char* col_list = buffer_release( select_buf );
4167 // Make sure the SELECT list isn't empty. This can happen, for example,
4168 // if we try to build a default SELECT clause from a non-core table.
4171 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4173 osrfAppSessionStatus(
4175 OSRF_STATUS_INTERNALSERVERERROR,
4176 "osrfMethodException",
4178 "SELECT list is empty"
4181 buffer_free( group_buf );
4182 if( defaultselhash )
4183 jsonObjectFree( defaultselhash );
4184 free( join_clause );
4190 table = searchValueTransform( join_hash );
4192 table = strdup( curr_query->core.source_def );
4196 osrfAppSessionStatus(
4198 OSRF_STATUS_INTERNALSERVERERROR,
4199 "osrfMethodException",
4201 "Unable to identify table for core class"
4204 buffer_free( group_buf );
4205 if( defaultselhash )
4206 jsonObjectFree( defaultselhash );
4207 free( join_clause );
4211 // Put it all together
4212 growing_buffer* sql_buf = buffer_init( 128 );
4213 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4217 // Append the join clause, if any
4219 buffer_add(sql_buf, join_clause );
4220 free( join_clause );
4223 char* order_by_list = NULL;
4224 char* having_buf = NULL;
4226 if( !from_function ) {
4228 // Build a WHERE clause, if there is one
4230 buffer_add( sql_buf, " WHERE " );
4232 // and it's on the WHERE clause
4233 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4236 osrfAppSessionStatus(
4238 OSRF_STATUS_INTERNALSERVERERROR,
4239 "osrfMethodException",
4241 "Severe query error in WHERE predicate -- see error log for more details"
4244 buffer_free( group_buf );
4245 buffer_free( sql_buf );
4246 if( defaultselhash )
4247 jsonObjectFree( defaultselhash );
4251 buffer_add( sql_buf, pred );
4255 // Build a HAVING clause, if there is one
4258 // and it's on the the WHERE clause
4259 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4261 if( ! having_buf ) {
4263 osrfAppSessionStatus(
4265 OSRF_STATUS_INTERNALSERVERERROR,
4266 "osrfMethodException",
4268 "Severe query error in HAVING predicate -- see error log for more details"
4271 buffer_free( group_buf );
4272 buffer_free( sql_buf );
4273 if( defaultselhash )
4274 jsonObjectFree( defaultselhash );
4279 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4281 // Build an ORDER BY clause, if there is one
4282 if( NULL == order_hash )
4283 ; // No ORDER BY? do nothing
4284 else if( JSON_ARRAY == order_hash->type ) {
4285 // Array of field specifications, each specification being a
4286 // hash to define the class, field, and other details
4288 jsonObject* order_spec;
4289 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4291 if( JSON_HASH != order_spec->type ) {
4292 osrfLogError( OSRF_LOG_MARK,
4293 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4294 modulename, json_type( order_spec->type ) );
4296 osrfAppSessionStatus(
4298 OSRF_STATUS_INTERNALSERVERERROR,
4299 "osrfMethodException",
4301 "Malformed ORDER BY clause -- see error log for more details"
4303 buffer_free( order_buf );
4305 buffer_free( group_buf );
4306 buffer_free( sql_buf );
4307 if( defaultselhash )
4308 jsonObjectFree( defaultselhash );
4312 const char* class_alias =
4313 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4315 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4318 OSRF_BUFFER_ADD( order_buf, ", " );
4320 order_buf = buffer_init( 128 );
4322 if( !field || !class_alias ) {
4323 osrfLogError( OSRF_LOG_MARK,
4324 "%s: Missing class or field name in field specification "
4325 "of ORDER BY clause",
4328 osrfAppSessionStatus(
4330 OSRF_STATUS_INTERNALSERVERERROR,
4331 "osrfMethodException",
4333 "Malformed ORDER BY clause -- see error log for more details"
4335 buffer_free( order_buf );
4337 buffer_free( group_buf );
4338 buffer_free( sql_buf );
4339 if( defaultselhash )
4340 jsonObjectFree( defaultselhash );
4344 ClassInfo* order_class_info = search_alias( class_alias );
4345 if( ! order_class_info ) {
4346 osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4347 "not in FROM clause", modulename, class_alias );
4349 osrfAppSessionStatus(
4351 OSRF_STATUS_INTERNALSERVERERROR,
4352 "osrfMethodException",
4354 "Invalid class referenced in ORDER BY clause -- "
4355 "see error log for more details"
4358 buffer_free( group_buf );
4359 buffer_free( sql_buf );
4360 if( defaultselhash )
4361 jsonObjectFree( defaultselhash );
4365 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4367 osrfLogError( OSRF_LOG_MARK,
4368 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4369 modulename, class_alias, field );
4371 osrfAppSessionStatus(
4373 OSRF_STATUS_INTERNALSERVERERROR,
4374 "osrfMethodException",
4376 "Invalid field referenced in ORDER BY clause -- "
4377 "see error log for more details"
4380 buffer_free( group_buf );
4381 buffer_free( sql_buf );
4382 if( defaultselhash )
4383 jsonObjectFree( defaultselhash );
4385 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4386 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4387 modulename, field );
4389 osrfAppSessionStatus(
4391 OSRF_STATUS_INTERNALSERVERERROR,
4392 "osrfMethodException",
4394 "Virtual field in ORDER BY clause -- see error log for more details"
4396 buffer_free( order_buf );
4398 buffer_free( group_buf );
4399 buffer_free( sql_buf );
4400 if( defaultselhash )
4401 jsonObjectFree( defaultselhash );
4405 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4406 char* transform_str = searchFieldTransform(
4407 class_alias, field_def, order_spec );
4408 if( ! transform_str ) {
4410 osrfAppSessionStatus(
4412 OSRF_STATUS_INTERNALSERVERERROR,
4413 "osrfMethodException",
4415 "Severe query error in ORDER BY clause -- "
4416 "see error log for more details"
4418 buffer_free( order_buf );
4420 buffer_free( group_buf );
4421 buffer_free( sql_buf );
4422 if( defaultselhash )
4423 jsonObjectFree( defaultselhash );
4427 OSRF_BUFFER_ADD( order_buf, transform_str );
4428 free( transform_str );
4431 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4433 const char* direction =
4434 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4436 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4437 OSRF_BUFFER_ADD( order_buf, " DESC" );
4439 OSRF_BUFFER_ADD( order_buf, " ASC" );
4442 } else if( JSON_HASH == order_hash->type ) {
4443 // This hash is keyed on class alias. Each class has either
4444 // an array of field names or a hash keyed on field name.
4445 jsonIterator* class_itr = jsonNewIterator( order_hash );
4446 while( (snode = jsonIteratorNext( class_itr )) ) {
4448 ClassInfo* order_class_info = search_alias( class_itr->key );
4449 if( ! order_class_info ) {
4450 osrfLogError( OSRF_LOG_MARK,
4451 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4452 modulename, class_itr->key );
4454 osrfAppSessionStatus(
4456 OSRF_STATUS_INTERNALSERVERERROR,
4457 "osrfMethodException",
4459 "Invalid class referenced in ORDER BY clause -- "
4460 "see error log for more details"
4462 jsonIteratorFree( class_itr );
4463 buffer_free( order_buf );
4465 buffer_free( group_buf );
4466 buffer_free( sql_buf );
4467 if( defaultselhash )
4468 jsonObjectFree( defaultselhash );
4472 osrfHash* field_list_def = order_class_info->fields;
4474 if( snode->type == JSON_HASH ) {
4476 // Hash is keyed on field names from the current class. For each field
4477 // there is another layer of hash to define the sorting details, if any,
4478 // or a string to indicate direction of sorting.
4479 jsonIterator* order_itr = jsonNewIterator( snode );
4480 while( (onode = jsonIteratorNext( order_itr )) ) {
4482 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4484 osrfLogError( OSRF_LOG_MARK,
4485 "%s: Invalid field \"%s\" in ORDER BY clause",
4486 modulename, order_itr->key );
4488 osrfAppSessionStatus(
4490 OSRF_STATUS_INTERNALSERVERERROR,
4491 "osrfMethodException",
4493 "Invalid field in ORDER BY clause -- "
4494 "see error log for more details"
4496 jsonIteratorFree( order_itr );
4497 jsonIteratorFree( class_itr );
4498 buffer_free( order_buf );
4500 buffer_free( group_buf );
4501 buffer_free( sql_buf );
4502 if( defaultselhash )
4503 jsonObjectFree( defaultselhash );
4505 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4506 osrfLogError( OSRF_LOG_MARK,
4507 "%s: Virtual field \"%s\" in ORDER BY clause",
4508 modulename, order_itr->key );
4510 osrfAppSessionStatus(
4512 OSRF_STATUS_INTERNALSERVERERROR,
4513 "osrfMethodException",
4515 "Virtual field in ORDER BY clause -- "
4516 "see error log for more details"
4518 jsonIteratorFree( order_itr );
4519 jsonIteratorFree( class_itr );
4520 buffer_free( order_buf );
4522 buffer_free( group_buf );
4523 buffer_free( sql_buf );
4524 if( defaultselhash )
4525 jsonObjectFree( defaultselhash );
4529 const char* direction = NULL;
4530 if( onode->type == JSON_HASH ) {
4531 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4532 string = searchFieldTransform(
4534 osrfHashGet( field_list_def, order_itr->key ),
4538 if( ctx ) osrfAppSessionStatus(
4540 OSRF_STATUS_INTERNALSERVERERROR,
4541 "osrfMethodException",
4543 "Severe query error in ORDER BY clause -- "
4544 "see error log for more details"
4546 jsonIteratorFree( order_itr );
4547 jsonIteratorFree( class_itr );
4549 buffer_free( group_buf );
4550 buffer_free( order_buf);
4551 buffer_free( sql_buf );
4552 if( defaultselhash )
4553 jsonObjectFree( defaultselhash );
4557 growing_buffer* field_buf = buffer_init( 16 );
4558 buffer_fadd( field_buf, "\"%s\".%s",
4559 class_itr->key, order_itr->key );
4560 string = buffer_release( field_buf );
4563 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4564 const char* dir = jsonObjectGetString( tmp_const );
4565 if(!strncasecmp( dir, "d", 1 )) {
4566 direction = " DESC";
4572 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4573 osrfLogError( OSRF_LOG_MARK,
4574 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4575 modulename, json_type( onode->type ) );
4577 osrfAppSessionStatus(
4579 OSRF_STATUS_INTERNALSERVERERROR,
4580 "osrfMethodException",
4582 "Malformed ORDER BY clause -- see error log for more details"
4584 jsonIteratorFree( order_itr );
4585 jsonIteratorFree( class_itr );
4587 buffer_free( group_buf );
4588 buffer_free( order_buf );
4589 buffer_free( sql_buf );
4590 if( defaultselhash )
4591 jsonObjectFree( defaultselhash );
4595 string = strdup( order_itr->key );
4596 const char* dir = jsonObjectGetString( onode );
4597 if( !strncasecmp( dir, "d", 1 )) {
4598 direction = " DESC";
4605 OSRF_BUFFER_ADD( order_buf, ", " );
4607 order_buf = buffer_init( 128 );
4609 OSRF_BUFFER_ADD( order_buf, string );
4613 OSRF_BUFFER_ADD( order_buf, direction );
4617 jsonIteratorFree( order_itr );
4619 } else if( snode->type == JSON_ARRAY ) {
4621 // Array is a list of fields from the current class
4622 unsigned long order_idx = 0;
4623 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4625 const char* _f = jsonObjectGetString( onode );
4627 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4629 osrfLogError( OSRF_LOG_MARK,
4630 "%s: Invalid field \"%s\" in ORDER BY clause",
4633 osrfAppSessionStatus(
4635 OSRF_STATUS_INTERNALSERVERERROR,
4636 "osrfMethodException",
4638 "Invalid field in ORDER BY clause -- "
4639 "see error log for more details"
4641 jsonIteratorFree( class_itr );
4642 buffer_free( order_buf );
4644 buffer_free( group_buf );
4645 buffer_free( sql_buf );
4646 if( defaultselhash )
4647 jsonObjectFree( defaultselhash );
4649 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4650 osrfLogError( OSRF_LOG_MARK,
4651 "%s: Virtual field \"%s\" in ORDER BY clause",
4654 osrfAppSessionStatus(
4656 OSRF_STATUS_INTERNALSERVERERROR,
4657 "osrfMethodException",
4659 "Virtual field in ORDER BY clause -- "
4660 "see error log for more details"
4662 jsonIteratorFree( class_itr );
4663 buffer_free( order_buf );
4665 buffer_free( group_buf );
4666 buffer_free( sql_buf );
4667 if( defaultselhash )
4668 jsonObjectFree( defaultselhash );
4673 OSRF_BUFFER_ADD( order_buf, ", " );
4675 order_buf = buffer_init( 128 );
4677 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4681 // IT'S THE OOOOOOOOOOOLD STYLE!
4683 osrfLogError( OSRF_LOG_MARK,
4684 "%s: Possible SQL injection attempt; direct order by is not allowed",
4687 osrfAppSessionStatus(
4689 OSRF_STATUS_INTERNALSERVERERROR,
4690 "osrfMethodException",
4692 "Severe query error -- see error log for more details"
4697 buffer_free( group_buf );
4698 buffer_free( order_buf );
4699 buffer_free( sql_buf );
4700 if( defaultselhash )
4701 jsonObjectFree( defaultselhash );
4702 jsonIteratorFree( class_itr );
4706 jsonIteratorFree( class_itr );
4708 osrfLogError( OSRF_LOG_MARK,
4709 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4710 modulename, json_type( order_hash->type ) );
4712 osrfAppSessionStatus(
4714 OSRF_STATUS_INTERNALSERVERERROR,
4715 "osrfMethodException",
4717 "Malformed ORDER BY clause -- see error log for more details"
4719 buffer_free( order_buf );
4721 buffer_free( group_buf );
4722 buffer_free( sql_buf );
4723 if( defaultselhash )
4724 jsonObjectFree( defaultselhash );
4729 order_by_list = buffer_release( order_buf );
4733 string = buffer_release( group_buf );
4735 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4736 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4737 OSRF_BUFFER_ADD( sql_buf, string );
4742 if( having_buf && *having_buf ) {
4743 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4744 OSRF_BUFFER_ADD( sql_buf, having_buf );
4748 if( order_by_list ) {
4750 if( *order_by_list ) {
4751 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4752 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4755 free( order_by_list );
4759 const char* str = jsonObjectGetString( limit );
4760 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4764 const char* str = jsonObjectGetString( offset );
4765 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4768 if( !(flags & SUBSELECT) )
4769 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4771 if( defaultselhash )
4772 jsonObjectFree( defaultselhash );
4774 return buffer_release( sql_buf );
4776 } // end of SELECT()
4778 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4780 const char* locale = osrf_message_get_last_locale();
4782 osrfHash* fields = osrfHashGet( meta, "fields" );
4783 char* core_class = osrfHashGet( meta, "classname" );
4785 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4787 jsonObject* node = NULL;
4788 jsonObject* snode = NULL;
4789 jsonObject* onode = NULL;
4790 const jsonObject* _tmp = NULL;
4791 jsonObject* selhash = NULL;
4792 jsonObject* defaultselhash = NULL;
4794 growing_buffer* sql_buf = buffer_init( 128 );
4795 growing_buffer* select_buf = buffer_init( 128 );
4797 if( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4798 defaultselhash = jsonNewObjectType( JSON_HASH );
4799 selhash = defaultselhash;
4802 // If there's no SELECT list for the core class, build one
4803 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4804 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4806 // Add every non-virtual field to the field list
4807 osrfHash* field_def = NULL;
4808 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4809 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4810 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4811 const char* field = osrfHashIteratorKey( field_itr );
4812 jsonObjectPush( field_list, jsonNewObject( field ) );
4815 osrfHashIteratorFree( field_itr );
4816 jsonObjectSetKey( selhash, core_class, field_list );
4820 jsonIterator* class_itr = jsonNewIterator( selhash );
4821 while( (snode = jsonIteratorNext( class_itr )) ) {
4823 const char* cname = class_itr->key;
4824 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4828 if( strcmp(core_class,class_itr->key )) {
4832 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4833 if( !found->size ) {
4834 jsonObjectFree( found );
4838 jsonObjectFree( found );
4841 jsonIterator* select_itr = jsonNewIterator( snode );
4842 while( (node = jsonIteratorNext( select_itr )) ) {
4843 const char* item_str = jsonObjectGetString( node );
4844 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4845 char* fname = osrfHashGet( field, "name" );
4853 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4858 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4859 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4862 i18n = osrfHashGet( field, "i18n" );
4864 if( str_is_true( i18n ) ) {
4865 char* pkey = osrfHashGet( idlClass, "primarykey" );
4866 char* tname = osrfHashGet( idlClass, "tablename" );
4868 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4869 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4870 tname, cname, fname, pkey, cname, pkey, locale, fname );
4872 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4875 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4879 jsonIteratorFree( select_itr );
4882 jsonIteratorFree( class_itr );
4884 char* col_list = buffer_release( select_buf );
4885 char* table = oilsGetRelation( meta );
4887 table = strdup( "(null)" );
4889 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4893 // Clear the query stack (as a fail-safe precaution against possible
4894 // leftover garbage); then push the first query frame onto the stack.
4895 clear_query_stack();
4897 if( add_query_core( NULL, core_class ) ) {
4899 osrfAppSessionStatus(
4901 OSRF_STATUS_INTERNALSERVERERROR,
4902 "osrfMethodException",
4904 "Unable to build query frame for core class"
4910 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4911 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
4912 OSRF_BUFFER_ADD( sql_buf, join_clause );
4913 free( join_clause );
4916 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4917 modulename, OSRF_BUFFER_C_STR( sql_buf ));
4919 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
4921 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4923 osrfAppSessionStatus(
4925 OSRF_STATUS_INTERNALSERVERERROR,
4926 "osrfMethodException",
4928 "Severe query error -- see error log for more details"
4930 buffer_free( sql_buf );
4931 if( defaultselhash )
4932 jsonObjectFree( defaultselhash );
4933 clear_query_stack();
4936 buffer_add( sql_buf, pred );
4941 char* string = NULL;
4942 if( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4944 growing_buffer* order_buf = buffer_init( 128 );
4947 jsonIterator* class_itr = jsonNewIterator( _tmp );
4948 while( (snode = jsonIteratorNext( class_itr )) ) {
4950 if( !jsonObjectGetKeyConst( selhash,class_itr->key ))
4953 if( snode->type == JSON_HASH ) {
4955 jsonIterator* order_itr = jsonNewIterator( snode );
4956 while( (onode = jsonIteratorNext( order_itr )) ) {
4958 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4959 class_itr->key, order_itr->key );
4963 char* direction = NULL;
4964 if( onode->type == JSON_HASH ) {
4965 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4966 string = searchFieldTransform( class_itr->key, field_def, onode );
4968 osrfAppSessionStatus(
4970 OSRF_STATUS_INTERNALSERVERERROR,
4971 "osrfMethodException",
4973 "Severe query error in ORDER BY clause -- "
4974 "see error log for more details"
4976 jsonIteratorFree( order_itr );
4977 jsonIteratorFree( class_itr );
4978 buffer_free( order_buf );
4979 buffer_free( sql_buf );
4980 if( defaultselhash )
4981 jsonObjectFree( defaultselhash );
4982 clear_query_stack();
4986 growing_buffer* field_buf = buffer_init( 16 );
4987 buffer_fadd( field_buf, "\"%s\".%s",
4988 class_itr->key, order_itr->key );
4989 string = buffer_release( field_buf );
4992 if( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4993 const char* dir = jsonObjectGetString( _tmp );
4994 if(!strncasecmp( dir, "d", 1 )) {
4995 direction = " DESC";
4999 string = strdup( order_itr->key );
5000 const char* dir = jsonObjectGetString( onode );
5001 if( !strncasecmp( dir, "d", 1 )) {
5002 direction = " DESC";
5011 buffer_add( order_buf, ", " );
5014 buffer_add( order_buf, string );
5018 buffer_add( order_buf, direction );
5022 jsonIteratorFree( order_itr );
5025 const char* str = jsonObjectGetString( snode );
5026 buffer_add( order_buf, str );
5032 jsonIteratorFree( class_itr );
5034 string = buffer_release( order_buf );
5037 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5038 OSRF_BUFFER_ADD( sql_buf, string );
5044 if( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ) {
5045 const char* str = jsonObjectGetString( _tmp );
5053 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
5055 const char* str = jsonObjectGetString( _tmp );
5064 if( defaultselhash )
5065 jsonObjectFree( defaultselhash );
5066 clear_query_stack();
5068 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5069 return buffer_release( sql_buf );
5072 int doJSONSearch ( osrfMethodContext* ctx ) {
5073 if(osrfMethodVerifyContext( ctx )) {
5074 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5078 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5083 dbhandle = writehandle;
5085 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5089 if( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
5090 flags |= SELECT_DISTINCT;
5092 if( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
5093 flags |= DISABLE_I18N;
5095 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5096 clear_query_stack(); // a possibly needless precaution
5097 char* sql = buildQuery( ctx, hash, flags );
5098 clear_query_stack();
5105 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5106 dbi_result result = dbi_conn_query( dbhandle, sql );
5109 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5111 if( dbi_result_first_row( result )) {
5112 /* JSONify the result */
5113 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5116 jsonObject* return_val = oilsMakeJSONFromResult( result );
5117 osrfAppRespond( ctx, return_val );
5118 jsonObjectFree( return_val );
5119 } while( dbi_result_next_row( result ));
5122 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5125 osrfAppRespondComplete( ctx, NULL );
5127 /* clean up the query */
5128 dbi_result_free( result );
5132 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]", modulename, sql );
5133 osrfAppSessionStatus(
5135 OSRF_STATUS_INTERNALSERVERERROR,
5136 "osrfMethodException",
5138 "Severe query error -- see error log for more details"
5146 // The last parameter, err, is used to report an error condition by updating an int owned by
5147 // the calling code.
5149 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5150 // It is the responsibility of the calling code to initialize *err before the
5151 // call, so that it will be able to make sense of the result.
5153 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5154 // redundant anyway.
5155 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5156 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5159 dbhandle = writehandle;
5161 char* core_class = osrfHashGet( class_meta, "classname" );
5162 char* pkey = osrfHashGet( class_meta, "primarykey" );
5164 const jsonObject* _tmp;
5166 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5168 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5173 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5175 dbi_result result = dbi_conn_query( dbhandle, sql );
5176 if( NULL == result ) {
5177 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
5178 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql );
5179 osrfAppSessionStatus(
5181 OSRF_STATUS_INTERNALSERVERERROR,
5182 "osrfMethodException",
5184 "Severe query error -- see error log for more details"
5191 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5194 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5195 jsonObject* row_obj = NULL;
5197 if( dbi_result_first_row( result )) {
5199 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5200 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5201 // eliminate the duplicates.
5202 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5203 osrfHash* dedup = osrfNewHash();
5205 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5206 char* pkey_val = oilsFMGetString( row_obj, pkey );
5207 if( osrfHashGet( dedup, pkey_val ) ) {
5208 jsonObjectFree( row_obj );
5211 osrfHashSet( dedup, pkey_val, pkey_val );
5212 jsonObjectPush( res_list, row_obj );
5214 } while( dbi_result_next_row( result ));
5215 osrfHashFree( dedup );
5218 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5222 /* clean up the query */
5223 dbi_result_free( result );
5226 // If we're asked to flesh, and there's anything to flesh, then flesh.
5227 if( res_list->size && query_hash ) {
5228 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5230 // Get the flesh depth
5231 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5232 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5233 flesh_depth = max_flesh_depth;
5235 // We need a non-zero flesh depth, and a list of fields to flesh
5236 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5237 if( temp_blob && flesh_depth > 0 ) {
5239 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5240 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5242 osrfStringArray* link_fields = NULL;
5243 osrfHash* links = osrfHashGet( class_meta, "links" );
5245 // Make an osrfStringArray of the names of fields to be fleshed
5246 if( flesh_fields ) {
5247 if( flesh_fields->size == 1 ) {
5248 const char* _t = jsonObjectGetString(
5249 jsonObjectGetIndex( flesh_fields, 0 ) );
5250 if( !strcmp( _t, "*" ))
5251 link_fields = osrfHashKeys( links );
5254 if( !link_fields ) {
5256 link_fields = osrfNewStringArray( 1 );
5257 jsonIterator* _i = jsonNewIterator( flesh_fields );
5258 while ((_f = jsonIteratorNext( _i ))) {
5259 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5261 jsonIteratorFree( _i );
5265 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5267 // Iterate over the JSON_ARRAY of rows
5269 unsigned long res_idx = 0;
5270 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5273 const char* link_field;
5275 // Iterate over the list of fleshable fields
5276 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5278 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5280 osrfHash* kid_link = osrfHashGet( links, link_field );
5282 continue; // Not a link field; skip it
5284 osrfHash* field = osrfHashGet( fields, link_field );
5286 continue; // Not a field at all; skip it (IDL is ill-formed)
5288 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5289 osrfHashGet( kid_link, "class" ));
5291 continue; // The class it links to doesn't exist; skip it
5293 const char* reltype = osrfHashGet( kid_link, "reltype" );
5295 continue; // No reltype; skip it (IDL is ill-formed)
5297 osrfHash* value_field = field;
5299 if( !strcmp( reltype, "has_many" )
5300 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5301 value_field = osrfHashGet(
5302 fields, osrfHashGet( class_meta, "primarykey" ) );
5305 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5307 if( link_map->size > 0 ) {
5308 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5311 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5316 osrfHashGet( kid_link, "class" ),
5323 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5324 osrfHashGet( kid_link, "field" ),
5325 osrfHashGet( kid_link, "class" ),
5326 osrfHashGet( kid_link, "key" ),
5327 osrfHashGet( kid_link, "reltype" )
5330 const char* search_key = jsonObjectGetString(
5331 jsonObjectGetIndex( cur,
5332 atoi( osrfHashGet( value_field, "array_position" ) )
5337 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5341 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5343 // construct WHERE clause
5344 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5347 osrfHashGet( kid_link, "key" ),
5348 jsonNewObject( search_key )
5351 // construct the rest of the query, mostly
5352 // by copying pieces of the previous level of query
5353 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5354 jsonObjectSetKey( rest_of_query, "flesh",
5355 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5359 jsonObjectSetKey( rest_of_query, "flesh_fields",
5360 jsonObjectClone( flesh_blob ));
5362 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5363 jsonObjectSetKey( rest_of_query, "order_by",
5364 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5368 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5369 jsonObjectSetKey( rest_of_query, "select",
5370 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5374 // do the query, recursively, to expand the fleshable field
5375 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5376 where_clause, rest_of_query, err );
5378 jsonObjectFree( where_clause );
5379 jsonObjectFree( rest_of_query );
5382 osrfStringArrayFree( link_fields );
5383 jsonObjectFree( res_list );
5384 jsonObjectFree( flesh_blob );
5388 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5389 osrfHashGet( kid_link, "class" ), kids->size );
5391 // Traverse the result set
5392 jsonObject* X = NULL;
5393 if( link_map->size > 0 && kids->size > 0 ) {
5395 kids = jsonNewObjectType( JSON_ARRAY );
5397 jsonObject* _k_node;
5398 unsigned long res_idx = 0;
5399 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5405 (unsigned long) atoi(
5411 osrfHashGet( kid_link, "class" )
5415 osrfStringArrayGetString( link_map, 0 )
5423 } // end while loop traversing X
5426 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5427 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5428 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5429 osrfHashGet( kid_link, "field" ));
5432 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5433 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5437 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5439 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5440 osrfHashGet( kid_link, "field" ) );
5443 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5444 jsonObjectClone( kids )
5449 jsonObjectFree( kids );
5453 jsonObjectFree( kids );
5455 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5456 osrfHashGet( kid_link, "field" ) );
5457 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5459 } // end while loop traversing list of fleshable fields
5460 } // end while loop traversing res_list
5461 jsonObjectFree( flesh_blob );
5462 osrfStringArrayFree( link_fields );
5471 int doUpdate( osrfMethodContext* ctx ) {
5472 if( osrfMethodVerifyContext( ctx )) {
5473 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5478 timeout_needs_resetting = 1;
5480 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5482 jsonObject* target = NULL;
5484 target = jsonObjectGetIndex( ctx->params, 1 );
5486 target = jsonObjectGetIndex( ctx->params, 0 );
5488 if(!verifyObjectClass( ctx, target )) {
5489 osrfAppRespondComplete( ctx, NULL );
5493 if( getXactId( ctx ) == NULL ) {
5494 osrfAppSessionStatus(
5496 OSRF_STATUS_BADREQUEST,
5497 "osrfMethodException",
5499 "No active transaction -- required for UPDATE"
5501 osrfAppRespondComplete( ctx, NULL );
5505 // The following test is harmless but redundant. If a class is
5506 // readonly, we don't register an update method for it.
5507 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5508 osrfAppSessionStatus(
5510 OSRF_STATUS_BADREQUEST,
5511 "osrfMethodException",
5513 "Cannot UPDATE readonly class"
5515 osrfAppRespondComplete( ctx, NULL );
5519 dbhandle = writehandle;
5520 const char* trans_id = getXactId( ctx );
5522 // Set the last_xact_id
5523 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5525 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5526 trans_id, target->classname, index );
5527 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5530 char* pkey = osrfHashGet( meta, "primarykey" );
5531 osrfHash* fields = osrfHashGet( meta, "fields" );
5533 char* id = oilsFMGetString( target, pkey );
5537 "%s updating %s object with %s = %s",
5539 osrfHashGet( meta, "fieldmapper" ),
5544 growing_buffer* sql = buffer_init( 128 );
5545 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5548 osrfHash* field_def = NULL;
5549 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5550 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5552 // Skip virtual fields, and the primary key
5553 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5556 const char* field_name = osrfHashIteratorKey( field_itr );
5557 if( ! strcmp( field_name, pkey ) )
5560 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5562 int value_is_numeric = 0; // boolean
5564 if( field_object && field_object->classname ) {
5565 value = oilsFMGetString(
5567 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5569 } else if( field_object && JSON_BOOL == field_object->type ) {
5570 if( jsonBoolIsTrue( field_object ) )
5571 value = strdup( "t" );
5573 value = strdup( "f" );
5575 value = jsonObjectToSimpleString( field_object );
5576 if( field_object && JSON_NUMBER == field_object->type )
5577 value_is_numeric = 1;
5580 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5581 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5583 if( !field_object || field_object->type == JSON_NULL ) {
5584 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5585 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5589 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5590 buffer_fadd( sql, " %s = NULL", field_name );
5593 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5597 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5599 const char* numtype = get_datatype( field_def );
5600 if( !strncmp( numtype, "INT", 3 ) ) {
5601 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5602 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5603 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5605 // Must really be intended as a string, so quote it
5606 if( dbi_conn_quote_string( dbhandle, &value )) {
5607 buffer_fadd( sql, " %s = %s", field_name, value );
5609 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5610 modulename, value );
5611 osrfAppSessionStatus(
5613 OSRF_STATUS_INTERNALSERVERERROR,
5614 "osrfMethodException",
5616 "Error quoting string -- please see the error log for more details"
5620 osrfHashIteratorFree( field_itr );
5622 osrfAppRespondComplete( ctx, NULL );
5627 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5630 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5634 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5635 buffer_fadd( sql, " %s = %s", field_name, value );
5637 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5638 osrfAppSessionStatus(
5640 OSRF_STATUS_INTERNALSERVERERROR,
5641 "osrfMethodException",
5643 "Error quoting string -- please see the error log for more details"
5647 osrfHashIteratorFree( field_itr );
5649 osrfAppRespondComplete( ctx, NULL );
5658 osrfHashIteratorFree( field_itr );
5660 jsonObject* obj = jsonNewObject( id );
5662 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5663 dbi_conn_quote_string( dbhandle, &id );
5665 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5667 char* query = buffer_release( sql );
5668 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5670 dbi_result result = dbi_conn_query( dbhandle, query );
5674 jsonObjectFree( obj );
5675 obj = jsonNewObject( NULL );
5678 "%s ERROR updating %s object with %s = %s",
5680 osrfHashGet( meta, "fieldmapper" ),
5687 osrfAppRespondComplete( ctx, obj );
5688 jsonObjectFree( obj );
5692 int doDelete( osrfMethodContext* ctx ) {
5693 if( osrfMethodVerifyContext( ctx )) {
5694 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5699 timeout_needs_resetting = 1;
5701 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5703 if( getXactId( ctx ) == NULL ) {
5704 osrfAppSessionStatus(
5706 OSRF_STATUS_BADREQUEST,
5707 "osrfMethodException",
5709 "No active transaction -- required for DELETE"
5711 osrfAppRespondComplete( ctx, NULL );
5715 // The following test is harmless but redundant. If a class is
5716 // readonly, we don't register a delete method for it.
5717 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5718 osrfAppSessionStatus(
5720 OSRF_STATUS_BADREQUEST,
5721 "osrfMethodException",
5723 "Cannot DELETE readonly class"
5725 osrfAppRespondComplete( ctx, NULL );
5729 dbhandle = writehandle;
5731 char* pkey = osrfHashGet( meta, "primarykey" );
5738 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5739 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5740 osrfAppRespondComplete( ctx, NULL );
5744 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5746 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5747 osrfAppRespondComplete( ctx, NULL );
5750 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5755 "%s deleting %s object with %s = %s",
5757 osrfHashGet( meta, "fieldmapper" ),
5762 jsonObject* obj = jsonNewObject( id );
5764 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5765 dbi_conn_quote_string( writehandle, &id );
5767 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
5768 osrfHashGet( meta, "tablename" ), pkey, id );
5771 jsonObjectFree( obj );
5772 obj = jsonNewObject( NULL );
5775 "%s ERROR deleting %s object with %s = %s",
5777 osrfHashGet( meta, "fieldmapper" ),
5785 osrfAppRespondComplete( ctx, obj );
5786 jsonObjectFree( obj );
5791 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5792 @param result An iterator for a result set; we only look at the current row.
5793 @param @meta Pointer to the class metadata for the core class.
5794 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5796 If a column is not defined in the IDL, or if it has no array_position defined for it in
5797 the IDL, or if it is defined as virtual, ignore it.
5799 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5800 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5801 array_position in the IDL.
5803 A field defined in the IDL but not represented in the returned row will leave a hole
5804 in the JSON_ARRAY. In effect it will be treated as a null value.
5806 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5807 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5808 classname corresponding to the @a meta argument.
5810 The calling code is responsible for freeing the the resulting jsonObject by calling
5813 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5814 if( !( result && meta )) return NULL;
5816 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5817 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
5818 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
5820 osrfHash* fields = osrfHashGet( meta, "fields" );
5822 int columnIndex = 1;
5823 const char* columnName;
5825 /* cycle through the columns in the row returned from the database */
5826 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
5828 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5830 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5832 /* determine the field type and storage attributes */
5833 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
5834 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5836 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5837 // or if it has no sequence number there, or if it's virtual, skip it.
5838 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
5841 if( str_is_true( osrfHashGet( _f, "virtual" )))
5842 continue; // skip this column: IDL says it's virtual
5844 const char* pos = (char*) osrfHashGet( _f, "array_position" );
5845 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
5846 continue; // since we assign sequence numbers dynamically as we load the IDL.
5848 fmIndex = atoi( pos );
5849 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
5851 continue; // This field is not defined in the IDL
5854 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
5855 // sequence number from the IDL (which is likely to be different from the sequence
5856 // of columns in the SELECT clause).
5857 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5858 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
5863 case DBI_TYPE_INTEGER :
5865 if( attr & DBI_INTEGER_SIZE8 )
5866 jsonObjectSetIndex( object, fmIndex,
5867 jsonNewNumberObject(
5868 dbi_result_get_longlong_idx( result, columnIndex )));
5870 jsonObjectSetIndex( object, fmIndex,
5871 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
5875 case DBI_TYPE_DECIMAL :
5876 jsonObjectSetIndex( object, fmIndex,
5877 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
5880 case DBI_TYPE_STRING :
5885 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
5890 case DBI_TYPE_DATETIME : {
5892 char dt_string[ 256 ] = "";
5895 // Fetch the date column as a time_t
5896 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
5898 // Translate the time_t to a human-readable string
5899 if( !( attr & DBI_DATETIME_DATE )) {
5900 gmtime_r( &_tmp_dt, &gmdt );
5901 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
5902 } else if( !( attr & DBI_DATETIME_TIME )) {
5903 localtime_r( &_tmp_dt, &gmdt );
5904 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
5906 localtime_r( &_tmp_dt, &gmdt );
5907 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
5910 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
5914 case DBI_TYPE_BINARY :
5915 osrfLogError( OSRF_LOG_MARK,
5916 "Can't do binary at column %s : index %d", columnName, columnIndex );
5925 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5926 if( !result ) return NULL;
5928 jsonObject* object = jsonNewObject( NULL );
5931 char dt_string[ 256 ];
5935 int columnIndex = 1;
5937 unsigned short type;
5938 const char* columnName;
5940 /* cycle through the column list */
5941 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
5943 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5945 fmIndex = -1; // reset the position
5947 /* determine the field type and storage attributes */
5948 type = dbi_result_get_field_type_idx( result, columnIndex );
5949 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5951 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5952 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
5957 case DBI_TYPE_INTEGER :
5959 if( attr & DBI_INTEGER_SIZE8 )
5960 jsonObjectSetKey( object, columnName,
5961 jsonNewNumberObject( dbi_result_get_longlong_idx(
5962 result, columnIndex )) );
5964 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
5965 dbi_result_get_int_idx( result, columnIndex )) );
5968 case DBI_TYPE_DECIMAL :
5969 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
5970 dbi_result_get_double_idx( result, columnIndex )) );
5973 case DBI_TYPE_STRING :
5974 jsonObjectSetKey( object, columnName,
5975 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
5978 case DBI_TYPE_DATETIME :
5980 memset( dt_string, '\0', sizeof( dt_string ));
5981 memset( &gmdt, '\0', sizeof( gmdt ));
5983 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
5985 if( !( attr & DBI_DATETIME_DATE )) {
5986 gmtime_r( &_tmp_dt, &gmdt );
5987 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
5988 } else if( !( attr & DBI_DATETIME_TIME )) {
5989 localtime_r( &_tmp_dt, &gmdt );
5990 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
5992 localtime_r( &_tmp_dt, &gmdt );
5993 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
5996 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
5999 case DBI_TYPE_BINARY :
6000 osrfLogError( OSRF_LOG_MARK,
6001 "Can't do binary at column %s : index %d", columnName, columnIndex );
6005 } // end while loop traversing result
6010 // Interpret a string as true or false
6011 int str_is_true( const char* str ) {
6012 if( NULL == str || strcasecmp( str, "true" ) )
6018 // Interpret a jsonObject as true or false
6019 static int obj_is_true( const jsonObject* obj ) {
6022 else switch( obj->type )
6030 if( strcasecmp( obj->value.s, "true" ) )
6034 case JSON_NUMBER : // Support 1/0 for perl's sake
6035 if( jsonObjectGetNumber( obj ) == 1.0 )
6044 // Translate a numeric code into a text string identifying a type of
6045 // jsonObject. To be used for building error messages.
6046 static const char* json_type( int code ) {
6052 return "JSON_ARRAY";
6054 return "JSON_STRING";
6056 return "JSON_NUMBER";
6062 return "(unrecognized)";
6066 // Extract the "primitive" attribute from an IDL field definition.
6067 // If we haven't initialized the app, then we must be running in
6068 // some kind of testbed. In that case, default to "string".
6069 static const char* get_primitive( osrfHash* field ) {
6070 const char* s = osrfHashGet( field, "primitive" );
6072 if( child_initialized )
6075 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6077 osrfHashGet( field, "name" )
6085 // Extract the "datatype" attribute from an IDL field definition.
6086 // If we haven't initialized the app, then we must be running in
6087 // some kind of testbed. In that case, default to to NUMERIC,
6088 // since we look at the datatype only for numbers.
6089 static const char* get_datatype( osrfHash* field ) {
6090 const char* s = osrfHashGet( field, "datatype" );
6092 if( child_initialized )
6095 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6097 osrfHashGet( field, "name" )
6106 @brief Determine whether a string is potentially a valid SQL identifier.
6107 @param s The identifier to be tested.
6108 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6110 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6111 need to follow all the rules exactly, such as requiring that the first character not
6114 We allow leading and trailing white space. In between, we do not allow punctuation
6115 (except for underscores and dollar signs), control characters, or embedded white space.
6117 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6118 for the foreseeable future such quoted identifiers are not likely to be an issue.
6120 static int is_identifier( const char* s) {
6124 // Skip leading white space
6125 while( isspace( (unsigned char) *s ) )
6129 return 0; // Nothing but white space? Not okay.
6131 // Check each character until we reach white space or
6132 // end-of-string. Letters, digits, underscores, and
6133 // dollar signs are okay. With the exception of periods
6134 // (as in schema.identifier), control characters and other
6135 // punctuation characters are not okay. Anything else
6136 // is okay -- it could for example be part of a multibyte
6137 // UTF8 character such as a letter with diacritical marks,
6138 // and those are allowed.
6140 if( isalnum( (unsigned char) *s )
6144 ; // Fine; keep going
6145 else if( ispunct( (unsigned char) *s )
6146 || iscntrl( (unsigned char) *s ) )
6149 } while( *s && ! isspace( (unsigned char) *s ) );
6151 // If we found any white space in the above loop,
6152 // the rest had better be all white space.
6154 while( isspace( (unsigned char) *s ) )
6158 return 0; // White space was embedded within non-white space
6164 @brief Determine whether to accept a character string as a comparison operator.
6165 @param op The candidate comparison operator.
6166 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6168 We don't validate the operator for real. We just make sure that it doesn't contain
6169 any semicolons or white space (with special exceptions for a few specific operators).
6170 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6171 space but it's still not a valid operator, then the database will complain.
6173 Another approach would be to compare the string against a short list of approved operators.
6174 We don't do that because we want to allow custom operators like ">100*", which at this
6175 writing would be difficult or impossible to express otherwise in a JSON query.
6177 static int is_good_operator( const char* op ) {
6178 if( !op ) return 0; // Sanity check
6182 if( isspace( (unsigned char) *s ) ) {
6183 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6184 // and IS NOT DISTINCT FROM.
6185 if( !strcasecmp( op, "similar to" ) )
6187 else if( !strcasecmp( op, "is distinct from" ) )
6189 else if( !strcasecmp( op, "is not distinct from" ) )
6194 else if( ';' == *s )
6202 @name Query Frame Management
6204 The following machinery supports a stack of query frames for use by SELECT().
6206 A query frame caches information about one level of a SELECT query. When we enter
6207 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6209 The query frame stores information about the core class, and about any joined classes
6212 The main purpose is to map table aliases to classes and tables, so that a query can
6213 join to the same table more than once. A secondary goal is to reduce the number of
6214 lookups in the IDL by caching the results.
6218 #define STATIC_CLASS_INFO_COUNT 3
6220 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6223 @brief Allocate a ClassInfo as raw memory.
6224 @return Pointer to the newly allocated ClassInfo.
6226 Except for the in_use flag, which is used only by the allocation and deallocation
6227 logic, we don't initialize the ClassInfo here.
6229 static ClassInfo* allocate_class_info( void ) {
6230 // In order to reduce the number of mallocs and frees, we return a static
6231 // instance of ClassInfo, if we can find one that we're not already using.
6232 // We rely on the fact that the compiler will implicitly initialize the
6233 // static instances so that in_use == 0.
6236 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6237 if( ! static_class_info[ i ].in_use ) {
6238 static_class_info[ i ].in_use = 1;
6239 return static_class_info + i;
6243 // The static ones are all in use. Malloc one.
6245 return safe_malloc( sizeof( ClassInfo ) );
6249 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6250 @param info Pointer to the ClassInfo to be cleared.
6252 static void clear_class_info( ClassInfo* info ) {
6257 // Free any malloc'd strings
6259 if( info->alias != info->alias_store )
6260 free( info->alias );
6262 if( info->class_name != info->class_name_store )
6263 free( info->class_name );
6265 free( info->source_def );
6267 info->alias = info->class_name = info->source_def = NULL;
6272 @brief Free a ClassInfo and everything it owns.
6273 @param info Pointer to the ClassInfo to be freed.
6275 static void free_class_info( ClassInfo* info ) {
6280 clear_class_info( info );
6282 // If it's one of the static instances, just mark it as not in use
6285 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6286 if( info == static_class_info + i ) {
6287 static_class_info[ i ].in_use = 0;
6292 // Otherwise it must have been malloc'd, so free it
6298 @brief Populate an already-allocated ClassInfo.
6299 @param info Pointer to the ClassInfo to be populated.
6300 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6302 @param class Name of the class.
6303 @return Zero if successful, or 1 if not.
6305 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6306 the relevant portions of the IDL for the specified class.
6308 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6311 osrfLogError( OSRF_LOG_MARK,
6312 "%s ERROR: No ClassInfo available to populate", modulename );
6313 info->alias = info->class_name = info->source_def = NULL;
6314 info->class_def = info->fields = info->links = NULL;
6319 osrfLogError( OSRF_LOG_MARK,
6320 "%s ERROR: No class name provided for lookup", modulename );
6321 info->alias = info->class_name = info->source_def = NULL;
6322 info->class_def = info->fields = info->links = NULL;
6326 // Alias defaults to class name if not supplied
6327 if( ! alias || ! alias[ 0 ] )
6330 // Look up class info in the IDL
6331 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6333 osrfLogError( OSRF_LOG_MARK,
6334 "%s ERROR: Class %s not defined in IDL", modulename, class );
6335 info->alias = info->class_name = info->source_def = NULL;
6336 info->class_def = info->fields = info->links = NULL;
6338 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6339 osrfLogError( OSRF_LOG_MARK,
6340 "%s ERROR: Class %s is defined as virtual", modulename, class );
6341 info->alias = info->class_name = info->source_def = NULL;
6342 info->class_def = info->fields = info->links = NULL;
6346 osrfHash* links = osrfHashGet( class_def, "links" );
6348 osrfLogError( OSRF_LOG_MARK,
6349 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6350 info->alias = info->class_name = info->source_def = NULL;
6351 info->class_def = info->fields = info->links = NULL;
6355 osrfHash* fields = osrfHashGet( class_def, "fields" );
6357 osrfLogError( OSRF_LOG_MARK,
6358 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6359 info->alias = info->class_name = info->source_def = NULL;
6360 info->class_def = info->fields = info->links = NULL;
6364 char* source_def = oilsGetRelation( class_def );
6368 // We got everything we need, so populate the ClassInfo
6369 if( strlen( alias ) > ALIAS_STORE_SIZE )
6370 info->alias = strdup( alias );
6372 strcpy( info->alias_store, alias );
6373 info->alias = info->alias_store;
6376 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6377 info->class_name = strdup( class );
6379 strcpy( info->class_name_store, class );
6380 info->class_name = info->class_name_store;
6383 info->source_def = source_def;
6385 info->class_def = class_def;
6386 info->links = links;
6387 info->fields = fields;
6392 #define STATIC_FRAME_COUNT 3
6394 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6397 @brief Allocate a QueryFrame as raw memory.
6398 @return Pointer to the newly allocated QueryFrame.
6400 Except for the in_use flag, which is used only by the allocation and deallocation
6401 logic, we don't initialize the QueryFrame here.
6403 static QueryFrame* allocate_frame( void ) {
6404 // In order to reduce the number of mallocs and frees, we return a static
6405 // instance of QueryFrame, if we can find one that we're not already using.
6406 // We rely on the fact that the compiler will implicitly initialize the
6407 // static instances so that in_use == 0.
6410 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6411 if( ! static_frame[ i ].in_use ) {
6412 static_frame[ i ].in_use = 1;
6413 return static_frame + i;
6417 // The static ones are all in use. Malloc one.
6419 return safe_malloc( sizeof( QueryFrame ) );
6423 @brief Free a QueryFrame, and all the memory it owns.
6424 @param frame Pointer to the QueryFrame to be freed.
6426 static void free_query_frame( QueryFrame* frame ) {
6431 clear_class_info( &frame->core );
6433 // Free the join list
6435 ClassInfo* info = frame->join_list;
6438 free_class_info( info );
6442 frame->join_list = NULL;
6445 // If the frame is a static instance, just mark it as unused
6447 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6448 if( frame == static_frame + i ) {
6449 static_frame[ i ].in_use = 0;
6454 // Otherwise it must have been malloc'd, so free it
6460 @brief Search a given QueryFrame for a specified alias.
6461 @param frame Pointer to the QueryFrame to be searched.
6462 @param target The alias for which to search.
6463 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6465 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6466 if( ! frame || ! target ) {
6470 ClassInfo* found_class = NULL;
6472 if( !strcmp( target, frame->core.alias ) )
6473 return &(frame->core);
6475 ClassInfo* curr_class = frame->join_list;
6476 while( curr_class ) {
6477 if( strcmp( target, curr_class->alias ) )
6478 curr_class = curr_class->next;
6480 found_class = curr_class;
6490 @brief Push a new (blank) QueryFrame onto the stack.
6492 static void push_query_frame( void ) {
6493 QueryFrame* frame = allocate_frame();
6494 frame->join_list = NULL;
6495 frame->next = curr_query;
6497 // Initialize the ClassInfo for the core class
6498 ClassInfo* core = &frame->core;
6499 core->alias = core->class_name = core->source_def = NULL;
6500 core->class_def = core->fields = core->links = NULL;
6506 @brief Pop a QueryFrame off the stack and destroy it.
6508 static void pop_query_frame( void ) {
6513 QueryFrame* popped = curr_query;
6514 curr_query = popped->next;
6516 free_query_frame( popped );
6520 @brief Populate the ClassInfo for the core class.
6521 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6522 class name as an alias.
6523 @param class_name Name of the core class.
6524 @return Zero if successful, or 1 if not.
6526 Populate the ClassInfo of the core class with copies of the alias and class name, and
6527 with pointers to the relevant portions of the IDL for the core class.
6529 static int add_query_core( const char* alias, const char* class_name ) {
6532 if( ! curr_query ) {
6533 osrfLogError( OSRF_LOG_MARK,
6534 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6536 } else if( curr_query->core.alias ) {
6537 osrfLogError( OSRF_LOG_MARK,
6538 "%s ERROR: Core class %s already populated as %s",
6539 modulename, curr_query->core.class_name, curr_query->core.alias );
6543 build_class_info( &curr_query->core, alias, class_name );
6544 if( curr_query->core.alias )
6547 osrfLogError( OSRF_LOG_MARK,
6548 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6554 @brief Search the current QueryFrame for a specified alias.
6555 @param target The alias for which to search.
6556 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6558 static inline ClassInfo* search_alias( const char* target ) {
6559 return search_alias_in_frame( curr_query, target );
6563 @brief Search all levels of query for a specified alias, starting with the current query.
6564 @param target The alias for which to search.
6565 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6567 static ClassInfo* search_all_alias( const char* target ) {
6568 ClassInfo* found_class = NULL;
6569 QueryFrame* curr_frame = curr_query;
6571 while( curr_frame ) {
6572 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6575 curr_frame = curr_frame->next;
6582 @brief Add a class to the list of classes joined to the current query.
6583 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6584 the class name as an alias.
6585 @param classname The name of the class to be added.
6586 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6588 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6590 if( ! classname || ! *classname ) { // sanity check
6591 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6598 const ClassInfo* conflict = search_alias( alias );
6600 osrfLogError( OSRF_LOG_MARK,
6601 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6602 modulename, alias, conflict->class_name );
6606 ClassInfo* info = allocate_class_info();
6608 if( build_class_info( info, alias, classname ) ) {
6609 free_class_info( info );
6613 // Add the new ClassInfo to the join list of the current QueryFrame
6614 info->next = curr_query->join_list;
6615 curr_query->join_list = info;
6621 @brief Destroy all nodes on the query stack.
6623 static void clear_query_stack( void ) {