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 void pop_query_frame( void );
106 static void push_query_frame( void );
107 static int add_query_core( const char* alias, const char* class_name );
108 static inline ClassInfo* search_alias( const char* target );
109 static ClassInfo* search_all_alias( const char* target );
110 static ClassInfo* add_joined_class( const char* alias, const char* classname );
111 static void clear_query_stack( void );
113 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
114 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
115 static const char* org_tree_root( osrfMethodContext* ctx );
116 static jsonObject* single_hash( const char* key, const char* value );
118 static int child_initialized = 0; /* boolean */
120 static dbi_conn writehandle; /* our MASTER db connection */
121 static dbi_conn dbhandle; /* our CURRENT db connection */
122 //static osrfHash * readHandles;
124 // The following points to the top of a stack of QueryFrames. It's a little
125 // confusing because the top level of the query is at the bottom of the stack.
126 static QueryFrame* curr_query = NULL;
128 static dbi_conn writehandle; /* our MASTER db connection */
129 static dbi_conn dbhandle; /* our CURRENT db connection */
130 //static osrfHash * readHandles;
132 static int max_flesh_depth = 100;
134 static int enforce_pcrud = 0; // Boolean
135 static char* modulename = NULL;
138 @brief Connect to the database.
139 @return A database connection if successful, or NULL if not.
141 dbi_conn oilsConnectDB( const char* mod_name ) {
143 osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
144 if( dbi_initialize( NULL ) == -1 ) {
145 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
148 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
150 char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
151 char* user = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
152 char* host = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
153 char* port = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
154 char* db = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
155 char* pw = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
157 osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
158 dbi_conn handle = dbi_conn_new( driver );
161 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
164 osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
166 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
167 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
169 if( host ) dbi_conn_set_option( handle, "host", host );
170 if( port ) dbi_conn_set_option_numeric( handle, "port", atoi( port ));
171 if( user ) dbi_conn_set_option( handle, "username", user );
172 if( pw ) dbi_conn_set_option( handle, "password", pw );
173 if( db ) dbi_conn_set_option( handle, "dbname", db );
181 if( dbi_conn_connect( handle ) < 0 ) {
183 if( dbi_conn_connect( handle ) < 0 ) {
185 dbi_conn_error( handle, &msg );
186 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
187 msg ? msg : "(No description available)" );
192 osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
198 @brief Select some options.
199 @param module_name: Name of the server.
200 @param do_pcrud: Boolean. True if we are to enforce PCRUD permissions.
202 This source file is used (at this writing) to implement three different servers:
203 - open-ils.reporter-store
207 These servers behave mostly the same, but they implement different combinations of
208 methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
210 Here we use the server name in messages to identify which kind of server issued them.
211 We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
213 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
215 module_name = "open-ils.cstore"; // bulletproofing with a default
220 modulename = strdup( module_name );
221 enforce_pcrud = do_pcrud;
222 max_flesh_depth = flesh_depth;
226 @brief Install a database connection.
227 @param conn Pointer to a database connection.
229 In some contexts, @a conn may merely provide a driver so that we can process strings
230 properly, without providing an open database connection.
232 void oilsSetDBConnection( dbi_conn conn ) {
233 dbhandle = writehandle = conn;
237 @brief Get a table name, view name, or subquery for use in a FROM clause.
238 @param class Pointer to the IDL class entry.
239 @return A table name, a view name, or a subquery in parentheses.
241 In some cases the IDL defines a class, not with a table name or a view name, but with
242 a SELECT statement, which may be used as a subquery.
244 char* oilsGetRelation( osrfHash* classdef ) {
246 char* source_def = NULL;
247 const char* tabledef = osrfHashGet( classdef, "tablename" );
250 source_def = strdup( tabledef ); // Return the name of a table or view
252 tabledef = osrfHashGet( classdef, "source_definition" );
254 // Return a subquery, enclosed in parentheses
255 source_def = safe_malloc( strlen( tabledef ) + 3 );
256 source_def[ 0 ] = '(';
257 strcpy( source_def + 1, tabledef );
258 strcat( source_def, ")" );
260 // Not found: return an error
261 const char* classname = osrfHashGet( classdef, "classname" );
266 "%s ERROR No tablename or source_definition for class \"%s\"",
277 @brief Add datatypes from the database to the fields in the IDL.
278 @param handle Handle for a database connection
279 @return Zero if successful, or 1 upon error.
281 For each relevant class in the IDL: ask the database for the datatype of every field.
282 In particular, determine which fields are text fields and which fields are numeric
283 fields, so that we know whether to enclose their values in quotes.
285 int oilsExtendIDL( dbi_conn handle ) {
286 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
287 osrfHash* class = NULL;
288 growing_buffer* query_buf = buffer_init( 64 );
289 int results_found = 0; // boolean
291 // For each class in the IDL...
292 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
293 const char* classname = osrfHashIteratorKey( class_itr );
294 osrfHash* fields = osrfHashGet( class, "fields" );
296 // If the class is virtual, ignore it
297 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
298 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
302 char* tabledef = oilsGetRelation( class );
304 continue; // No such relation -- a query of it would be doomed to failure
306 buffer_reset( query_buf );
307 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
311 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
312 modulename, OSRF_BUFFER_C_STR( query_buf ) );
314 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
319 const char* columnName;
320 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
322 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
325 /* fetch the fieldmapper index */
326 osrfHash* _f = osrfHashGet(fields, columnName);
329 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
331 /* determine the field type and storage attributes */
333 switch( dbi_result_get_field_type_idx( result, columnIndex )) {
335 case DBI_TYPE_INTEGER : {
337 if( !osrfHashGet(_f, "primitive") )
338 osrfHashSet(_f, "number", "primitive");
340 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
341 if( attr & DBI_INTEGER_SIZE8 )
342 osrfHashSet( _f, "INT8", "datatype" );
344 osrfHashSet( _f, "INT", "datatype" );
347 case DBI_TYPE_DECIMAL :
348 if( !osrfHashGet( _f, "primitive" ))
349 osrfHashSet( _f, "number", "primitive" );
351 osrfHashSet( _f, "NUMERIC", "datatype" );
354 case DBI_TYPE_STRING :
355 if( !osrfHashGet( _f, "primitive" ))
356 osrfHashSet( _f, "string", "primitive" );
358 osrfHashSet( _f,"TEXT", "datatype" );
361 case DBI_TYPE_DATETIME :
362 if( !osrfHashGet( _f, "primitive" ))
363 osrfHashSet( _f, "string", "primitive" );
365 osrfHashSet( _f, "TIMESTAMP", "datatype" );
368 case DBI_TYPE_BINARY :
369 if( !osrfHashGet( _f, "primitive" ))
370 osrfHashSet( _f, "string", "primitive" );
372 osrfHashSet( _f, "BYTEA", "datatype" );
377 "Setting [%s] to primitive [%s] and datatype [%s]...",
379 osrfHashGet( _f, "primitive" ),
380 osrfHashGet( _f, "datatype" )
384 } // end while loop for traversing columns of result
385 dbi_result_free( result );
388 int errnum = dbi_conn_error( handle, &msg );
389 osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
390 errnum, msg ? msg : "(No description available)" );
392 } // end for each class in IDL
394 buffer_free( query_buf );
395 osrfHashIteratorFree( class_itr );
396 child_initialized = 1;
398 if( !results_found ) {
399 osrfLogError( OSRF_LOG_MARK,
400 "No results found for any class -- bad database connection?" );
408 @brief Free an osrfHash that stores a transaction ID.
409 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
411 This function is a callback, to be called by the application session when it ends.
412 The application session stores the osrfHash via an opaque pointer.
414 If the osrfHash contains an entry for the key "xact_id", it means that an
415 uncommitted transaction is pending. Roll it back.
417 void userDataFree( void* blob ) {
418 osrfHash* hash = (osrfHash*) blob;
419 if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
420 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
422 int errnum = dbi_conn_error( writehandle, &msg );
423 osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
424 errnum, msg ? msg : "(No description available)" );
428 osrfHashFree( hash );
432 @name Managing session data
433 @brief Maintain data stored via the userData pointer of the application session.
435 Currently, session-level data is stored in an osrfHash. Other arrangements are
436 possible, and some would be more efficient. The application session calls a
437 callback function to free userData before terminating.
439 Currently, the only data we store at the session level is the transaction id. By this
440 means we can ensure that any pending transactions are rolled back before the application
446 @brief Free an item in the application session's userData.
447 @param key The name of a key for an osrfHash.
448 @param item An opaque pointer to the item associated with the key.
450 We store an osrfHash as userData with the application session, and arrange (by
451 installing userDataFree() as a different callback) for the session to free that
452 osrfHash before terminating.
454 This function is a callback for freeing items in the osrfHash. Currently we store
456 - Transaction id of a pending transaction; a character string. Key: "xact_id".
457 - Authkey; a character string. Key: "authkey".
458 - User object from the authentication server; a jsonObject. Key: "user_login".
460 If we ever store anything else in userData, we will need to revisit this function so
461 that it will free whatever else needs freeing.
463 static void sessionDataFree( char* key, void* item ) {
464 if( !strcmp( key, "xact_id" )
465 || !strcmp( key, "authkey" ) ) {
467 } else if( !strcmp( key, "user_login" ) )
468 jsonObjectFree( (jsonObject*) item );
472 @brief Save a transaction id.
473 @param ctx Pointer to the method context.
475 Save the session_id of the current application session as a transaction id.
477 static void setXactId( osrfMethodContext* ctx ) {
478 if( ctx && ctx->session ) {
479 osrfAppSession* session = ctx->session;
481 osrfHash* cache = session->userData;
483 // If the session doesn't already have a hash, create one. Make sure
484 // that the application session frees the hash when it terminates.
485 if( NULL == cache ) {
486 session->userData = cache = osrfNewHash();
487 osrfHashSetCallback( cache, &sessionDataFree );
488 ctx->session->userDataFree = &userDataFree;
491 // Save the transaction id in the hash, with the key "xact_id"
492 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
497 @brief Get the transaction ID for the current transaction, if any.
498 @param ctx Pointer to the method context.
499 @return Pointer to the transaction ID.
501 The return value points to an internal buffer, and will become invalid upon issuing
502 a commit or rollback.
504 static inline const char* getXactId( osrfMethodContext* ctx ) {
505 if( ctx && ctx->session && ctx->session->userData )
506 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
512 @brief Clear the current transaction id.
513 @param ctx Pointer to the method context.
515 static inline void clearXactId( osrfMethodContext* ctx ) {
516 if( ctx && ctx->session && ctx->session->userData )
517 osrfHashRemove( ctx->session->userData, "xact_id" );
522 @brief Save the user's login in the userData for the current application session.
523 @param ctx Pointer to the method context.
524 @param user_login Pointer to the user login object to be cached (we cache the original,
527 If @a user_login is NULL, remove the user login if one is already cached.
529 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
530 if( ctx && ctx->session ) {
531 osrfAppSession* session = ctx->session;
533 osrfHash* cache = session->userData;
535 // If the session doesn't already have a hash, create one. Make sure
536 // that the application session frees the hash when it terminates.
537 if( NULL == cache ) {
538 session->userData = cache = osrfNewHash();
539 osrfHashSetCallback( cache, &sessionDataFree );
540 ctx->session->userDataFree = &userDataFree;
544 osrfHashSet( cache, user_login, "user_login" );
546 osrfHashRemove( cache, "user_login" );
551 @brief Get the user login object for the current application session, if any.
552 @param ctx Pointer to the method context.
553 @return Pointer to the user login object if found; otherwise NULL.
555 The user login object was returned from the authentication server, and then cached so
556 we don't have to call the authentication server again for the same user.
558 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
559 if( ctx && ctx->session && ctx->session->userData )
560 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
566 @brief Save a copy of an authkey in the userData of the current application session.
567 @param ctx Pointer to the method context.
568 @param authkey The authkey to be saved.
570 If @a authkey is NULL, remove the authkey if one is already cached.
572 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
573 if( ctx && ctx->session && authkey ) {
574 osrfAppSession* session = ctx->session;
575 osrfHash* cache = session->userData;
577 // If the session doesn't already have a hash, create one. Make sure
578 // that the application session frees the hash when it terminates.
579 if( NULL == cache ) {
580 session->userData = cache = osrfNewHash();
581 osrfHashSetCallback( cache, &sessionDataFree );
582 ctx->session->userDataFree = &userDataFree;
585 // Save the transaction id in the hash, with the key "xact_id"
586 if( authkey && *authkey )
587 osrfHashSet( cache, strdup( authkey ), "authkey" );
589 osrfHashRemove( cache, "authkey" );
594 @brief Reset the login timeout.
595 @param authkey The authentication key for the current login session.
596 @param now The current time.
597 @return Zero if successful, or 1 if not.
599 Tell the authentication server to reset the timeout so that the login session won't
600 expire for a while longer.
602 We could dispense with the @a now parameter by calling time(). But we just called
603 time() in order to decide whether to reset the timeout, so we might as well reuse
604 the result instead of calling time() again.
606 static int reset_timeout( const char* authkey, time_t now ) {
607 jsonObject* auth_object = jsonNewObject( authkey );
609 // Ask the authentication server to reset the timeout. It returns an event
610 // indicating success or failure.
611 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
612 "open-ils.auth.session.reset_timeout", auth_object );
613 jsonObjectFree( auth_object );
615 if( !result || result->type != JSON_HASH ) {
616 osrfLogError( OSRF_LOG_MARK,
617 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
618 jsonObjectFree( result );
619 return 1; // Not the right sort of object returned
622 const jsonObject* ilsevent = jsonObjectGetKey( result, "ilsevent" );
623 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
624 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
625 jsonObjectFree( result );
626 return 1; // Return code from method not available
629 if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
630 const char* desc = jsonObjectGetString( jsonObjectGetKey( result, "desc" ) );
632 desc = "(No reason available)"; // failsafe; shouldn't happen
633 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
634 jsonObjectFree( result );
638 // Revise our local proxy for the timeout deadline
639 // by a smallish fraction of the timeout interval
640 const char* timeout = jsonObjectGetString( jsonObjectGetKey( result, "payload" ) );
642 timeout = "1"; // failsafe; shouldn't happen
643 time_next_reset = now + atoi( timeout ) / 15;
645 jsonObjectFree( result );
646 return 0; // Successfully reset timeout
650 @brief Get the authkey string for the current application session, if any.
651 @param ctx Pointer to the method context.
652 @return Pointer to the cached authkey if found; otherwise NULL.
654 If present, the authkey string was cached from a previous method call.
656 static const char* getAuthkey( osrfMethodContext* ctx ) {
657 if( ctx && ctx->session && ctx->session->userData ) {
658 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
660 // Possibly reset the authentication timeout to keep the login alive. We do so
661 // no more than once per method call, and not at all if it has been only a short
662 // time since the last reset.
664 // Here we reset explicitly, if at all. We also implicitly reset the timeout
665 // whenever we call the "open-ils.auth.session.retrieve" method.
666 if( timeout_needs_resetting ) {
667 time_t now = time( NULL );
668 if( now >= time_next_reset && reset_timeout( authkey, now ) )
669 authkey = NULL; // timeout has apparently expired already
672 timeout_needs_resetting = 0;
680 @brief Implement the transaction.begin method.
681 @param ctx Pointer to the method context.
682 @return Zero if successful, or -1 upon error.
684 Start a transaction. Save a transaction ID for future reference.
687 - authkey (PCRUD only)
689 Return to client: Transaction ID
691 int beginTransaction( osrfMethodContext* ctx ) {
692 if(osrfMethodVerifyContext( ctx )) {
693 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
697 if( enforce_pcrud ) {
698 timeout_needs_resetting = 1;
699 const jsonObject* user = verifyUserPCRUD( ctx );
704 dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
707 int errnum = dbi_conn_error( writehandle, &msg );
708 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
709 modulename, errnum, msg ? msg : "(No description available)" );
710 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
711 "osrfMethodException", ctx->request, "Error starting transaction" );
715 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
716 osrfAppRespondComplete( ctx, ret );
717 jsonObjectFree( ret );
723 @brief Implement the savepoint.set method.
724 @param ctx Pointer to the method context.
725 @return Zero if successful, or -1 if not.
727 Issue a SAVEPOINT to the database server.
730 - authkey (PCRUD only)
733 Return to client: Savepoint name
735 int setSavepoint( osrfMethodContext* ctx ) {
736 if(osrfMethodVerifyContext( ctx )) {
737 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
742 if( enforce_pcrud ) {
744 timeout_needs_resetting = 1;
745 const jsonObject* user = verifyUserPCRUD( ctx );
750 // Verify that a transaction is pending
751 const char* trans_id = getXactId( ctx );
752 if( NULL == trans_id ) {
753 osrfAppSessionStatus(
755 OSRF_STATUS_INTERNALSERVERERROR,
756 "osrfMethodException",
758 "No active transaction -- required for savepoints"
763 // Get the savepoint name from the method params
764 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
766 dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", spName );
769 int errnum = dbi_conn_error( writehandle, &msg );
772 "%s: Error creating savepoint %s in transaction %s: %d %s",
777 msg ? msg : "(No description available)"
779 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
780 "osrfMethodException", ctx->request, "Error creating savepoint" );
783 jsonObject* ret = jsonNewObject( spName );
784 osrfAppRespondComplete( ctx, ret );
785 jsonObjectFree( ret );
791 @brief Implement the savepoint.release method.
792 @param ctx Pointer to the method context.
793 @return Zero if successful, or -1 if not.
795 Issue a RELEASE SAVEPOINT to the database server.
798 - authkey (PCRUD only)
801 Return to client: Savepoint name
803 int releaseSavepoint( osrfMethodContext* ctx ) {
804 if(osrfMethodVerifyContext( ctx )) {
805 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
810 if( enforce_pcrud ) {
812 timeout_needs_resetting = 1;
813 const jsonObject* user = verifyUserPCRUD( ctx );
818 // Verify that a transaction is pending
819 const char* trans_id = getXactId( ctx );
820 if( NULL == trans_id ) {
821 osrfAppSessionStatus(
823 OSRF_STATUS_INTERNALSERVERERROR,
824 "osrfMethodException",
826 "No active transaction -- required for savepoints"
831 // Get the savepoint name from the method params
832 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
834 dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", spName );
837 int errnum = dbi_conn_error( writehandle, &msg );
840 "%s: Error releasing savepoint %s in transaction %s: %d %s",
845 msg ? msg : "(No description available)"
847 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
848 "osrfMethodException", ctx->request, "Error releasing savepoint" );
851 jsonObject* ret = jsonNewObject( spName );
852 osrfAppRespondComplete( ctx, ret );
853 jsonObjectFree( ret );
859 @brief Implement the savepoint.rollback method.
860 @param ctx Pointer to the method context.
861 @return Zero if successful, or -1 if not.
863 Issue a ROLLBACK TO SAVEPOINT to the database server.
866 - authkey (PCRUD only)
869 Return to client: Savepoint name
871 int rollbackSavepoint( osrfMethodContext* ctx ) {
872 if(osrfMethodVerifyContext( ctx )) {
873 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
878 if( enforce_pcrud ) {
880 timeout_needs_resetting = 1;
881 const jsonObject* user = verifyUserPCRUD( ctx );
886 // Verify that a transaction is pending
887 const char* trans_id = getXactId( ctx );
888 if( NULL == trans_id ) {
889 osrfAppSessionStatus(
891 OSRF_STATUS_INTERNALSERVERERROR,
892 "osrfMethodException",
894 "No active transaction -- required for savepoints"
899 // Get the savepoint name from the method params
900 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
902 dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName );
905 int errnum = dbi_conn_error( writehandle, &msg );
908 "%s: Error rolling back savepoint %s in transaction %s: %d %s",
913 msg ? msg : "(No description available)"
915 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
916 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
919 jsonObject* ret = jsonNewObject( spName );
920 osrfAppRespondComplete( ctx, ret );
921 jsonObjectFree( ret );
927 @brief Implement the transaction.commit method.
928 @param ctx Pointer to the method context.
929 @return Zero if successful, or -1 if not.
931 Issue a COMMIT to the database server.
934 - authkey (PCRUD only)
936 Return to client: Transaction ID.
938 int commitTransaction( osrfMethodContext* ctx ) {
939 if(osrfMethodVerifyContext( ctx )) {
940 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
944 if( enforce_pcrud ) {
945 timeout_needs_resetting = 1;
946 const jsonObject* user = verifyUserPCRUD( ctx );
951 // Verify that a transaction is pending
952 const char* trans_id = getXactId( ctx );
953 if( NULL == trans_id ) {
954 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
955 "osrfMethodException", ctx->request, "No active transaction to commit" );
959 dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
962 int errnum = dbi_conn_error( writehandle, &msg );
963 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
964 modulename, errnum, msg ? msg : "(No description available)" );
965 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
966 "osrfMethodException", ctx->request, "Error committing transaction" );
969 jsonObject* ret = jsonNewObject( trans_id );
970 osrfAppRespondComplete( ctx, ret );
971 jsonObjectFree( ret );
978 @brief Implement the transaction.rollback method.
979 @param ctx Pointer to the method context.
980 @return Zero if successful, or -1 if not.
982 Issue a ROLLBACK to the database server.
985 - authkey (PCRUD only)
987 Return to client: Transaction ID
989 int rollbackTransaction( osrfMethodContext* ctx ) {
990 if( osrfMethodVerifyContext( ctx )) {
991 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
995 if( enforce_pcrud ) {
996 timeout_needs_resetting = 1;
997 const jsonObject* user = verifyUserPCRUD( ctx );
1002 // Verify that a transaction is pending
1003 const char* trans_id = getXactId( ctx );
1004 if( NULL == trans_id ) {
1005 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1006 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1010 dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1013 int errnum = dbi_conn_error( writehandle, &msg );
1014 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1015 modulename, errnum, msg ? msg : "(No description available)" );
1016 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1017 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1020 jsonObject* ret = jsonNewObject( trans_id );
1021 osrfAppRespondComplete( ctx, ret );
1022 jsonObjectFree( ret );
1029 @brief Implement the "search" method.
1030 @param ctx Pointer to the method context.
1031 @return Zero if successful, or -1 if not.
1034 - authkey (PCRUD only)
1035 - WHERE clause, as jsonObject
1036 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1038 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1039 Optionally flesh linked fields.
1041 int doSearch( osrfMethodContext* ctx ) {
1042 if( osrfMethodVerifyContext( ctx )) {
1043 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1048 timeout_needs_resetting = 1;
1050 jsonObject* where_clause;
1051 jsonObject* rest_of_query;
1053 if( enforce_pcrud ) {
1054 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1055 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1057 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1058 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1061 // Get the class metadata
1062 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1063 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1067 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1069 osrfAppRespondComplete( ctx, NULL );
1073 // Return each row to the client (except that some may be suppressed by PCRUD)
1074 jsonObject* cur = 0;
1075 unsigned long res_idx = 0;
1076 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1077 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1079 osrfAppRespond( ctx, cur );
1081 jsonObjectFree( obj );
1083 osrfAppRespondComplete( ctx, NULL );
1088 @brief Implement the "id_list" method.
1089 @param ctx Pointer to the method context.
1090 @param err Pointer through which to return an error code.
1091 @return Zero if successful, or -1 if not.
1094 - authkey (PCRUD only)
1095 - WHERE clause, as jsonObject
1096 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1098 Return to client: The primary key values for all rows of the relevant class that
1099 satisfy a specified WHERE clause.
1101 This method relies on the assumption that every class has a primary key consisting of
1104 int doIdList( osrfMethodContext* ctx ) {
1105 if( osrfMethodVerifyContext( ctx )) {
1106 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1111 timeout_needs_resetting = 1;
1113 jsonObject* where_clause;
1114 jsonObject* rest_of_query;
1116 // We use the where clause without change. But we need to massage the rest of the
1117 // query, so we work with a copy of it instead of modifying the original.
1119 if( enforce_pcrud ) {
1120 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1121 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1123 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1124 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1127 // Eliminate certain SQL clauses, if present.
1128 if( rest_of_query ) {
1129 jsonObjectRemoveKey( rest_of_query, "select" );
1130 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1131 jsonObjectRemoveKey( rest_of_query, "flesh" );
1132 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1134 rest_of_query = jsonNewObjectType( JSON_HASH );
1137 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1139 // Get the class metadata
1140 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1141 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1143 // Build a SELECT list containing just the primary key,
1144 // i.e. like { "classname":["keyname"] }
1145 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1147 // Load array with name of primary key
1148 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1149 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1150 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1152 jsonObjectSetKey( rest_of_query, "select", select_clause );
1157 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1159 jsonObjectFree( rest_of_query );
1161 osrfAppRespondComplete( ctx, NULL );
1165 // Return each primary key value to the client
1167 unsigned long res_idx = 0;
1168 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1169 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1170 continue; // Suppress due to lack of permission
1172 osrfAppRespond( ctx,
1173 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1176 jsonObjectFree( obj );
1177 osrfAppRespondComplete( ctx, NULL );
1182 @brief Verify that we have a valid class reference.
1183 @param ctx Pointer to the method context.
1184 @param param Pointer to the method parameters.
1185 @return 1 if the class reference is valid, or zero if it isn't.
1187 The class of the method params must match the class to which the method id devoted.
1188 For PCRUD there are additional restrictions.
1190 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1192 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1193 osrfHash* class = osrfHashGet( method_meta, "class" );
1195 // Compare the method's class to the parameters' class
1196 if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1198 // Oops -- they don't match. Complain.
1199 growing_buffer* msg = buffer_init( 128 );
1202 "%s: %s method for type %s was passed a %s",
1204 osrfHashGet( method_meta, "methodtype" ),
1205 osrfHashGet( class, "classname" ),
1206 param->classname ? param->classname : "(null)"
1209 char* m = buffer_release( msg );
1210 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1218 return verifyObjectPCRUD( ctx, param );
1224 @brief (PCRUD only) Verify that the user is properly logged in.
1225 @param ctx Pointer to the method context.
1226 @return If the user is logged in, a pointer to the user object from the authentication
1227 server; otherwise NULL.
1229 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1231 // Get the authkey (the first method parameter)
1232 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1234 // See if we have the same authkey, and a user object,
1235 // locally cached from a previous call
1236 const char* cached_authkey = getAuthkey( ctx );
1237 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1238 const jsonObject* cached_user = getUserLogin( ctx );
1243 // We have no matching authentication data in the cache. Authenticate from scratch.
1244 jsonObject* auth_object = jsonNewObject( auth );
1246 // Fetch the user object from the authentication server
1247 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1249 jsonObjectFree( auth_object );
1251 if( !user->classname || strcmp(user->classname, "au" )) {
1253 growing_buffer* msg = buffer_init( 128 );
1256 "%s: permacrud received a bad auth token: %s",
1261 char* m = buffer_release( msg );
1262 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1266 jsonObjectFree( user );
1270 setUserLogin( ctx, user );
1271 setAuthkey( ctx, auth );
1273 // Allow ourselves up to a second before we have to reset the login timeout.
1274 // It would be nice to use some fraction of the timeout interval enforced by the
1275 // authentication server, but that value is not readily available at this point.
1276 // Instead, we use a conservative default interval.
1277 time_next_reset = time( NULL ) + 1;
1283 @brief For PCRUD: Determine whether the current user may access the current row.
1284 @param ctx Pointer to the method context.
1285 @param obj Pointer to the row being potentially accessed.
1286 @return 1 if access is permitted, or 0 if it isn't.
1288 The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1290 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1292 dbhandle = writehandle;
1294 // Figure out what class and method are involved
1295 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1296 osrfHash* class = osrfHashGet( method_metadata, "class" );
1297 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1299 // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1300 // contexts we will do another lookup of the current row, even if we already have a
1301 // previously fetched row image, because the row image in hand may not include the
1302 // foreign key(s) that we need.
1304 // This is a quick fix with a bludgeon. There are ways to avoid the extra lookup,
1305 // but they aren't implemented yet.
1308 if( *method_type == 's' || *method_type == 'i' ) {
1309 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1311 } else if( *method_type == 'u' || *method_type == 'd' ) {
1312 fetch = 1; // MUST go to the db for the object for update and delete
1315 // Get the appropriate permacrud entry from the IDL, depending on method type
1316 osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1318 // No permacrud for this method type on this class
1320 growing_buffer* msg = buffer_init( 128 );
1323 "%s: %s on class %s has no permacrud IDL entry",
1325 osrfHashGet( method_metadata, "methodtype" ),
1326 osrfHashGet( class, "classname" )
1329 char* m = buffer_release( msg );
1330 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1331 "osrfMethodException", ctx->request, m );
1338 // Get the user id, and make sure the user is logged in
1339 const jsonObject* user = verifyUserPCRUD( ctx );
1341 return 0; // Not logged in? No access.
1343 int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1345 // Get a list of permissions from the permacrud entry.
1346 osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1348 // Build a list of org units that own the row. This is fairly convoluted because there
1349 // are several different ways that an org unit may own the row, as defined by the
1352 // Local context means that the row includes a foreign key pointing to actor.org_unit,
1353 // identifying an owning org_unit..
1354 osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1356 // Foreign context adds a layer of indirection. The row points to some other row that
1357 // an org unit may own. The "jump" attribute, if present, adds another layer of
1359 osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1361 // The following string array stores the list of org units. (We don't have a thingie
1362 // for storing lists of integers, so we fake it with a list of strings.)
1363 osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1366 const char* pkey_value = NULL;
1367 if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1368 // If the global_required attribute is present and true, then the only owning
1369 // org unit is the root org unit, i.e. the one with no parent.
1370 osrfLogDebug( OSRF_LOG_MARK,
1371 "global-level permissions required, fetching top of the org tree" );
1373 // check for perm at top of org tree
1374 const char* org_tree_root_id = org_tree_root( ctx );
1375 if( org_tree_root_id ) {
1376 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1377 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1379 osrfStringArrayFree( context_org_array );
1384 // If the global_required attribute is absent or false, then we look for
1385 // local and/or foreign context. In order to find the relevant foreign
1386 // keys, we must either read the relevant row from the database, or look at
1387 // the image of the row that we already have in memory.
1389 // (Herein lies a bug. Even if we have an image of the row in memory, that
1390 // image may not include the foreign key column(s) that we need.)
1392 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1393 "fetching context org ids" );
1394 const char* pkey = osrfHashGet( class, "primarykey" );
1395 jsonObject *param = NULL;
1397 if( obj->classname ) {
1398 pkey_value = oilsFMGetStringConst( obj, pkey );
1400 param = jsonObjectClone( obj );
1401 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1404 pkey_value = jsonObjectGetString( obj );
1406 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1407 "of %s and retrieving from the database", pkey_value );
1411 // Fetch the row so that we can look at the foreign key(s)
1412 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1413 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1414 jsonObjectFree( _tmp_params );
1416 param = jsonObjectExtractIndex( _list, 0 );
1417 jsonObjectFree( _list );
1421 // The row doesn't exist. Complain, and deny access.
1422 osrfLogDebug( OSRF_LOG_MARK,
1423 "Object not found in the database with primary key %s of %s",
1426 growing_buffer* msg = buffer_init( 128 );
1429 "%s: no object found with primary key %s of %s",
1435 char* m = buffer_release( msg );
1436 osrfAppSessionStatus(
1438 OSRF_STATUS_INTERNALSERVERERROR,
1439 "osrfMethodException",
1448 if( local_context && local_context->size > 0 ) {
1449 // The IDL provides a list of column names for the foreign keys denoting
1450 // local context. Look up the value of each one, and if it isn't null,
1451 // add it to the list of org units.
1452 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1453 local_context->size );
1455 const char* lcontext = NULL;
1456 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1457 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1458 if( fkey_value ) { // if not null
1459 osrfStringArrayAdd( context_org_array, fkey_value );
1462 "adding class-local field %s (value: %s) to the context org list",
1464 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1470 if( foreign_context ) {
1471 unsigned long class_count = osrfHashGetCount( foreign_context );
1472 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1474 if( class_count > 0 ) {
1476 // The IDL provides a list of foreign key columns pointing to rows that
1477 // an org unit may own. Follow each link, identify the owning org unit,
1478 // and add it to the list.
1479 osrfHash* fcontext = NULL;
1480 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1481 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1482 // For each class to which a foreign key points:
1483 const char* class_name = osrfHashIteratorKey( class_itr );
1484 osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1488 "%d foreign context fields(s) specified for class %s",
1489 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1493 // Get the name of the key field in the foreign table
1494 const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1496 // Get the value of the foreign key pointing to the foreign table
1497 char* foreign_pkey_value =
1498 oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1499 if( !foreign_pkey_value )
1500 continue; // Foreign key value is null; skip it
1502 // Look up the row to which the foreign key points
1503 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1504 jsonObject* _list = doFieldmapperSearch(
1505 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1507 jsonObject* _fparam = NULL;
1508 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1509 _fparam = jsonObjectExtractIndex( _list, 0 );
1511 jsonObjectFree( _tmp_params );
1512 jsonObjectFree( _list );
1514 // At this point _fparam either points to the row identified by the
1515 // foreign key, or it's NULL (no such row found).
1517 osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1519 const char* bad_class = NULL; // For noting failed lookups
1521 bad_class = class_name; // Referenced row not found
1522 else if( jump_list ) {
1523 // Follow a chain of rows, linked by foreign keys, to find an owner
1524 const char* flink = NULL;
1526 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1527 // For each entry in the jump list. Each entry (i.e. flink) is
1528 // the name of a foreign key column in the current row.
1530 // From the IDL, get the linkage information for the next jump
1531 osrfHash* foreign_link_hash =
1532 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1534 // Get the class metadata for the class
1535 // to which the foreign key points
1536 osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1537 osrfHashGet( foreign_link_hash, "class" ));
1539 // Get the name of the referenced key of that class
1540 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1542 // Get the value of the foreign key pointing to that class
1543 free( foreign_pkey_value );
1544 foreign_pkey_value = oilsFMGetString( _fparam, flink );
1545 if( !foreign_pkey_value )
1546 break; // Foreign key is null; quit looking
1548 // Build a WHERE clause for the lookup
1549 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1552 _list = doFieldmapperSearch( ctx, foreign_class_meta,
1553 _tmp_params, NULL, &err );
1555 // Get the resulting row
1556 jsonObjectFree( _fparam );
1557 if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1558 _fparam = jsonObjectExtractIndex( _list, 0 );
1560 // Referenced row not found
1562 bad_class = osrfHashGet( foreign_link_hash, "class" );
1565 jsonObjectFree( _tmp_params );
1566 jsonObjectFree( _list );
1572 // We had a foreign key pointing to such-and-such a row, but then
1573 // we couldn't fetch that row. The data in the database are in an
1574 // inconsistent state; the database itself may even be corrupted.
1575 growing_buffer* msg = buffer_init( 128 );
1578 "%s: no object of class %s found with primary key %s of %s",
1582 foreign_pkey_value ? foreign_pkey_value : "(null)"
1585 char* m = buffer_release( msg );
1586 osrfAppSessionStatus(
1588 OSRF_STATUS_INTERNALSERVERERROR,
1589 "osrfMethodException",
1595 osrfHashIteratorFree( class_itr );
1596 free( foreign_pkey_value );
1597 jsonObjectFree( param );
1602 free( foreign_pkey_value );
1605 // Examine each context column of the foreign row,
1606 // and add its value to the list of org units.
1608 const char* foreign_field = NULL;
1609 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1610 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1611 osrfStringArrayAdd( context_org_array,
1612 oilsFMGetStringConst( _fparam, foreign_field ));
1613 osrfLogDebug( OSRF_LOG_MARK,
1614 "adding foreign class %s field %s (value: %s) "
1615 "to the context org list",
1618 osrfStringArrayGetString(
1619 context_org_array, context_org_array->size - 1 )
1623 jsonObjectFree( _fparam );
1627 osrfHashIteratorFree( class_itr );
1631 jsonObjectFree( param );
1634 const char* context_org = NULL;
1635 const char* perm = NULL;
1638 if( permission->size == 0 ) {
1639 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1643 // For every combination of permission and context org unit: call a stored procedure
1644 // to determine if the user has this permission in the context of this org unit.
1645 // If the answer is yes at any point, then we're done, and the user has permission.
1646 // In other words permissions are additive.
1648 while( (perm = osrfStringArrayGetString(permission, i++)) ) {
1650 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
1656 "Checking object permission [%s] for user %d "
1657 "on object %s (class %s) at org %d",
1661 osrfHashGet( class, "classname" ),
1665 result = dbi_conn_queryf(
1667 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1670 osrfHashGet( class, "classname" ),
1678 "Received a result for object permission [%s] "
1679 "for user %d on object %s (class %s) at org %d",
1683 osrfHashGet( class, "classname" ),
1687 if( dbi_result_first_row( result )) {
1688 jsonObject* return_val = oilsMakeJSONFromResult( result );
1689 const char* has_perm = jsonObjectGetString(
1690 jsonObjectGetKeyConst( return_val, "has_perm" ));
1694 "Status of object permission [%s] for user %d "
1695 "on object %s (class %s) at org %d is %s",
1699 osrfHashGet(class, "classname"),
1704 if( *has_perm == 't' )
1706 jsonObjectFree( return_val );
1709 dbi_result_free( result );
1714 int errnum = dbi_conn_error( writehandle, &msg );
1715 osrfLogWarning( OSRF_LOG_MARK,
1716 "Unable to call check object permissions: %d, %s",
1717 errnum, msg ? msg : "(No description available)" );
1721 osrfLogDebug( OSRF_LOG_MARK,
1722 "Checking non-object permission [%s] for user %d at org %d",
1723 perm, userid, atoi(context_org) );
1724 result = dbi_conn_queryf(
1726 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1733 osrfLogDebug( OSRF_LOG_MARK,
1734 "Received a result for permission [%s] for user %d at org %d",
1735 perm, userid, atoi( context_org ));
1736 if( dbi_result_first_row( result )) {
1737 jsonObject* return_val = oilsMakeJSONFromResult( result );
1738 const char* has_perm = jsonObjectGetString(
1739 jsonObjectGetKeyConst( return_val, "has_perm" ));
1740 osrfLogDebug( OSRF_LOG_MARK,
1741 "Status of permission [%s] for user %d at org %d is [%s]",
1742 perm, userid, atoi( context_org ), has_perm );
1743 if( *has_perm == 't' )
1745 jsonObjectFree( return_val );
1748 dbi_result_free( result );
1753 int errnum = dbi_conn_error( writehandle, &msg );
1754 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
1755 errnum, msg ? msg : "(No description available)" );
1763 osrfStringArrayFree( context_org_array );
1769 @brief Look up the root of the org_unit tree.
1770 @param ctx Pointer to the method context.
1771 @return The id of the root org unit, as a character string.
1773 Query actor.org_unit where parent_ou is null, and return the id as a string.
1775 This function assumes that there is only one root org unit, i.e. that we
1776 have a single tree, not a forest.
1778 The calling code is responsible for freeing the returned string.
1780 static const char* org_tree_root( osrfMethodContext* ctx ) {
1782 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1783 static time_t last_lookup_time = 0;
1784 time_t current_time = time( NULL );
1786 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1787 // We successfully looked this up less than an hour ago.
1788 // It's not likely to have changed since then.
1789 return strdup( cached_root_id );
1791 last_lookup_time = current_time;
1794 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1795 jsonObject* result = doFieldmapperSearch(
1796 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1797 jsonObjectFree( where_clause );
1799 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1802 jsonObjectFree( result );
1804 growing_buffer* msg = buffer_init( 128 );
1805 OSRF_BUFFER_ADD( msg, modulename );
1806 OSRF_BUFFER_ADD( msg,
1807 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1809 char* m = buffer_release( msg );
1810 osrfAppSessionStatus( ctx->session,
1811 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1814 cached_root_id[ 0 ] = '\0';
1818 const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
1819 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1821 strcpy( cached_root_id, root_org_unit_id );
1822 jsonObjectFree( result );
1823 return cached_root_id;
1827 @brief Create a JSON_HASH with a single key/value pair.
1828 @param key The key of the key/value pair.
1829 @param value the value of the key/value pair.
1830 @return Pointer to a newly created jsonObject of type JSON_HASH.
1832 The value of the key/value is either a string or (if @a value is NULL) a null.
1834 static jsonObject* single_hash( const char* key, const char* value ) {
1836 if( ! key ) key = "";
1838 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1839 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1844 int doCreate( osrfMethodContext* ctx ) {
1845 if(osrfMethodVerifyContext( ctx )) {
1846 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1851 timeout_needs_resetting = 1;
1853 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1854 jsonObject* target = NULL;
1855 jsonObject* options = NULL;
1857 if( enforce_pcrud ) {
1858 target = jsonObjectGetIndex( ctx->params, 1 );
1859 options = jsonObjectGetIndex( ctx->params, 2 );
1861 target = jsonObjectGetIndex( ctx->params, 0 );
1862 options = jsonObjectGetIndex( ctx->params, 1 );
1865 if( !verifyObjectClass( ctx, target )) {
1866 osrfAppRespondComplete( ctx, NULL );
1870 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1872 const char* trans_id = getXactId( ctx );
1874 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1876 osrfAppSessionStatus(
1878 OSRF_STATUS_BADREQUEST,
1879 "osrfMethodException",
1881 "No active transaction -- required for CREATE"
1883 osrfAppRespondComplete( ctx, NULL );
1887 // The following test is harmless but redundant. If a class is
1888 // readonly, we don't register a create method for it.
1889 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1890 osrfAppSessionStatus(
1892 OSRF_STATUS_BADREQUEST,
1893 "osrfMethodException",
1895 "Cannot INSERT readonly class"
1897 osrfAppRespondComplete( ctx, NULL );
1901 // Set the last_xact_id
1902 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1904 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1905 trans_id, target->classname, index);
1906 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
1909 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1911 dbhandle = writehandle;
1913 osrfHash* fields = osrfHashGet( meta, "fields" );
1914 char* pkey = osrfHashGet( meta, "primarykey" );
1915 char* seq = osrfHashGet( meta, "sequence" );
1917 growing_buffer* table_buf = buffer_init( 128 );
1918 growing_buffer* col_buf = buffer_init( 128 );
1919 growing_buffer* val_buf = buffer_init( 128 );
1921 OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
1922 OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
1923 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1924 buffer_add( val_buf,"VALUES (" );
1928 osrfHash* field = NULL;
1929 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1930 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1932 const char* field_name = osrfHashIteratorKey( field_itr );
1934 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1937 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1940 if( field_object && field_object->classname ) {
1941 value = oilsFMGetString(
1943 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
1945 } else if( field_object && JSON_BOOL == field_object->type ) {
1946 if( jsonBoolIsTrue( field_object ) )
1947 value = strdup( "t" );
1949 value = strdup( "f" );
1951 value = jsonObjectToSimpleString( field_object );
1957 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1958 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1961 buffer_add( col_buf, field_name );
1963 if( !field_object || field_object->type == JSON_NULL ) {
1964 buffer_add( val_buf, "DEFAULT" );
1966 } else if( !strcmp( get_primitive( field ), "number" )) {
1967 const char* numtype = get_datatype( field );
1968 if( !strcmp( numtype, "INT8" )) {
1969 buffer_fadd( val_buf, "%lld", atoll( value ));
1971 } else if( !strcmp( numtype, "INT" )) {
1972 buffer_fadd( val_buf, "%d", atoi( value ));
1974 } else if( !strcmp( numtype, "NUMERIC" )) {
1975 buffer_fadd( val_buf, "%f", atof( value ));
1978 if( dbi_conn_quote_string( writehandle, &value )) {
1979 OSRF_BUFFER_ADD( val_buf, value );
1982 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
1983 osrfAppSessionStatus(
1985 OSRF_STATUS_INTERNALSERVERERROR,
1986 "osrfMethodException",
1988 "Error quoting string -- please see the error log for more details"
1991 buffer_free( table_buf );
1992 buffer_free( col_buf );
1993 buffer_free( val_buf );
1994 osrfAppRespondComplete( ctx, NULL );
2002 osrfHashIteratorFree( field_itr );
2004 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2005 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2007 char* table_str = buffer_release( table_buf );
2008 char* col_str = buffer_release( col_buf );
2009 char* val_str = buffer_release( val_buf );
2010 growing_buffer* sql = buffer_init( 128 );
2011 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2016 char* query = buffer_release( sql );
2018 osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2020 jsonObject* obj = NULL;
2023 dbi_result result = dbi_conn_query( writehandle, query );
2025 obj = jsonNewObject( NULL );
2027 int errnum = dbi_conn_error( writehandle, &msg );
2030 "%s ERROR inserting %s object using query [%s]: %d %s",
2032 osrfHashGet(meta, "fieldmapper"),
2035 msg ? msg : "(No description available)"
2037 osrfAppSessionStatus(
2039 OSRF_STATUS_INTERNALSERVERERROR,
2040 "osrfMethodException",
2042 "INSERT error -- please see the error log for more details"
2047 char* id = oilsFMGetString( target, pkey );
2049 unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2050 growing_buffer* _id = buffer_init( 10 );
2051 buffer_fadd( _id, "%lld", new_id );
2052 id = buffer_release( _id );
2055 // Find quietness specification, if present
2056 const char* quiet_str = NULL;
2058 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2060 quiet_str = jsonObjectGetString( quiet_obj );
2063 if( str_is_true( quiet_str )) { // if quietness is specified
2064 obj = jsonNewObject( id );
2068 // Fetch the row that we just inserted, so that we can return it to the client
2069 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2070 jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2073 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2077 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2079 jsonObjectFree( list );
2080 jsonObjectFree( where_clause );
2087 osrfAppRespondComplete( ctx, obj );
2088 jsonObjectFree( obj );
2093 @brief Implement the retrieve method.
2094 @param ctx Pointer to the method context.
2095 @param err Pointer through which to return an error code.
2096 @return If successful, a pointer to the result to be returned to the client;
2099 From the method's class, fetch a row with a specified value in the primary key. This
2100 method relies on the database design convention that a primary key consists of a single
2104 - authkey (PCRUD only)
2105 - value of the primary key for the desired row, for building the WHERE clause
2106 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2108 Return to client: One row from the query.
2110 int doRetrieve( osrfMethodContext* ctx ) {
2111 if(osrfMethodVerifyContext( ctx )) {
2112 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2117 timeout_needs_resetting = 1;
2122 if( enforce_pcrud ) {
2127 // Get the class metadata
2128 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2130 // Get the value of the primary key, from a method parameter
2131 const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2135 "%s retrieving %s object with primary key value of %s",
2137 osrfHashGet( class_def, "fieldmapper" ),
2138 jsonObjectGetString( id_obj )
2141 // Build a WHERE clause based on the key value
2142 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2145 osrfHashGet( class_def, "primarykey" ), // name of key column
2146 jsonObjectClone( id_obj ) // value of key column
2149 jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2153 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2155 jsonObjectFree( where_clause );
2157 osrfAppRespondComplete( ctx, NULL );
2161 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2162 jsonObjectFree( list );
2164 if( enforce_pcrud ) {
2165 if(!verifyObjectPCRUD( ctx, obj )) {
2166 jsonObjectFree( obj );
2168 growing_buffer* msg = buffer_init( 128 );
2169 OSRF_BUFFER_ADD( msg, modulename );
2170 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2172 char* m = buffer_release( msg );
2173 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2177 osrfAppRespondComplete( ctx, NULL );
2182 osrfAppRespondComplete( ctx, obj );
2183 jsonObjectFree( obj );
2188 @brief Translate a numeric value to a string representation for the database.
2189 @param field Pointer to the IDL field definition.
2190 @param value Pointer to a jsonObject holding the value of a field.
2191 @return Pointer to a newly allocated string.
2193 The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2194 its contents are numeric. A non-numeric string is likely to result in invalid SQL,
2195 or (what is worse) valid SQL that is wrong.
2197 If the datatype of the receiving field is not numeric, wrap the value in quotes.
2199 The calling code is responsible for freeing the resulting string by calling free().
2201 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2202 growing_buffer* val_buf = buffer_init( 32 );
2203 const char* numtype = get_datatype( field );
2205 // For historical reasons the following contains cruft that could be cleaned up.
2206 if( !strncmp( numtype, "INT", 3 ) ) {
2207 if( value->type == JSON_NUMBER )
2208 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2209 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2211 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2214 } else if( !strcmp( numtype, "NUMERIC" )) {
2215 if( value->type == JSON_NUMBER )
2216 buffer_fadd( val_buf, jsonObjectGetString( value ));
2218 buffer_fadd( val_buf, jsonObjectGetString( value ));
2222 // Presumably this was really intended to be a string, so quote it
2223 char* str = jsonObjectToSimpleString( value );
2224 if( dbi_conn_quote_string( dbhandle, &str )) {
2225 OSRF_BUFFER_ADD( val_buf, str );
2228 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2230 buffer_free( val_buf );
2235 return buffer_release( val_buf );
2238 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2239 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2240 growing_buffer* sql_buf = buffer_init( 32 );
2246 osrfHashGet( field, "name" )
2250 buffer_add( sql_buf, "IN (" );
2251 } else if( !strcasecmp( op,"not in" )) {
2252 buffer_add( sql_buf, "NOT IN (" );
2254 buffer_add( sql_buf, "IN (" );
2257 if( node->type == JSON_HASH ) {
2258 // subquery predicate
2259 char* subpred = buildQuery( ctx, node, SUBSELECT );
2261 buffer_free( sql_buf );
2265 buffer_add( sql_buf, subpred );
2268 } else if( node->type == JSON_ARRAY ) {
2269 // literal value list
2270 int in_item_index = 0;
2271 int in_item_first = 1;
2272 const jsonObject* in_item;
2273 while( (in_item = jsonObjectGetIndex( node, in_item_index++ )) ) {
2278 buffer_add( sql_buf, ", " );
2281 if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2282 osrfLogError( OSRF_LOG_MARK,
2283 "%s: Expected string or number within IN list; found %s",
2284 modulename, json_type( in_item->type ) );
2285 buffer_free( sql_buf );
2289 // Append the literal value -- quoted if not a number
2290 if( JSON_NUMBER == in_item->type ) {
2291 char* val = jsonNumberToDBString( field, in_item );
2292 OSRF_BUFFER_ADD( sql_buf, val );
2295 } else if( !strcmp( get_primitive( field ), "number" )) {
2296 char* val = jsonNumberToDBString( field, in_item );
2297 OSRF_BUFFER_ADD( sql_buf, val );
2301 char* key_string = jsonObjectToSimpleString( in_item );
2302 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2303 OSRF_BUFFER_ADD( sql_buf, key_string );
2306 osrfLogError( OSRF_LOG_MARK,
2307 "%s: Error quoting key string [%s]", modulename, key_string );
2309 buffer_free( sql_buf );
2315 if( in_item_first ) {
2316 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2317 buffer_free( sql_buf );
2321 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2322 modulename, json_type( node->type ));
2323 buffer_free( sql_buf );
2327 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2329 return buffer_release( sql_buf );
2332 // Receive a JSON_ARRAY representing a function call. The first
2333 // entry in the array is the function name. The rest are parameters.
2334 static char* searchValueTransform( const jsonObject* array ) {
2336 if( array->size < 1 ) {
2337 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2341 // Get the function name
2342 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2343 if( func_item->type != JSON_STRING ) {
2344 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2345 modulename, json_type( func_item->type ));
2349 growing_buffer* sql_buf = buffer_init( 32 );
2351 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2352 OSRF_BUFFER_ADD( sql_buf, "( " );
2354 // Get the parameters
2355 int func_item_index = 1; // We already grabbed the zeroth entry
2356 while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2358 // Add a separator comma, if we need one
2359 if( func_item_index > 2 )
2360 buffer_add( sql_buf, ", " );
2362 // Add the current parameter
2363 if( func_item->type == JSON_NULL ) {
2364 buffer_add( sql_buf, "NULL" );
2366 char* val = jsonObjectToSimpleString( func_item );
2367 if( dbi_conn_quote_string( dbhandle, &val )) {
2368 OSRF_BUFFER_ADD( sql_buf, val );
2371 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2373 buffer_free( sql_buf );
2380 buffer_add( sql_buf, " )" );
2382 return buffer_release( sql_buf );
2385 static char* searchFunctionPredicate( const char* class_alias, osrfHash* field,
2386 const jsonObject* node, const char* op ) {
2388 if( ! is_good_operator( op ) ) {
2389 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2393 char* val = searchValueTransform( node );
2397 growing_buffer* sql_buf = buffer_init( 32 );
2402 osrfHashGet( field, "name" ),
2409 return buffer_release( sql_buf );
2412 // class_alias is a class name or other table alias
2413 // field is a field definition as stored in the IDL
2414 // node comes from the method parameter, and may represent an entry in the SELECT list
2415 static char* searchFieldTransform( const char* class_alias, osrfHash* field,
2416 const jsonObject* node ) {
2417 growing_buffer* sql_buf = buffer_init( 32 );
2419 const char* field_transform = jsonObjectGetString(
2420 jsonObjectGetKeyConst( node, "transform" ) );
2421 const char* transform_subcolumn = jsonObjectGetString(
2422 jsonObjectGetKeyConst( node, "result_field" ) );
2424 if( transform_subcolumn ) {
2425 if( ! is_identifier( transform_subcolumn ) ) {
2426 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2427 modulename, transform_subcolumn );
2428 buffer_free( sql_buf );
2431 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2434 if( field_transform ) {
2436 if( ! is_identifier( field_transform ) ) {
2437 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2438 modulename, field_transform );
2439 buffer_free( sql_buf );
2443 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2444 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2445 field_transform, class_alias, osrfHashGet( field, "name" ));
2447 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2448 field_transform, class_alias, osrfHashGet( field, "name" ));
2451 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2454 if( array->type != JSON_ARRAY ) {
2455 osrfLogError( OSRF_LOG_MARK,
2456 "%s: Expected JSON_ARRAY for function params; found %s",
2457 modulename, json_type( array->type ) );
2458 buffer_free( sql_buf );
2461 int func_item_index = 0;
2462 jsonObject* func_item;
2463 while( (func_item = jsonObjectGetIndex( array, func_item_index++ ))) {
2465 char* val = jsonObjectToSimpleString( func_item );
2468 buffer_add( sql_buf, ",NULL" );
2469 } else if( dbi_conn_quote_string( dbhandle, &val )) {
2470 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2471 OSRF_BUFFER_ADD( sql_buf, val );
2473 osrfLogError( OSRF_LOG_MARK,
2474 "%s: Error quoting key string [%s]", modulename, val );
2476 buffer_free( sql_buf );
2483 buffer_add( sql_buf, " )" );
2486 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet( field, "name" ));
2489 if( transform_subcolumn )
2490 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2492 return buffer_release( sql_buf );
2495 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2496 const jsonObject* node, const char* op ) {
2498 if( ! is_good_operator( op ) ) {
2499 osrfLogError( OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op );
2503 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2504 if( ! field_transform )
2507 int extra_parens = 0; // boolean
2509 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2511 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2513 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2515 free( field_transform );
2519 } else if( value_obj->type == JSON_ARRAY ) {
2520 value = searchValueTransform( value_obj );
2522 osrfLogError( OSRF_LOG_MARK,
2523 "%s: Error building value transform for field transform", modulename );
2524 free( field_transform );
2527 } else if( value_obj->type == JSON_HASH ) {
2528 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2530 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2532 free( field_transform );
2536 } else if( value_obj->type == JSON_NUMBER ) {
2537 value = jsonNumberToDBString( field, value_obj );
2538 } else if( value_obj->type == JSON_NULL ) {
2539 osrfLogError( OSRF_LOG_MARK,
2540 "%s: Error building predicate for field transform: null value", modulename );
2541 free( field_transform );
2543 } else if( value_obj->type == JSON_BOOL ) {
2544 osrfLogError( OSRF_LOG_MARK,
2545 "%s: Error building predicate for field transform: boolean value", modulename );
2546 free( field_transform );
2549 if( !strcmp( get_primitive( field ), "number") ) {
2550 value = jsonNumberToDBString( field, value_obj );
2552 value = jsonObjectToSimpleString( value_obj );
2553 if( !dbi_conn_quote_string( dbhandle, &value )) {
2554 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2555 modulename, value );
2557 free( field_transform );
2563 const char* left_parens = "";
2564 const char* right_parens = "";
2566 if( extra_parens ) {
2571 growing_buffer* sql_buf = buffer_init( 32 );
2575 "%s%s %s %s %s %s%s",
2586 free( field_transform );
2588 return buffer_release( sql_buf );
2591 static char* searchSimplePredicate( const char* op, const char* class_alias,
2592 osrfHash* field, const jsonObject* node ) {
2594 if( ! is_good_operator( op ) ) {
2595 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2601 // Get the value to which we are comparing the specified column
2602 if( node->type != JSON_NULL ) {
2603 if( node->type == JSON_NUMBER ) {
2604 val = jsonNumberToDBString( field, node );
2605 } else if( !strcmp( get_primitive( field ), "number" ) ) {
2606 val = jsonNumberToDBString( field, node );
2608 val = jsonObjectToSimpleString( node );
2613 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2614 // Value is not numeric; enclose it in quotes
2615 if( !dbi_conn_quote_string( dbhandle, &val ) ) {
2616 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2623 // Compare to a null value
2624 val = strdup( "NULL" );
2625 if( strcmp( op, "=" ))
2631 growing_buffer* sql_buf = buffer_init( 32 );
2632 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2633 char* pred = buffer_release( sql_buf );
2640 static char* searchBETWEENPredicate( const char* class_alias,
2641 osrfHash* field, const jsonObject* node ) {
2643 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2644 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2646 if( NULL == y_node ) {
2647 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2650 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2651 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2658 if( !strcmp( get_primitive( field ), "number") ) {
2659 x_string = jsonNumberToDBString( field, x_node );
2660 y_string = jsonNumberToDBString( field, y_node );
2663 x_string = jsonObjectToSimpleString( x_node );
2664 y_string = jsonObjectToSimpleString( y_node );
2665 if( !(dbi_conn_quote_string( dbhandle, &x_string )
2666 && dbi_conn_quote_string( dbhandle, &y_string )) ) {
2667 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2668 modulename, x_string, y_string );
2675 growing_buffer* sql_buf = buffer_init( 32 );
2676 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2677 class_alias, osrfHashGet( field, "name" ), x_string, y_string );
2681 return buffer_release( sql_buf );
2684 static char* searchPredicate( const ClassInfo* class_info, osrfHash* field,
2685 jsonObject* node, osrfMethodContext* ctx ) {
2688 if( node->type == JSON_ARRAY ) { // equality IN search
2689 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2690 } else if( node->type == JSON_HASH ) { // other search
2691 jsonIterator* pred_itr = jsonNewIterator( node );
2692 if( !jsonIteratorHasNext( pred_itr ) ) {
2693 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2694 modulename, osrfHashGet(field, "name" ));
2696 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2698 // Verify that there are no additional predicates
2699 if( jsonIteratorHasNext( pred_itr ) ) {
2700 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2701 modulename, osrfHashGet(field, "name" ));
2702 } else if( !(strcasecmp( pred_itr->key,"between" )) )
2703 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2704 else if( !(strcasecmp( pred_itr->key,"in" ))
2705 || !(strcasecmp( pred_itr->key,"not in" )) )
2706 pred = searchINPredicate(
2707 class_info->alias, field, pred_node, pred_itr->key, ctx );
2708 else if( pred_node->type == JSON_ARRAY )
2709 pred = searchFunctionPredicate(
2710 class_info->alias, field, pred_node, pred_itr->key );
2711 else if( pred_node->type == JSON_HASH )
2712 pred = searchFieldTransformPredicate(
2713 class_info, field, pred_node, pred_itr->key );
2715 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2717 jsonIteratorFree( pred_itr );
2719 } else if( node->type == JSON_NULL ) { // IS NULL search
2720 growing_buffer* _p = buffer_init( 64 );
2723 "\"%s\".%s IS NULL",
2724 class_info->class_name,
2725 osrfHashGet( field, "name" )
2727 pred = buffer_release( _p );
2728 } else { // equality search
2729 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2748 field : call_number,
2764 static char* searchJOIN( const jsonObject* join_hash, const ClassInfo* left_info ) {
2766 const jsonObject* working_hash;
2767 jsonObject* freeable_hash = NULL;
2769 if( join_hash->type == JSON_HASH ) {
2770 working_hash = join_hash;
2771 } else if( join_hash->type == JSON_STRING ) {
2772 // turn it into a JSON_HASH by creating a wrapper
2773 // around a copy of the original
2774 const char* _tmp = jsonObjectGetString( join_hash );
2775 freeable_hash = jsonNewObjectType( JSON_HASH );
2776 jsonObjectSetKey( freeable_hash, _tmp, NULL );
2777 working_hash = freeable_hash;
2781 "%s: JOIN failed; expected JSON object type not found",
2787 growing_buffer* join_buf = buffer_init( 128 );
2788 const char* leftclass = left_info->class_name;
2790 jsonObject* snode = NULL;
2791 jsonIterator* search_itr = jsonNewIterator( working_hash );
2793 while ( (snode = jsonIteratorNext( search_itr )) ) {
2794 const char* right_alias = search_itr->key;
2796 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2798 class = right_alias;
2800 const ClassInfo* right_info = add_joined_class( right_alias, class );
2804 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2808 jsonIteratorFree( search_itr );
2809 buffer_free( join_buf );
2811 jsonObjectFree( freeable_hash );
2814 osrfHash* links = right_info->links;
2815 const char* table = right_info->source_def;
2817 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2818 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2820 if( field && !fkey ) {
2821 // Look up the corresponding join column in the IDL.
2822 // The link must be defined in the child table,
2823 // and point to the right parent table.
2824 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2825 const char* reltype = NULL;
2826 const char* other_class = NULL;
2827 reltype = osrfHashGet( idl_link, "reltype" );
2828 if( reltype && strcmp( reltype, "has_many" ) )
2829 other_class = osrfHashGet( idl_link, "class" );
2830 if( other_class && !strcmp( other_class, leftclass ) )
2831 fkey = osrfHashGet( idl_link, "key" );
2835 "%s: JOIN failed. No link defined from %s.%s to %s",
2841 buffer_free( join_buf );
2843 jsonObjectFree( freeable_hash );
2844 jsonIteratorFree( search_itr );
2848 } else if( !field && fkey ) {
2849 // Look up the corresponding join column in the IDL.
2850 // The link must be defined in the child table,
2851 // and point to the right parent table.
2852 osrfHash* left_links = left_info->links;
2853 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2854 const char* reltype = NULL;
2855 const char* other_class = NULL;
2856 reltype = osrfHashGet( idl_link, "reltype" );
2857 if( reltype && strcmp( reltype, "has_many" ) )
2858 other_class = osrfHashGet( idl_link, "class" );
2859 if( other_class && !strcmp( other_class, class ) )
2860 field = osrfHashGet( idl_link, "key" );
2864 "%s: JOIN failed. No link defined from %s.%s to %s",
2870 buffer_free( join_buf );
2872 jsonObjectFree( freeable_hash );
2873 jsonIteratorFree( search_itr );
2877 } else if( !field && !fkey ) {
2878 osrfHash* left_links = left_info->links;
2880 // For each link defined for the left class:
2881 // see if the link references the joined class
2882 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2883 osrfHash* curr_link = NULL;
2884 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2885 const char* other_class = osrfHashGet( curr_link, "class" );
2886 if( other_class && !strcmp( other_class, class ) ) {
2888 // In the IDL, the parent class doesn't always know then names of the child
2889 // columns that are pointing to it, so don't use that end of the link
2890 const char* reltype = osrfHashGet( curr_link, "reltype" );
2891 if( reltype && strcmp( reltype, "has_many" ) ) {
2892 // Found a link between the classes
2893 fkey = osrfHashIteratorKey( itr );
2894 field = osrfHashGet( curr_link, "key" );
2899 osrfHashIteratorFree( itr );
2901 if( !field || !fkey ) {
2902 // Do another such search, with the classes reversed
2904 // For each link defined for the joined class:
2905 // see if the link references the left class
2906 osrfHashIterator* itr = osrfNewHashIterator( links );
2907 osrfHash* curr_link = NULL;
2908 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2909 const char* other_class = osrfHashGet( curr_link, "class" );
2910 if( other_class && !strcmp( other_class, leftclass ) ) {
2912 // In the IDL, the parent class doesn't know then names of the child
2913 // columns that are pointing to it, so don't use that end of the link
2914 const char* reltype = osrfHashGet( curr_link, "reltype" );
2915 if( reltype && strcmp( reltype, "has_many" ) ) {
2916 // Found a link between the classes
2917 field = osrfHashIteratorKey( itr );
2918 fkey = osrfHashGet( curr_link, "key" );
2923 osrfHashIteratorFree( itr );
2926 if( !field || !fkey ) {
2929 "%s: JOIN failed. No link defined between %s and %s",
2934 buffer_free( join_buf );
2936 jsonObjectFree( freeable_hash );
2937 jsonIteratorFree( search_itr );
2942 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2944 if( !strcasecmp( type,"left" )) {
2945 buffer_add( join_buf, " LEFT JOIN" );
2946 } else if( !strcasecmp( type,"right" )) {
2947 buffer_add( join_buf, " RIGHT JOIN" );
2948 } else if( !strcasecmp( type,"full" )) {
2949 buffer_add( join_buf, " FULL JOIN" );
2951 buffer_add( join_buf, " INNER JOIN" );
2954 buffer_add( join_buf, " INNER JOIN" );
2957 buffer_fadd( join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2958 table, right_alias, right_alias, field, left_info->alias, fkey );
2960 // Add any other join conditions as specified by "filter"
2961 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2963 const char* filter_op = jsonObjectGetString(
2964 jsonObjectGetKeyConst( snode, "filter_op" ) );
2965 if( filter_op && !strcasecmp( "or",filter_op )) {
2966 buffer_add( join_buf, " OR " );
2968 buffer_add( join_buf, " AND " );
2971 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2973 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2974 OSRF_BUFFER_ADD( join_buf, jpred );
2979 "%s: JOIN failed. Invalid conditional expression.",
2982 jsonIteratorFree( search_itr );
2983 buffer_free( join_buf );
2985 jsonObjectFree( freeable_hash );
2990 buffer_add( join_buf, " ) " );
2992 // Recursively add a nested join, if one is present
2993 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2995 char* jpred = searchJOIN( join_filter, right_info );
2997 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2998 OSRF_BUFFER_ADD( join_buf, jpred );
3001 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3002 jsonIteratorFree( search_itr );
3003 buffer_free( join_buf );
3005 jsonObjectFree( freeable_hash );
3012 jsonObjectFree( freeable_hash );
3013 jsonIteratorFree( search_itr );
3015 return buffer_release( join_buf );
3020 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3021 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3022 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3024 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3026 search_hash is the JSON expression of the conditions.
3027 meta is the class definition from the IDL, for the relevant table.
3028 opjoin_type indicates whether multiple conditions, if present, should be
3029 connected by AND or OR.
3030 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3031 to pass it to other functions -- and all they do with it is to use the session
3032 and request members to send error messages back to the client.
3036 static char* searchWHERE( const jsonObject* search_hash, const ClassInfo* class_info,
3037 int opjoin_type, osrfMethodContext* ctx ) {
3041 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3042 "opjoin_type = %d, ctx addr = %p",
3045 class_info->class_def,
3050 growing_buffer* sql_buf = buffer_init( 128 );
3052 jsonObject* node = NULL;
3055 if( search_hash->type == JSON_ARRAY ) {
3056 if( 0 == search_hash->size ) {
3059 "%s: Invalid predicate structure: empty JSON array",
3062 buffer_free( sql_buf );
3066 unsigned long i = 0;
3067 while(( node = jsonObjectGetIndex( search_hash, i++ ) )) {
3071 if( opjoin_type == OR_OP_JOIN )
3072 buffer_add( sql_buf, " OR " );
3074 buffer_add( sql_buf, " AND " );
3077 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3079 buffer_free( sql_buf );
3083 buffer_fadd( sql_buf, "( %s )", subpred );
3087 } else if( search_hash->type == JSON_HASH ) {
3088 osrfLogDebug( OSRF_LOG_MARK,
3089 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3090 jsonIterator* search_itr = jsonNewIterator( search_hash );
3091 if( !jsonIteratorHasNext( search_itr ) ) {
3094 "%s: Invalid predicate structure: empty JSON object",
3097 jsonIteratorFree( search_itr );
3098 buffer_free( sql_buf );
3102 while( (node = jsonIteratorNext( search_itr )) ) {
3107 if( opjoin_type == OR_OP_JOIN )
3108 buffer_add( sql_buf, " OR " );
3110 buffer_add( sql_buf, " AND " );
3113 if( '+' == search_itr->key[ 0 ] ) {
3115 // This plus sign prefixes a class name or other table alias;
3116 // make sure the table alias is in scope
3117 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3118 if( ! alias_info ) {
3121 "%s: Invalid table alias \"%s\" in WHERE clause",
3125 jsonIteratorFree( search_itr );
3126 buffer_free( sql_buf );
3130 if( node->type == JSON_STRING ) {
3131 // It's the name of a column; make sure it belongs to the class
3132 const char* fieldname = jsonObjectGetString( node );
3133 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3136 "%s: Invalid column name \"%s\" in WHERE clause "
3137 "for table alias \"%s\"",
3142 jsonIteratorFree( search_itr );
3143 buffer_free( sql_buf );
3147 buffer_fadd( sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3149 // It's something more complicated
3150 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3152 jsonIteratorFree( search_itr );
3153 buffer_free( sql_buf );
3157 buffer_fadd( sql_buf, "( %s )", subpred );
3160 } else if( '-' == search_itr->key[ 0 ] ) {
3161 if( !strcasecmp( "-or", search_itr->key )) {
3162 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3164 jsonIteratorFree( search_itr );
3165 buffer_free( sql_buf );
3169 buffer_fadd( sql_buf, "( %s )", subpred );
3171 } else if( !strcasecmp( "-and", search_itr->key )) {
3172 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3174 jsonIteratorFree( search_itr );
3175 buffer_free( sql_buf );
3179 buffer_fadd( sql_buf, "( %s )", subpred );
3181 } else if( !strcasecmp("-not",search_itr->key) ) {
3182 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3184 jsonIteratorFree( search_itr );
3185 buffer_free( sql_buf );
3189 buffer_fadd( sql_buf, " NOT ( %s )", subpred );
3191 } else if( !strcasecmp( "-exists", search_itr->key )) {
3192 char* subpred = buildQuery( ctx, node, SUBSELECT );
3194 jsonIteratorFree( search_itr );
3195 buffer_free( sql_buf );
3199 buffer_fadd( sql_buf, "EXISTS ( %s )", subpred );
3201 } else if( !strcasecmp("-not-exists", search_itr->key )) {
3202 char* subpred = buildQuery( ctx, node, SUBSELECT );
3204 jsonIteratorFree( search_itr );
3205 buffer_free( sql_buf );
3209 buffer_fadd( sql_buf, "NOT EXISTS ( %s )", subpred );
3211 } else { // Invalid "minus" operator
3214 "%s: Invalid operator \"%s\" in WHERE clause",
3218 jsonIteratorFree( search_itr );
3219 buffer_free( sql_buf );
3225 const char* class = class_info->class_name;
3226 osrfHash* fields = class_info->fields;
3227 osrfHash* field = osrfHashGet( fields, search_itr->key );
3230 const char* table = class_info->source_def;
3233 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3236 table ? table : "?",
3239 jsonIteratorFree( search_itr );
3240 buffer_free( sql_buf );
3244 char* subpred = searchPredicate( class_info, field, node, ctx );
3246 buffer_free( sql_buf );
3247 jsonIteratorFree( search_itr );
3251 buffer_add( sql_buf, subpred );
3255 jsonIteratorFree( search_itr );
3258 // ERROR ... only hash and array allowed at this level
3259 char* predicate_string = jsonObjectToJSON( search_hash );
3262 "%s: Invalid predicate structure: %s",
3266 buffer_free( sql_buf );
3267 free( predicate_string );
3271 return buffer_release( sql_buf );
3274 /* Build a JSON_ARRAY of field names for a given table alias
3276 static jsonObject* defaultSelectList( const char* table_alias ) {
3281 ClassInfo* class_info = search_all_alias( table_alias );
3282 if( ! class_info ) {
3285 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3292 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3293 osrfHash* field_def = NULL;
3294 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3295 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3296 const char* field_name = osrfHashIteratorKey( field_itr );
3297 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3298 jsonObjectPush( array, jsonNewObject( field_name ) );
3301 osrfHashIteratorFree( field_itr );
3306 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3307 // The jsonObject must be a JSON_HASH with an single entry for "union",
3308 // "intersect", or "except". The data associated with this key must be an
3309 // array of hashes, each hash being a query.
3310 // Also allowed but currently ignored: entries for "order_by" and "alias".
3311 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3313 if( ! combo || combo->type != JSON_HASH )
3314 return NULL; // should be impossible; validated by caller
3316 const jsonObject* query_array = NULL; // array of subordinate queries
3317 const char* op = NULL; // name of operator, e.g. UNION
3318 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3319 int op_count = 0; // for detecting conflicting operators
3320 int excepting = 0; // boolean
3321 int all = 0; // boolean
3322 jsonObject* order_obj = NULL;
3324 // Identify the elements in the hash
3325 jsonIterator* query_itr = jsonNewIterator( combo );
3326 jsonObject* curr_obj = NULL;
3327 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3328 if( ! strcmp( "union", query_itr->key ) ) {
3331 query_array = curr_obj;
3332 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3335 query_array = curr_obj;
3336 } else if( ! strcmp( "except", query_itr->key ) ) {
3340 query_array = curr_obj;
3341 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3344 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3347 order_obj = curr_obj;
3348 } else if( ! strcmp( "alias", query_itr->key ) ) {
3349 if( curr_obj->type != JSON_STRING ) {
3350 jsonIteratorFree( query_itr );
3353 alias = jsonObjectGetString( curr_obj );
3354 } else if( ! strcmp( "all", query_itr->key ) ) {
3355 if( obj_is_true( curr_obj ) )
3359 osrfAppSessionStatus(
3361 OSRF_STATUS_INTERNALSERVERERROR,
3362 "osrfMethodException",
3364 "Malformed query; unexpected entry in query object"
3368 "%s: Unexpected entry for \"%s\" in%squery",
3373 jsonIteratorFree( query_itr );
3377 jsonIteratorFree( query_itr );
3379 // More sanity checks
3380 if( ! query_array ) {
3382 osrfAppSessionStatus(
3384 OSRF_STATUS_INTERNALSERVERERROR,
3385 "osrfMethodException",
3387 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3391 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3394 return NULL; // should be impossible...
3395 } else if( op_count > 1 ) {
3397 osrfAppSessionStatus(
3399 OSRF_STATUS_INTERNALSERVERERROR,
3400 "osrfMethodException",
3402 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3406 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3410 } if( query_array->type != JSON_ARRAY ) {
3412 osrfAppSessionStatus(
3414 OSRF_STATUS_INTERNALSERVERERROR,
3415 "osrfMethodException",
3417 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3421 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3424 json_type( query_array->type )
3427 } if( query_array->size < 2 ) {
3429 osrfAppSessionStatus(
3431 OSRF_STATUS_INTERNALSERVERERROR,
3432 "osrfMethodException",
3434 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3438 "%s:%srequires multiple queries as operands",
3443 } else if( excepting && query_array->size > 2 ) {
3445 osrfAppSessionStatus(
3447 OSRF_STATUS_INTERNALSERVERERROR,
3448 "osrfMethodException",
3450 "EXCEPT operator has too many queries as operands"
3454 "%s:EXCEPT operator has too many queries as operands",
3458 } else if( order_obj && ! alias ) {
3460 osrfAppSessionStatus(
3462 OSRF_STATUS_INTERNALSERVERERROR,
3463 "osrfMethodException",
3465 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3469 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3475 // So far so good. Now build the SQL.
3476 growing_buffer* sql = buffer_init( 256 );
3478 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3479 // Add a layer of parentheses
3480 if( flags & SUBCOMBO )
3481 OSRF_BUFFER_ADD( sql, "( " );
3483 // Traverse the query array. Each entry should be a hash.
3484 int first = 1; // boolean
3486 jsonObject* query = NULL;
3487 while( (query = jsonObjectGetIndex( query_array, i++ )) ) {
3488 if( query->type != JSON_HASH ) {
3490 osrfAppSessionStatus(
3492 OSRF_STATUS_INTERNALSERVERERROR,
3493 "osrfMethodException",
3495 "Malformed query under UNION, INTERSECT or EXCEPT"
3499 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3502 json_type( query->type )
3511 OSRF_BUFFER_ADD( sql, op );
3513 OSRF_BUFFER_ADD( sql, "ALL " );
3516 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3520 "%s: Error building query under%s",
3528 OSRF_BUFFER_ADD( sql, query_str );
3531 if( flags & SUBCOMBO )
3532 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3534 if( !(flags & SUBSELECT) )
3535 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3537 return buffer_release( sql );
3540 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3541 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3542 // or "except" to indicate the type of query.
3543 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3547 osrfAppSessionStatus(
3549 OSRF_STATUS_INTERNALSERVERERROR,
3550 "osrfMethodException",
3552 "Malformed query; no query object"
3554 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3556 } else if( query->type != JSON_HASH ) {
3558 osrfAppSessionStatus(
3560 OSRF_STATUS_INTERNALSERVERERROR,
3561 "osrfMethodException",
3563 "Malformed query object"
3567 "%s: Query object is %s instead of JSON_HASH",
3569 json_type( query->type )
3574 // Determine what kind of query it purports to be, and dispatch accordingly.
3575 if( jsonObjectGetKey( query, "union" ) ||
3576 jsonObjectGetKey( query, "intersect" ) ||
3577 jsonObjectGetKey( query, "except" ) ) {
3578 return doCombo( ctx, query, flags );
3580 // It is presumably a SELECT query
3582 // Push a node onto the stack for the current query. Every level of
3583 // subquery gets its own QueryFrame on the Stack.
3586 // Build an SQL SELECT statement
3589 jsonObjectGetKey( query, "select" ),
3590 jsonObjectGetKey( query, "from" ),
3591 jsonObjectGetKey( query, "where" ),
3592 jsonObjectGetKey( query, "having" ),
3593 jsonObjectGetKey( query, "order_by" ),
3594 jsonObjectGetKey( query, "limit" ),
3595 jsonObjectGetKey( query, "offset" ),
3604 /* method context */ osrfMethodContext* ctx,
3606 /* SELECT */ jsonObject* selhash,
3607 /* FROM */ jsonObject* join_hash,
3608 /* WHERE */ jsonObject* search_hash,
3609 /* HAVING */ jsonObject* having_hash,
3610 /* ORDER BY */ jsonObject* order_hash,
3611 /* LIMIT */ jsonObject* limit,
3612 /* OFFSET */ jsonObject* offset,
3613 /* flags */ int flags
3615 const char* locale = osrf_message_get_last_locale();
3617 // general tmp objects
3618 const jsonObject* tmp_const;
3619 jsonObject* selclass = NULL;
3620 jsonObject* snode = NULL;
3621 jsonObject* onode = NULL;
3623 char* string = NULL;
3624 int from_function = 0;
3629 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3631 // punt if there's no FROM clause
3632 if( !join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3635 "%s: FROM clause is missing or empty",
3639 osrfAppSessionStatus(
3641 OSRF_STATUS_INTERNALSERVERERROR,
3642 "osrfMethodException",
3644 "FROM clause is missing or empty in JSON query"
3649 // the core search class
3650 const char* core_class = NULL;
3652 // get the core class -- the only key of the top level FROM clause, or a string
3653 if( join_hash->type == JSON_HASH ) {
3654 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3655 snode = jsonIteratorNext( tmp_itr );
3657 // Populate the current QueryFrame with information
3658 // about the core class
3659 if( add_query_core( NULL, tmp_itr->key ) ) {
3661 osrfAppSessionStatus(
3663 OSRF_STATUS_INTERNALSERVERERROR,
3664 "osrfMethodException",
3666 "Unable to look up core class"
3670 core_class = curr_query->core.class_name;
3673 jsonObject* extra = jsonIteratorNext( tmp_itr );
3675 jsonIteratorFree( tmp_itr );
3678 // There shouldn't be more than one entry in join_hash
3682 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3686 osrfAppSessionStatus(
3688 OSRF_STATUS_INTERNALSERVERERROR,
3689 "osrfMethodException",
3691 "Malformed FROM clause in JSON query"
3693 return NULL; // Malformed join_hash; extra entry
3695 } else if( join_hash->type == JSON_ARRAY ) {
3696 // We're selecting from a function, not from a table
3698 core_class = jsonObjectGetString( jsonObjectGetIndex( join_hash, 0 ));
3701 } else if( join_hash->type == JSON_STRING ) {
3702 // Populate the current QueryFrame with information
3703 // about the core class
3704 core_class = jsonObjectGetString( join_hash );
3706 if( add_query_core( NULL, core_class ) ) {
3708 osrfAppSessionStatus(
3710 OSRF_STATUS_INTERNALSERVERERROR,
3711 "osrfMethodException",
3713 "Unable to look up core class"
3721 "%s: FROM clause is unexpected JSON type: %s",
3723 json_type( join_hash->type )
3726 osrfAppSessionStatus(
3728 OSRF_STATUS_INTERNALSERVERERROR,
3729 "osrfMethodException",
3731 "Ill-formed FROM clause in JSON query"
3736 // Build the join clause, if any, while filling out the list
3737 // of joined classes in the current QueryFrame.
3738 char* join_clause = NULL;
3739 if( join_hash && ! from_function ) {
3741 join_clause = searchJOIN( join_hash, &curr_query->core );
3742 if( ! join_clause ) {
3744 osrfAppSessionStatus(
3746 OSRF_STATUS_INTERNALSERVERERROR,
3747 "osrfMethodException",
3749 "Unable to construct JOIN clause(s)"
3755 // For in case we don't get a select list
3756 jsonObject* defaultselhash = NULL;
3758 // if there is no select list, build a default select list ...
3759 if( !selhash && !from_function ) {
3760 jsonObject* default_list = defaultSelectList( core_class );
3761 if( ! default_list ) {
3763 osrfAppSessionStatus(
3765 OSRF_STATUS_INTERNALSERVERERROR,
3766 "osrfMethodException",
3768 "Unable to build default SELECT clause in JSON query"
3770 free( join_clause );
3775 selhash = defaultselhash = jsonNewObjectType( JSON_HASH );
3776 jsonObjectSetKey( selhash, core_class, default_list );
3779 // The SELECT clause can be encoded only by a hash
3780 if( !from_function && selhash->type != JSON_HASH ) {
3783 "%s: Expected JSON_HASH for SELECT clause; found %s",
3785 json_type( selhash->type )
3789 osrfAppSessionStatus(
3791 OSRF_STATUS_INTERNALSERVERERROR,
3792 "osrfMethodException",
3794 "Malformed SELECT clause in JSON query"
3796 free( join_clause );
3800 // If you see a null or wild card specifier for the core class, or an
3801 // empty array, replace it with a default SELECT list
3802 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3804 int default_needed = 0; // boolean
3805 if( JSON_STRING == tmp_const->type
3806 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3808 else if( JSON_NULL == tmp_const->type )
3811 if( default_needed ) {
3812 // Build a default SELECT list
3813 jsonObject* default_list = defaultSelectList( core_class );
3814 if( ! default_list ) {
3816 osrfAppSessionStatus(
3818 OSRF_STATUS_INTERNALSERVERERROR,
3819 "osrfMethodException",
3821 "Can't build default SELECT clause in JSON query"
3823 free( join_clause );
3828 jsonObjectSetKey( selhash, core_class, default_list );
3832 // temp buffers for the SELECT list and GROUP BY clause
3833 growing_buffer* select_buf = buffer_init( 128 );
3834 growing_buffer* group_buf = buffer_init( 128 );
3836 int aggregate_found = 0; // boolean
3838 // Build a select list
3839 if( from_function ) // From a function we select everything
3840 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3843 // Build the SELECT list as SQL
3847 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3848 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3850 const char* cname = selclass_itr->key;
3852 // Make sure the target relation is in the FROM clause.
3854 // At this point join_hash is a step down from the join_hash we
3855 // received as a parameter. If the original was a JSON_STRING,
3856 // then json_hash is now NULL. If the original was a JSON_HASH,
3857 // then json_hash is now the first (and only) entry in it,
3858 // denoting the core class. We've already excluded the
3859 // possibility that the original was a JSON_ARRAY, because in
3860 // that case from_function would be non-NULL, and we wouldn't
3863 // If the current table alias isn't in scope, bail out
3864 ClassInfo* class_info = search_alias( cname );
3865 if( ! class_info ) {
3868 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3873 osrfAppSessionStatus(
3875 OSRF_STATUS_INTERNALSERVERERROR,
3876 "osrfMethodException",
3878 "Selected class not in FROM clause in JSON query"
3880 jsonIteratorFree( selclass_itr );
3881 buffer_free( select_buf );
3882 buffer_free( group_buf );
3883 if( defaultselhash )
3884 jsonObjectFree( defaultselhash );
3885 free( join_clause );
3889 if( selclass->type != JSON_ARRAY ) {
3892 "%s: Malformed SELECT list for class \"%s\"; not an array",
3897 osrfAppSessionStatus(
3899 OSRF_STATUS_INTERNALSERVERERROR,
3900 "osrfMethodException",
3902 "Selected class not in FROM clause in JSON query"
3905 jsonIteratorFree( selclass_itr );
3906 buffer_free( select_buf );
3907 buffer_free( group_buf );
3908 if( defaultselhash )
3909 jsonObjectFree( defaultselhash );
3910 free( join_clause );
3914 // Look up some attributes of the current class
3915 osrfHash* idlClass = class_info->class_def;
3916 osrfHash* class_field_set = class_info->fields;
3917 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3918 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3920 if( 0 == selclass->size ) {
3923 "%s: No columns selected from \"%s\"",
3929 // stitch together the column list for the current table alias...
3930 unsigned long field_idx = 0;
3931 jsonObject* selfield = NULL;
3932 while(( selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3934 // If we need a separator comma, add one
3938 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3941 // if the field specification is a string, add it to the list
3942 if( selfield->type == JSON_STRING ) {
3944 // Look up the field in the IDL
3945 const char* col_name = jsonObjectGetString( selfield );
3946 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3948 // No such field in current class
3951 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3957 osrfAppSessionStatus(
3959 OSRF_STATUS_INTERNALSERVERERROR,
3960 "osrfMethodException",
3962 "Selected column not defined in JSON query"
3964 jsonIteratorFree( selclass_itr );
3965 buffer_free( select_buf );
3966 buffer_free( group_buf );
3967 if( defaultselhash )
3968 jsonObjectFree( defaultselhash );
3969 free( join_clause );
3971 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3972 // Virtual field not allowed
3975 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3981 osrfAppSessionStatus(
3983 OSRF_STATUS_INTERNALSERVERERROR,
3984 "osrfMethodException",
3986 "Selected column may not be virtual in JSON query"
3988 jsonIteratorFree( selclass_itr );
3989 buffer_free( select_buf );
3990 buffer_free( group_buf );
3991 if( defaultselhash )
3992 jsonObjectFree( defaultselhash );
3993 free( join_clause );
3999 if( flags & DISABLE_I18N )
4002 i18n = osrfHashGet( field_def, "i18n" );
4004 if( str_is_true( i18n ) ) {
4005 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4006 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4007 class_tname, cname, col_name, class_pkey,
4008 cname, class_pkey, locale, col_name );
4010 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4011 cname, col_name, col_name );
4014 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4015 cname, col_name, col_name );
4018 // ... but it could be an object, in which case we check for a Field Transform
4019 } else if( selfield->type == JSON_HASH ) {
4021 const char* col_name = jsonObjectGetString(
4022 jsonObjectGetKeyConst( selfield, "column" ) );
4024 // Get the field definition from the IDL
4025 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4027 // No such field in current class
4030 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4036 osrfAppSessionStatus(
4038 OSRF_STATUS_INTERNALSERVERERROR,
4039 "osrfMethodException",
4041 "Selected column is not defined in JSON query"
4043 jsonIteratorFree( selclass_itr );
4044 buffer_free( select_buf );
4045 buffer_free( group_buf );
4046 if( defaultselhash )
4047 jsonObjectFree( defaultselhash );
4048 free( join_clause );
4050 } else if( str_is_true( osrfHashGet( field_def, "virtual" ))) {
4051 // No such field in current class
4054 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4060 osrfAppSessionStatus(
4062 OSRF_STATUS_INTERNALSERVERERROR,
4063 "osrfMethodException",
4065 "Selected column is virtual in JSON query"
4067 jsonIteratorFree( selclass_itr );
4068 buffer_free( select_buf );
4069 buffer_free( group_buf );
4070 if( defaultselhash )
4071 jsonObjectFree( defaultselhash );
4072 free( join_clause );
4076 // Decide what to use as a column alias
4078 if((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4079 _alias = jsonObjectGetString( tmp_const );
4080 } else { // Use field name as the alias
4084 if( jsonObjectGetKeyConst( selfield, "transform" )) {
4085 char* transform_str = searchFieldTransform(
4086 class_info->alias, field_def, selfield );
4087 if( transform_str ) {
4088 buffer_fadd( select_buf, " %s AS \"%s\"", transform_str, _alias );
4089 free( transform_str );
4092 osrfAppSessionStatus(
4094 OSRF_STATUS_INTERNALSERVERERROR,
4095 "osrfMethodException",
4097 "Unable to generate transform function in JSON query"
4099 jsonIteratorFree( selclass_itr );
4100 buffer_free( select_buf );
4101 buffer_free( group_buf );
4102 if( defaultselhash )
4103 jsonObjectFree( defaultselhash );
4104 free( join_clause );
4111 if( flags & DISABLE_I18N )
4114 i18n = osrfHashGet( field_def, "i18n" );
4116 if( str_is_true( i18n ) ) {
4117 buffer_fadd( select_buf,
4118 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4119 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4120 class_tname, cname, col_name, class_pkey, cname,
4121 class_pkey, locale, _alias );
4123 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4124 cname, col_name, _alias );
4127 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4128 cname, col_name, _alias );
4135 "%s: Selected item is unexpected JSON type: %s",
4137 json_type( selfield->type )
4140 osrfAppSessionStatus(
4142 OSRF_STATUS_INTERNALSERVERERROR,
4143 "osrfMethodException",
4145 "Ill-formed SELECT item in JSON query"
4147 jsonIteratorFree( selclass_itr );
4148 buffer_free( select_buf );
4149 buffer_free( group_buf );
4150 if( defaultselhash )
4151 jsonObjectFree( defaultselhash );
4152 free( join_clause );
4156 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
4157 if( obj_is_true( agg_obj ) )
4158 aggregate_found = 1;
4160 // Append a comma (except for the first one)
4161 // and add the column to a GROUP BY clause
4165 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4167 buffer_fadd( group_buf, " %d", sel_pos );
4171 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4173 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4174 if ( ! obj_is_true( aggregate_obj ) ) {
4178 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4181 buffer_fadd(group_buf, " %d", sel_pos);
4184 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4188 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4191 _column = searchFieldTransform(class_info->alias, field, selfield);
4192 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4193 OSRF_BUFFER_ADD(group_buf, _column);
4194 _column = searchFieldTransform(class_info->alias, field, selfield);
4201 } // end while -- iterating across SELECT columns
4203 } // end while -- iterating across classes
4205 jsonIteratorFree( selclass_itr );
4209 char* col_list = buffer_release( select_buf );
4211 // Make sure the SELECT list isn't empty. This can happen, for example,
4212 // if we try to build a default SELECT clause from a non-core table.
4215 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4217 osrfAppSessionStatus(
4219 OSRF_STATUS_INTERNALSERVERERROR,
4220 "osrfMethodException",
4222 "SELECT list is empty"
4225 buffer_free( group_buf );
4226 if( defaultselhash )
4227 jsonObjectFree( defaultselhash );
4228 free( join_clause );
4234 table = searchValueTransform( join_hash );
4236 table = strdup( curr_query->core.source_def );
4240 osrfAppSessionStatus(
4242 OSRF_STATUS_INTERNALSERVERERROR,
4243 "osrfMethodException",
4245 "Unable to identify table for core class"
4248 buffer_free( group_buf );
4249 if( defaultselhash )
4250 jsonObjectFree( defaultselhash );
4251 free( join_clause );
4255 // Put it all together
4256 growing_buffer* sql_buf = buffer_init( 128 );
4257 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4261 // Append the join clause, if any
4263 buffer_add(sql_buf, join_clause );
4264 free( join_clause );
4267 char* order_by_list = NULL;
4268 char* having_buf = NULL;
4270 if( !from_function ) {
4272 // Build a WHERE clause, if there is one
4274 buffer_add( sql_buf, " WHERE " );
4276 // and it's on the WHERE clause
4277 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4280 osrfAppSessionStatus(
4282 OSRF_STATUS_INTERNALSERVERERROR,
4283 "osrfMethodException",
4285 "Severe query error in WHERE predicate -- see error log for more details"
4288 buffer_free( group_buf );
4289 buffer_free( sql_buf );
4290 if( defaultselhash )
4291 jsonObjectFree( defaultselhash );
4295 buffer_add( sql_buf, pred );
4299 // Build a HAVING clause, if there is one
4302 // and it's on the the WHERE clause
4303 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4305 if( ! having_buf ) {
4307 osrfAppSessionStatus(
4309 OSRF_STATUS_INTERNALSERVERERROR,
4310 "osrfMethodException",
4312 "Severe query error in HAVING predicate -- see error log for more details"
4315 buffer_free( group_buf );
4316 buffer_free( sql_buf );
4317 if( defaultselhash )
4318 jsonObjectFree( defaultselhash );
4323 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4325 // Build an ORDER BY clause, if there is one
4326 if( NULL == order_hash )
4327 ; // No ORDER BY? do nothing
4328 else if( JSON_ARRAY == order_hash->type ) {
4329 // Array of field specifications, each specification being a
4330 // hash to define the class, field, and other details
4332 jsonObject* order_spec;
4333 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4335 if( JSON_HASH != order_spec->type ) {
4336 osrfLogError( OSRF_LOG_MARK,
4337 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4338 modulename, json_type( order_spec->type ) );
4340 osrfAppSessionStatus(
4342 OSRF_STATUS_INTERNALSERVERERROR,
4343 "osrfMethodException",
4345 "Malformed ORDER BY clause -- see error log for more details"
4347 buffer_free( order_buf );
4349 buffer_free( group_buf );
4350 buffer_free( sql_buf );
4351 if( defaultselhash )
4352 jsonObjectFree( defaultselhash );
4356 const char* class_alias =
4357 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4359 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4362 OSRF_BUFFER_ADD( order_buf, ", " );
4364 order_buf = buffer_init( 128 );
4366 if( !field || !class_alias ) {
4367 osrfLogError( OSRF_LOG_MARK,
4368 "%s: Missing class or field name in field specification "
4369 "of ORDER BY clause",
4372 osrfAppSessionStatus(
4374 OSRF_STATUS_INTERNALSERVERERROR,
4375 "osrfMethodException",
4377 "Malformed ORDER BY clause -- see error log for more details"
4379 buffer_free( order_buf );
4381 buffer_free( group_buf );
4382 buffer_free( sql_buf );
4383 if( defaultselhash )
4384 jsonObjectFree( defaultselhash );
4388 ClassInfo* order_class_info = search_alias( class_alias );
4389 if( ! order_class_info ) {
4390 osrfLogError( OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4391 "not in FROM clause", modulename, class_alias );
4393 osrfAppSessionStatus(
4395 OSRF_STATUS_INTERNALSERVERERROR,
4396 "osrfMethodException",
4398 "Invalid class referenced in ORDER BY clause -- "
4399 "see error log for more details"
4402 buffer_free( group_buf );
4403 buffer_free( sql_buf );
4404 if( defaultselhash )
4405 jsonObjectFree( defaultselhash );
4409 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4411 osrfLogError( OSRF_LOG_MARK,
4412 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4413 modulename, class_alias, field );
4415 osrfAppSessionStatus(
4417 OSRF_STATUS_INTERNALSERVERERROR,
4418 "osrfMethodException",
4420 "Invalid field referenced in ORDER BY clause -- "
4421 "see error log for more details"
4424 buffer_free( group_buf );
4425 buffer_free( sql_buf );
4426 if( defaultselhash )
4427 jsonObjectFree( defaultselhash );
4429 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4430 osrfLogError( OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4431 modulename, field );
4433 osrfAppSessionStatus(
4435 OSRF_STATUS_INTERNALSERVERERROR,
4436 "osrfMethodException",
4438 "Virtual field in ORDER BY clause -- see error log for more details"
4440 buffer_free( order_buf );
4442 buffer_free( group_buf );
4443 buffer_free( sql_buf );
4444 if( defaultselhash )
4445 jsonObjectFree( defaultselhash );
4449 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4450 char* transform_str = searchFieldTransform(
4451 class_alias, field_def, order_spec );
4452 if( ! transform_str ) {
4454 osrfAppSessionStatus(
4456 OSRF_STATUS_INTERNALSERVERERROR,
4457 "osrfMethodException",
4459 "Severe query error in ORDER BY clause -- "
4460 "see error log for more details"
4462 buffer_free( order_buf );
4464 buffer_free( group_buf );
4465 buffer_free( sql_buf );
4466 if( defaultselhash )
4467 jsonObjectFree( defaultselhash );
4471 OSRF_BUFFER_ADD( order_buf, transform_str );
4472 free( transform_str );
4475 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4477 const char* direction =
4478 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4480 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4481 OSRF_BUFFER_ADD( order_buf, " DESC" );
4483 OSRF_BUFFER_ADD( order_buf, " ASC" );
4486 } else if( JSON_HASH == order_hash->type ) {
4487 // This hash is keyed on class alias. Each class has either
4488 // an array of field names or a hash keyed on field name.
4489 jsonIterator* class_itr = jsonNewIterator( order_hash );
4490 while( (snode = jsonIteratorNext( class_itr )) ) {
4492 ClassInfo* order_class_info = search_alias( class_itr->key );
4493 if( ! order_class_info ) {
4494 osrfLogError( OSRF_LOG_MARK,
4495 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4496 modulename, class_itr->key );
4498 osrfAppSessionStatus(
4500 OSRF_STATUS_INTERNALSERVERERROR,
4501 "osrfMethodException",
4503 "Invalid class referenced in ORDER BY clause -- "
4504 "see error log for more details"
4506 jsonIteratorFree( class_itr );
4507 buffer_free( order_buf );
4509 buffer_free( group_buf );
4510 buffer_free( sql_buf );
4511 if( defaultselhash )
4512 jsonObjectFree( defaultselhash );
4516 osrfHash* field_list_def = order_class_info->fields;
4518 if( snode->type == JSON_HASH ) {
4520 // Hash is keyed on field names from the current class. For each field
4521 // there is another layer of hash to define the sorting details, if any,
4522 // or a string to indicate direction of sorting.
4523 jsonIterator* order_itr = jsonNewIterator( snode );
4524 while( (onode = jsonIteratorNext( order_itr )) ) {
4526 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4528 osrfLogError( OSRF_LOG_MARK,
4529 "%s: Invalid field \"%s\" in ORDER BY clause",
4530 modulename, order_itr->key );
4532 osrfAppSessionStatus(
4534 OSRF_STATUS_INTERNALSERVERERROR,
4535 "osrfMethodException",
4537 "Invalid field in ORDER BY clause -- "
4538 "see error log for more details"
4540 jsonIteratorFree( order_itr );
4541 jsonIteratorFree( class_itr );
4542 buffer_free( order_buf );
4544 buffer_free( group_buf );
4545 buffer_free( sql_buf );
4546 if( defaultselhash )
4547 jsonObjectFree( defaultselhash );
4549 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4550 osrfLogError( OSRF_LOG_MARK,
4551 "%s: Virtual field \"%s\" in ORDER BY clause",
4552 modulename, order_itr->key );
4554 osrfAppSessionStatus(
4556 OSRF_STATUS_INTERNALSERVERERROR,
4557 "osrfMethodException",
4559 "Virtual field in ORDER BY clause -- "
4560 "see error log for more details"
4562 jsonIteratorFree( order_itr );
4563 jsonIteratorFree( class_itr );
4564 buffer_free( order_buf );
4566 buffer_free( group_buf );
4567 buffer_free( sql_buf );
4568 if( defaultselhash )
4569 jsonObjectFree( defaultselhash );
4573 const char* direction = NULL;
4574 if( onode->type == JSON_HASH ) {
4575 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
4576 string = searchFieldTransform(
4578 osrfHashGet( field_list_def, order_itr->key ),
4582 if( ctx ) osrfAppSessionStatus(
4584 OSRF_STATUS_INTERNALSERVERERROR,
4585 "osrfMethodException",
4587 "Severe query error in ORDER BY clause -- "
4588 "see error log for more details"
4590 jsonIteratorFree( order_itr );
4591 jsonIteratorFree( class_itr );
4593 buffer_free( group_buf );
4594 buffer_free( order_buf);
4595 buffer_free( sql_buf );
4596 if( defaultselhash )
4597 jsonObjectFree( defaultselhash );
4601 growing_buffer* field_buf = buffer_init( 16 );
4602 buffer_fadd( field_buf, "\"%s\".%s",
4603 class_itr->key, order_itr->key );
4604 string = buffer_release( field_buf );
4607 if( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4608 const char* dir = jsonObjectGetString( tmp_const );
4609 if(!strncasecmp( dir, "d", 1 )) {
4610 direction = " DESC";
4616 } else if( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4617 osrfLogError( OSRF_LOG_MARK,
4618 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4619 modulename, json_type( onode->type ) );
4621 osrfAppSessionStatus(
4623 OSRF_STATUS_INTERNALSERVERERROR,
4624 "osrfMethodException",
4626 "Malformed ORDER BY clause -- see error log for more details"
4628 jsonIteratorFree( order_itr );
4629 jsonIteratorFree( class_itr );
4631 buffer_free( group_buf );
4632 buffer_free( order_buf );
4633 buffer_free( sql_buf );
4634 if( defaultselhash )
4635 jsonObjectFree( defaultselhash );
4639 string = strdup( order_itr->key );
4640 const char* dir = jsonObjectGetString( onode );
4641 if( !strncasecmp( dir, "d", 1 )) {
4642 direction = " DESC";
4649 OSRF_BUFFER_ADD( order_buf, ", " );
4651 order_buf = buffer_init( 128 );
4653 OSRF_BUFFER_ADD( order_buf, string );
4657 OSRF_BUFFER_ADD( order_buf, direction );
4661 jsonIteratorFree( order_itr );
4663 } else if( snode->type == JSON_ARRAY ) {
4665 // Array is a list of fields from the current class
4666 unsigned long order_idx = 0;
4667 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4669 const char* _f = jsonObjectGetString( onode );
4671 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4673 osrfLogError( OSRF_LOG_MARK,
4674 "%s: Invalid field \"%s\" in ORDER BY clause",
4677 osrfAppSessionStatus(
4679 OSRF_STATUS_INTERNALSERVERERROR,
4680 "osrfMethodException",
4682 "Invalid field in ORDER BY clause -- "
4683 "see error log for more details"
4685 jsonIteratorFree( class_itr );
4686 buffer_free( order_buf );
4688 buffer_free( group_buf );
4689 buffer_free( sql_buf );
4690 if( defaultselhash )
4691 jsonObjectFree( defaultselhash );
4693 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4694 osrfLogError( OSRF_LOG_MARK,
4695 "%s: Virtual field \"%s\" in ORDER BY clause",
4698 osrfAppSessionStatus(
4700 OSRF_STATUS_INTERNALSERVERERROR,
4701 "osrfMethodException",
4703 "Virtual field in ORDER BY clause -- "
4704 "see error log for more details"
4706 jsonIteratorFree( class_itr );
4707 buffer_free( order_buf );
4709 buffer_free( group_buf );
4710 buffer_free( sql_buf );
4711 if( defaultselhash )
4712 jsonObjectFree( defaultselhash );
4717 OSRF_BUFFER_ADD( order_buf, ", " );
4719 order_buf = buffer_init( 128 );
4721 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f );
4725 // IT'S THE OOOOOOOOOOOLD STYLE!
4727 osrfLogError( OSRF_LOG_MARK,
4728 "%s: Possible SQL injection attempt; direct order by is not allowed",
4731 osrfAppSessionStatus(
4733 OSRF_STATUS_INTERNALSERVERERROR,
4734 "osrfMethodException",
4736 "Severe query error -- see error log for more details"
4741 buffer_free( group_buf );
4742 buffer_free( order_buf );
4743 buffer_free( sql_buf );
4744 if( defaultselhash )
4745 jsonObjectFree( defaultselhash );
4746 jsonIteratorFree( class_itr );
4750 jsonIteratorFree( class_itr );
4752 osrfLogError( OSRF_LOG_MARK,
4753 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4754 modulename, json_type( order_hash->type ) );
4756 osrfAppSessionStatus(
4758 OSRF_STATUS_INTERNALSERVERERROR,
4759 "osrfMethodException",
4761 "Malformed ORDER BY clause -- see error log for more details"
4763 buffer_free( order_buf );
4765 buffer_free( group_buf );
4766 buffer_free( sql_buf );
4767 if( defaultselhash )
4768 jsonObjectFree( defaultselhash );
4773 order_by_list = buffer_release( order_buf );
4777 string = buffer_release( group_buf );
4779 if( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4780 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4781 OSRF_BUFFER_ADD( sql_buf, string );
4786 if( having_buf && *having_buf ) {
4787 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4788 OSRF_BUFFER_ADD( sql_buf, having_buf );
4792 if( order_by_list ) {
4794 if( *order_by_list ) {
4795 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4796 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4799 free( order_by_list );
4803 const char* str = jsonObjectGetString( limit );
4804 buffer_fadd( sql_buf, " LIMIT %d", atoi( str ));
4808 const char* str = jsonObjectGetString( offset );
4809 buffer_fadd( sql_buf, " OFFSET %d", atoi( str ));
4812 if( !(flags & SUBSELECT) )
4813 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
4815 if( defaultselhash )
4816 jsonObjectFree( defaultselhash );
4818 return buffer_release( sql_buf );
4820 } // end of SELECT()
4822 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4824 const char* locale = osrf_message_get_last_locale();
4826 osrfHash* fields = osrfHashGet( meta, "fields" );
4827 char* core_class = osrfHashGet( meta, "classname" );
4829 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4831 jsonObject* node = NULL;
4832 jsonObject* snode = NULL;
4833 jsonObject* onode = NULL;
4834 const jsonObject* _tmp = NULL;
4835 jsonObject* selhash = NULL;
4836 jsonObject* defaultselhash = NULL;
4838 growing_buffer* sql_buf = buffer_init( 128 );
4839 growing_buffer* select_buf = buffer_init( 128 );
4841 if( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4842 defaultselhash = jsonNewObjectType( JSON_HASH );
4843 selhash = defaultselhash;
4846 // If there's no SELECT list for the core class, build one
4847 if( !jsonObjectGetKeyConst( selhash, core_class ) ) {
4848 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4850 // Add every non-virtual field to the field list
4851 osrfHash* field_def = NULL;
4852 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4853 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4854 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4855 const char* field = osrfHashIteratorKey( field_itr );
4856 jsonObjectPush( field_list, jsonNewObject( field ) );
4859 osrfHashIteratorFree( field_itr );
4860 jsonObjectSetKey( selhash, core_class, field_list );
4864 jsonIterator* class_itr = jsonNewIterator( selhash );
4865 while( (snode = jsonIteratorNext( class_itr )) ) {
4867 const char* cname = class_itr->key;
4868 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4872 if( strcmp(core_class,class_itr->key )) {
4876 jsonObject* found = jsonObjectFindPath( join_hash, "//%s", class_itr->key );
4877 if( !found->size ) {
4878 jsonObjectFree( found );
4882 jsonObjectFree( found );
4885 jsonIterator* select_itr = jsonNewIterator( snode );
4886 while( (node = jsonIteratorNext( select_itr )) ) {
4887 const char* item_str = jsonObjectGetString( node );
4888 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4889 char* fname = osrfHashGet( field, "name" );
4897 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4902 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4903 if( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4906 i18n = osrfHashGet( field, "i18n" );
4908 if( str_is_true( i18n ) ) {
4909 char* pkey = osrfHashGet( idlClass, "primarykey" );
4910 char* tname = osrfHashGet( idlClass, "tablename" );
4912 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4913 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4914 tname, cname, fname, pkey, cname, pkey, locale, fname );
4916 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4919 buffer_fadd( select_buf, " \"%s\".%s", cname, fname );
4923 jsonIteratorFree( select_itr );
4926 jsonIteratorFree( class_itr );
4928 char* col_list = buffer_release( select_buf );
4929 char* table = oilsGetRelation( meta );
4931 table = strdup( "(null)" );
4933 buffer_fadd( sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4937 // Clear the query stack (as a fail-safe precaution against possible
4938 // leftover garbage); then push the first query frame onto the stack.
4939 clear_query_stack();
4941 if( add_query_core( NULL, core_class ) ) {
4943 osrfAppSessionStatus(
4945 OSRF_STATUS_INTERNALSERVERERROR,
4946 "osrfMethodException",
4948 "Unable to build query frame for core class"
4954 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4955 OSRF_BUFFER_ADD_CHAR( sql_buf, ' ' );
4956 OSRF_BUFFER_ADD( sql_buf, join_clause );
4957 free( join_clause );
4960 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4961 modulename, OSRF_BUFFER_C_STR( sql_buf ));
4963 OSRF_BUFFER_ADD( sql_buf, " WHERE " );
4965 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4967 osrfAppSessionStatus(
4969 OSRF_STATUS_INTERNALSERVERERROR,
4970 "osrfMethodException",
4972 "Severe query error -- see error log for more details"
4974 buffer_free( sql_buf );
4975 if( defaultselhash )
4976 jsonObjectFree( defaultselhash );
4977 clear_query_stack();
4980 buffer_add( sql_buf, pred );
4985 char* string = NULL;
4986 if( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4988 growing_buffer* order_buf = buffer_init( 128 );
4991 jsonIterator* class_itr = jsonNewIterator( _tmp );
4992 while( (snode = jsonIteratorNext( class_itr )) ) {
4994 if( !jsonObjectGetKeyConst( selhash,class_itr->key ))
4997 if( snode->type == JSON_HASH ) {
4999 jsonIterator* order_itr = jsonNewIterator( snode );
5000 while( (onode = jsonIteratorNext( order_itr )) ) {
5002 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
5003 class_itr->key, order_itr->key );
5007 char* direction = NULL;
5008 if( onode->type == JSON_HASH ) {
5009 if( jsonObjectGetKeyConst( onode, "transform" ) ) {
5010 string = searchFieldTransform( class_itr->key, field_def, onode );
5012 osrfAppSessionStatus(
5014 OSRF_STATUS_INTERNALSERVERERROR,
5015 "osrfMethodException",
5017 "Severe query error in ORDER BY clause -- "
5018 "see error log for more details"
5020 jsonIteratorFree( order_itr );
5021 jsonIteratorFree( class_itr );
5022 buffer_free( order_buf );
5023 buffer_free( sql_buf );
5024 if( defaultselhash )
5025 jsonObjectFree( defaultselhash );
5026 clear_query_stack();
5030 growing_buffer* field_buf = buffer_init( 16 );
5031 buffer_fadd( field_buf, "\"%s\".%s",
5032 class_itr->key, order_itr->key );
5033 string = buffer_release( field_buf );
5036 if( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
5037 const char* dir = jsonObjectGetString( _tmp );
5038 if(!strncasecmp( dir, "d", 1 )) {
5039 direction = " DESC";
5043 string = strdup( order_itr->key );
5044 const char* dir = jsonObjectGetString( onode );
5045 if( !strncasecmp( dir, "d", 1 )) {
5046 direction = " DESC";
5055 buffer_add( order_buf, ", " );
5058 buffer_add( order_buf, string );
5062 buffer_add( order_buf, direction );
5066 jsonIteratorFree( order_itr );
5069 const char* str = jsonObjectGetString( snode );
5070 buffer_add( order_buf, str );
5076 jsonIteratorFree( class_itr );
5078 string = buffer_release( order_buf );
5081 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5082 OSRF_BUFFER_ADD( sql_buf, string );
5088 if( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ) {
5089 const char* str = jsonObjectGetString( _tmp );
5097 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
5099 const char* str = jsonObjectGetString( _tmp );
5108 if( defaultselhash )
5109 jsonObjectFree( defaultselhash );
5110 clear_query_stack();
5112 OSRF_BUFFER_ADD_CHAR( sql_buf, ';' );
5113 return buffer_release( sql_buf );
5116 int doJSONSearch ( osrfMethodContext* ctx ) {
5117 if(osrfMethodVerifyContext( ctx )) {
5118 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5122 osrfLogDebug( OSRF_LOG_MARK, "Received query request" );
5127 dbhandle = writehandle;
5129 jsonObject* hash = jsonObjectGetIndex( ctx->params, 0 );
5133 if( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
5134 flags |= SELECT_DISTINCT;
5136 if( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
5137 flags |= DISABLE_I18N;
5139 osrfLogDebug( OSRF_LOG_MARK, "Building SQL ..." );
5140 clear_query_stack(); // a possibly needless precaution
5141 char* sql = buildQuery( ctx, hash, flags );
5142 clear_query_stack();
5149 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5150 dbi_result result = dbi_conn_query( dbhandle, sql );
5153 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5155 if( dbi_result_first_row( result )) {
5156 /* JSONify the result */
5157 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5160 jsonObject* return_val = oilsMakeJSONFromResult( result );
5161 osrfAppRespond( ctx, return_val );
5162 jsonObjectFree( return_val );
5163 } while( dbi_result_next_row( result ));
5166 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql );
5169 osrfAppRespondComplete( ctx, NULL );
5171 /* clean up the query */
5172 dbi_result_free( result );
5177 int errnum = dbi_conn_error( dbhandle, &msg );
5178 osrfLogError( OSRF_LOG_MARK, "%s: Error with query [%s]: %d %s",
5179 modulename, sql, errnum, msg ? msg : "(No description available)" );
5180 osrfAppSessionStatus(
5182 OSRF_STATUS_INTERNALSERVERERROR,
5183 "osrfMethodException",
5185 "Severe query error -- see error log for more details"
5193 // The last parameter, err, is used to report an error condition by updating an int owned by
5194 // the calling code.
5196 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5197 // It is the responsibility of the calling code to initialize *err before the
5198 // call, so that it will be able to make sense of the result.
5200 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5201 // redundant anyway.
5202 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
5203 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5206 dbhandle = writehandle;
5208 char* core_class = osrfHashGet( class_meta, "classname" );
5209 char* pkey = osrfHashGet( class_meta, "primarykey" );
5211 const jsonObject* _tmp;
5213 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5215 osrfLogDebug( OSRF_LOG_MARK, "Problem building query, returning NULL" );
5220 osrfLogDebug( OSRF_LOG_MARK, "%s SQL = %s", modulename, sql );
5222 dbi_result result = dbi_conn_query( dbhandle, sql );
5223 if( NULL == result ) {
5225 int errnum = dbi_conn_error( dbhandle, &msg );
5226 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]: %d %s",
5227 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql, errnum,
5228 msg ? msg : "(No description available)" );
5229 osrfAppSessionStatus(
5231 OSRF_STATUS_INTERNALSERVERERROR,
5232 "osrfMethodException",
5234 "Severe query error -- see error log for more details"
5241 osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
5244 jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
5245 jsonObject* row_obj = NULL;
5247 if( dbi_result_first_row( result )) {
5249 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5250 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5251 // eliminate the duplicates.
5252 osrfLogDebug( OSRF_LOG_MARK, "Query returned at least one row" );
5253 osrfHash* dedup = osrfNewHash();
5255 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5256 char* pkey_val = oilsFMGetString( row_obj, pkey );
5257 if( osrfHashGet( dedup, pkey_val ) ) {
5258 jsonObjectFree( row_obj );
5261 osrfHashSet( dedup, pkey_val, pkey_val );
5262 jsonObjectPush( res_list, row_obj );
5264 } while( dbi_result_next_row( result ));
5265 osrfHashFree( dedup );
5268 osrfLogDebug( OSRF_LOG_MARK, "%s returned no results for query %s",
5272 /* clean up the query */
5273 dbi_result_free( result );
5276 // If we're asked to flesh, and there's anything to flesh, then flesh it
5277 // (but not for PCRUD, lest the user to bypass permissions by fleshing
5278 // something that he has no permission to look at).
5279 if( res_list->size && query_hash && ! enforce_pcrud ) {
5280 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5282 // Get the flesh depth
5283 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5284 if( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5285 flesh_depth = max_flesh_depth;
5287 // We need a non-zero flesh depth, and a list of fields to flesh
5288 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5289 if( temp_blob && flesh_depth > 0 ) {
5291 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5292 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5294 osrfStringArray* link_fields = NULL;
5295 osrfHash* links = osrfHashGet( class_meta, "links" );
5297 // Make an osrfStringArray of the names of fields to be fleshed
5298 if( flesh_fields ) {
5299 if( flesh_fields->size == 1 ) {
5300 const char* _t = jsonObjectGetString(
5301 jsonObjectGetIndex( flesh_fields, 0 ) );
5302 if( !strcmp( _t, "*" ))
5303 link_fields = osrfHashKeys( links );
5306 if( !link_fields ) {
5308 link_fields = osrfNewStringArray( 1 );
5309 jsonIterator* _i = jsonNewIterator( flesh_fields );
5310 while ((_f = jsonIteratorNext( _i ))) {
5311 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5313 jsonIteratorFree( _i );
5317 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5319 // Iterate over the JSON_ARRAY of rows
5321 unsigned long res_idx = 0;
5322 while((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5325 const char* link_field;
5327 // Iterate over the list of fleshable fields
5328 while( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5330 osrfLogDebug( OSRF_LOG_MARK, "Starting to flesh %s", link_field );
5332 osrfHash* kid_link = osrfHashGet( links, link_field );
5334 continue; // Not a link field; skip it
5336 osrfHash* field = osrfHashGet( fields, link_field );
5338 continue; // Not a field at all; skip it (IDL is ill-formed)
5340 osrfHash* kid_idl = osrfHashGet( oilsIDL(),
5341 osrfHashGet( kid_link, "class" ));
5343 continue; // The class it links to doesn't exist; skip it
5345 const char* reltype = osrfHashGet( kid_link, "reltype" );
5347 continue; // No reltype; skip it (IDL is ill-formed)
5349 osrfHash* value_field = field;
5351 if( !strcmp( reltype, "has_many" )
5352 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5353 value_field = osrfHashGet(
5354 fields, osrfHashGet( class_meta, "primarykey" ) );
5357 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5359 if( link_map->size > 0 ) {
5360 jsonObject* _kid_key = jsonNewObjectType( JSON_ARRAY );
5363 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5368 osrfHashGet( kid_link, "class" ),
5375 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5376 osrfHashGet( kid_link, "field" ),
5377 osrfHashGet( kid_link, "class" ),
5378 osrfHashGet( kid_link, "key" ),
5379 osrfHashGet( kid_link, "reltype" )
5382 const char* search_key = jsonObjectGetString(
5383 jsonObjectGetIndex( cur,
5384 atoi( osrfHashGet( value_field, "array_position" ) )
5389 osrfLogDebug( OSRF_LOG_MARK, "Nothing to search for!" );
5393 osrfLogDebug( OSRF_LOG_MARK, "Creating param objects..." );
5395 // construct WHERE clause
5396 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
5399 osrfHashGet( kid_link, "key" ),
5400 jsonNewObject( search_key )
5403 // construct the rest of the query, mostly
5404 // by copying pieces of the previous level of query
5405 jsonObject* rest_of_query = jsonNewObjectType( JSON_HASH );
5406 jsonObjectSetKey( rest_of_query, "flesh",
5407 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5411 jsonObjectSetKey( rest_of_query, "flesh_fields",
5412 jsonObjectClone( flesh_blob ));
5414 if( jsonObjectGetKeyConst( query_hash, "order_by" )) {
5415 jsonObjectSetKey( rest_of_query, "order_by",
5416 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "order_by" ))
5420 if( jsonObjectGetKeyConst( query_hash, "select" )) {
5421 jsonObjectSetKey( rest_of_query, "select",
5422 jsonObjectClone( jsonObjectGetKeyConst( query_hash, "select" ))
5426 // do the query, recursively, to expand the fleshable field
5427 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5428 where_clause, rest_of_query, err );
5430 jsonObjectFree( where_clause );
5431 jsonObjectFree( rest_of_query );
5434 osrfStringArrayFree( link_fields );
5435 jsonObjectFree( res_list );
5436 jsonObjectFree( flesh_blob );
5440 osrfLogDebug( OSRF_LOG_MARK, "Search for %s return %d linked objects",
5441 osrfHashGet( kid_link, "class" ), kids->size );
5443 // Traverse the result set
5444 jsonObject* X = NULL;
5445 if( link_map->size > 0 && kids->size > 0 ) {
5447 kids = jsonNewObjectType( JSON_ARRAY );
5449 jsonObject* _k_node;
5450 unsigned long res_idx = 0;
5451 while((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5457 (unsigned long) atoi(
5463 osrfHashGet( kid_link, "class" )
5467 osrfStringArrayGetString( link_map, 0 )
5475 } // end while loop traversing X
5478 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_a" )
5479 || !strcmp( osrfHashGet( kid_link, "reltype" ), "might_have" )) {
5480 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5481 osrfHashGet( kid_link, "field" ));
5484 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5485 jsonObjectClone( jsonObjectGetIndex( kids, 0 ))
5489 if( !strcmp( osrfHashGet( kid_link, "reltype" ), "has_many" )) {
5491 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5492 osrfHashGet( kid_link, "field" ) );
5495 (unsigned long) atoi( osrfHashGet( field, "array_position" ) ),
5496 jsonObjectClone( kids )
5501 jsonObjectFree( kids );
5505 jsonObjectFree( kids );
5507 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5508 osrfHashGet( kid_link, "field" ) );
5509 osrfLogDebug( OSRF_LOG_MARK, "%s", jsonObjectToJSON( cur ));
5511 } // end while loop traversing list of fleshable fields
5512 } // end while loop traversing res_list
5513 jsonObjectFree( flesh_blob );
5514 osrfStringArrayFree( link_fields );
5523 int doUpdate( osrfMethodContext* ctx ) {
5524 if( osrfMethodVerifyContext( ctx )) {
5525 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5530 timeout_needs_resetting = 1;
5532 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5534 jsonObject* target = NULL;
5536 target = jsonObjectGetIndex( ctx->params, 1 );
5538 target = jsonObjectGetIndex( ctx->params, 0 );
5540 if(!verifyObjectClass( ctx, target )) {
5541 osrfAppRespondComplete( ctx, NULL );
5545 if( getXactId( ctx ) == NULL ) {
5546 osrfAppSessionStatus(
5548 OSRF_STATUS_BADREQUEST,
5549 "osrfMethodException",
5551 "No active transaction -- required for UPDATE"
5553 osrfAppRespondComplete( ctx, NULL );
5557 // The following test is harmless but redundant. If a class is
5558 // readonly, we don't register an update method for it.
5559 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5560 osrfAppSessionStatus(
5562 OSRF_STATUS_BADREQUEST,
5563 "osrfMethodException",
5565 "Cannot UPDATE readonly class"
5567 osrfAppRespondComplete( ctx, NULL );
5571 dbhandle = writehandle;
5572 const char* trans_id = getXactId( ctx );
5574 // Set the last_xact_id
5575 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5577 osrfLogDebug( OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5578 trans_id, target->classname, index );
5579 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
5582 char* pkey = osrfHashGet( meta, "primarykey" );
5583 osrfHash* fields = osrfHashGet( meta, "fields" );
5585 char* id = oilsFMGetString( target, pkey );
5589 "%s updating %s object with %s = %s",
5591 osrfHashGet( meta, "fieldmapper" ),
5596 growing_buffer* sql = buffer_init( 128 );
5597 buffer_fadd( sql,"UPDATE %s SET", osrfHashGet( meta, "tablename" ));
5600 osrfHash* field_def = NULL;
5601 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5602 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5604 // Skip virtual fields, and the primary key
5605 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5608 const char* field_name = osrfHashIteratorKey( field_itr );
5609 if( ! strcmp( field_name, pkey ) )
5612 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5614 int value_is_numeric = 0; // boolean
5616 if( field_object && field_object->classname ) {
5617 value = oilsFMGetString(
5619 (char*) oilsIDLFindPath( "/%s/primarykey", field_object->classname )
5621 } else if( field_object && JSON_BOOL == field_object->type ) {
5622 if( jsonBoolIsTrue( field_object ) )
5623 value = strdup( "t" );
5625 value = strdup( "f" );
5627 value = jsonObjectToSimpleString( field_object );
5628 if( field_object && JSON_NUMBER == field_object->type )
5629 value_is_numeric = 1;
5632 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5633 osrfHashGet( meta, "fieldmapper" ), field_name, value);
5635 if( !field_object || field_object->type == JSON_NULL ) {
5636 if( !( !( strcmp( osrfHashGet( meta, "classname" ), "au" ) )
5637 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5641 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5642 buffer_fadd( sql, " %s = NULL", field_name );
5645 } else if( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5649 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5651 const char* numtype = get_datatype( field_def );
5652 if( !strncmp( numtype, "INT", 3 ) ) {
5653 buffer_fadd( sql, " %s = %ld", field_name, atol( value ) );
5654 } else if( !strcmp( numtype, "NUMERIC" ) ) {
5655 buffer_fadd( sql, " %s = %f", field_name, atof( value ) );
5657 // Must really be intended as a string, so quote it
5658 if( dbi_conn_quote_string( dbhandle, &value )) {
5659 buffer_fadd( sql, " %s = %s", field_name, value );
5661 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5662 modulename, value );
5663 osrfAppSessionStatus(
5665 OSRF_STATUS_INTERNALSERVERERROR,
5666 "osrfMethodException",
5668 "Error quoting string -- please see the error log for more details"
5672 osrfHashIteratorFree( field_itr );
5674 osrfAppRespondComplete( ctx, NULL );
5679 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5682 if( dbi_conn_quote_string( dbhandle, &value ) ) {
5686 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5687 buffer_fadd( sql, " %s = %s", field_name, value );
5689 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
5690 osrfAppSessionStatus(
5692 OSRF_STATUS_INTERNALSERVERERROR,
5693 "osrfMethodException",
5695 "Error quoting string -- please see the error log for more details"
5699 osrfHashIteratorFree( field_itr );
5701 osrfAppRespondComplete( ctx, NULL );
5710 osrfHashIteratorFree( field_itr );
5712 jsonObject* obj = jsonNewObject( id );
5714 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey )), "number" ))
5715 dbi_conn_quote_string( dbhandle, &id );
5717 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5719 char* query = buffer_release( sql );
5720 osrfLogDebug( OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query );
5722 dbi_result result = dbi_conn_query( dbhandle, query );
5726 jsonObjectFree( obj );
5727 obj = jsonNewObject( NULL );
5729 int errnum = dbi_conn_error( dbhandle, &msg );
5732 "%s ERROR updating %s object with %s = %s: %d %s",
5734 osrfHashGet( meta, "fieldmapper" ),
5738 msg ? msg : "(No description available)"
5743 osrfAppRespondComplete( ctx, obj );
5744 jsonObjectFree( obj );
5748 int doDelete( osrfMethodContext* ctx ) {
5749 if( osrfMethodVerifyContext( ctx )) {
5750 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5755 timeout_needs_resetting = 1;
5757 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5759 if( getXactId( ctx ) == NULL ) {
5760 osrfAppSessionStatus(
5762 OSRF_STATUS_BADREQUEST,
5763 "osrfMethodException",
5765 "No active transaction -- required for DELETE"
5767 osrfAppRespondComplete( ctx, NULL );
5771 // The following test is harmless but redundant. If a class is
5772 // readonly, we don't register a delete method for it.
5773 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5774 osrfAppSessionStatus(
5776 OSRF_STATUS_BADREQUEST,
5777 "osrfMethodException",
5779 "Cannot DELETE readonly class"
5781 osrfAppRespondComplete( ctx, NULL );
5785 dbhandle = writehandle;
5787 char* pkey = osrfHashGet( meta, "primarykey" );
5794 if( jsonObjectGetIndex( ctx->params, _obj_pos )->classname ) {
5795 if( !verifyObjectClass( ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5796 osrfAppRespondComplete( ctx, NULL );
5800 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5802 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5803 osrfAppRespondComplete( ctx, NULL );
5806 id = jsonObjectToSimpleString( jsonObjectGetIndex( ctx->params, _obj_pos ));
5811 "%s deleting %s object with %s = %s",
5813 osrfHashGet( meta, "fieldmapper" ),
5818 jsonObject* obj = jsonNewObject( id );
5820 if( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5821 dbi_conn_quote_string( writehandle, &id );
5823 dbi_result result = dbi_conn_queryf( writehandle, "DELETE FROM %s WHERE %s = %s;",
5824 osrfHashGet( meta, "tablename" ), pkey, id );
5827 jsonObjectFree( obj );
5828 obj = jsonNewObject( NULL );
5830 int errnum = dbi_conn_error( writehandle, &msg );
5833 "%s ERROR deleting %s object with %s = %s: %d %s",
5835 osrfHashGet( meta, "fieldmapper" ),
5839 msg ? msg : "(No description available)"
5845 osrfAppRespondComplete( ctx, obj );
5846 jsonObjectFree( obj );
5851 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5852 @param result An iterator for a result set; we only look at the current row.
5853 @param @meta Pointer to the class metadata for the core class.
5854 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5856 If a column is not defined in the IDL, or if it has no array_position defined for it in
5857 the IDL, or if it is defined as virtual, ignore it.
5859 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5860 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5861 array_position in the IDL.
5863 A field defined in the IDL but not represented in the returned row will leave a hole
5864 in the JSON_ARRAY. In effect it will be treated as a null value.
5866 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5867 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5868 classname corresponding to the @a meta argument.
5870 The calling code is responsible for freeing the the resulting jsonObject by calling
5873 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5874 if( !( result && meta )) return NULL;
5876 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5877 jsonObjectSetClass( object, osrfHashGet( meta, "classname" ));
5878 osrfLogInternal( OSRF_LOG_MARK, "Setting object class to %s ", object->classname );
5880 osrfHash* fields = osrfHashGet( meta, "fields" );
5882 int columnIndex = 1;
5883 const char* columnName;
5885 /* cycle through the columns in the row returned from the database */
5886 while( (columnName = dbi_result_get_field_name( result, columnIndex )) ) {
5888 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
5890 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5892 /* determine the field type and storage attributes */
5893 unsigned short type = dbi_result_get_field_type_idx( result, columnIndex );
5894 int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
5896 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5897 // or if it has no sequence number there, or if it's virtual, skip it.
5898 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
5901 if( str_is_true( osrfHashGet( _f, "virtual" )))
5902 continue; // skip this column: IDL says it's virtual
5904 const char* pos = (char*) osrfHashGet( _f, "array_position" );
5905 if( !pos ) // IDL has no sequence number for it. This shouldn't happen,
5906 continue; // since we assign sequence numbers dynamically as we load the IDL.
5908 fmIndex = atoi( pos );
5909 osrfLogInternal( OSRF_LOG_MARK, "... Found column at position [%s]...", pos );
5911 continue; // This field is not defined in the IDL
5914 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
5915 // sequence number from the IDL (which is likely to be different from the sequence
5916 // of columns in the SELECT clause).
5917 if( dbi_result_field_is_null_idx( result, columnIndex )) {
5918 jsonObjectSetIndex( object, fmIndex, jsonNewObject( NULL ));
5923 case DBI_TYPE_INTEGER :
5925 if( attr & DBI_INTEGER_SIZE8 )
5926 jsonObjectSetIndex( object, fmIndex,
5927 jsonNewNumberObject(
5928 dbi_result_get_longlong_idx( result, columnIndex )));
5930 jsonObjectSetIndex( object, fmIndex,
5931 jsonNewNumberObject( dbi_result_get_int_idx( result, columnIndex )));
5935 case DBI_TYPE_DECIMAL :
5936 jsonObjectSetIndex( object, fmIndex,
5937 jsonNewNumberObject( dbi_result_get_double_idx(result, columnIndex )));
5940 case DBI_TYPE_STRING :
5945 jsonNewObject( dbi_result_get_string_idx( result, columnIndex ))
5950 case DBI_TYPE_DATETIME : {
5952 char dt_string[ 256 ] = "";
5955 // Fetch the date column as a time_t
5956 time_t _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
5958 // Translate the time_t to a human-readable string
5959 if( !( attr & DBI_DATETIME_DATE )) {
5960 gmtime_r( &_tmp_dt, &gmdt );
5961 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
5962 } else if( !( attr & DBI_DATETIME_TIME )) {
5963 localtime_r( &_tmp_dt, &gmdt );
5964 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
5966 localtime_r( &_tmp_dt, &gmdt );
5967 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
5970 jsonObjectSetIndex( object, fmIndex, jsonNewObject( dt_string ));
5974 case DBI_TYPE_BINARY :
5975 osrfLogError( OSRF_LOG_MARK,
5976 "Can't do binary at column %s : index %d", columnName, columnIndex );
5985 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5986 if( !result ) return NULL;
5988 jsonObject* object = jsonNewObject( NULL );
5991 char dt_string[ 256 ];
5995 int columnIndex = 1;
5997 unsigned short type;
5998 const char* columnName;
6000 /* cycle through the column list */
6001 while(( columnName = dbi_result_get_field_name( result, columnIndex ))) {
6003 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...", (char*) columnName );
6005 fmIndex = -1; // reset the position
6007 /* determine the field type and storage attributes */
6008 type = dbi_result_get_field_type_idx( result, columnIndex );
6009 attr = dbi_result_get_field_attribs_idx( result, columnIndex );
6011 if( dbi_result_field_is_null_idx( result, columnIndex )) {
6012 jsonObjectSetKey( object, columnName, jsonNewObject( NULL ));
6017 case DBI_TYPE_INTEGER :
6019 if( attr & DBI_INTEGER_SIZE8 )
6020 jsonObjectSetKey( object, columnName,
6021 jsonNewNumberObject( dbi_result_get_longlong_idx(
6022 result, columnIndex )) );
6024 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6025 dbi_result_get_int_idx( result, columnIndex )) );
6028 case DBI_TYPE_DECIMAL :
6029 jsonObjectSetKey( object, columnName, jsonNewNumberObject(
6030 dbi_result_get_double_idx( result, columnIndex )) );
6033 case DBI_TYPE_STRING :
6034 jsonObjectSetKey( object, columnName,
6035 jsonNewObject( dbi_result_get_string_idx( result, columnIndex )));
6038 case DBI_TYPE_DATETIME :
6040 memset( dt_string, '\0', sizeof( dt_string ));
6041 memset( &gmdt, '\0', sizeof( gmdt ));
6043 _tmp_dt = dbi_result_get_datetime_idx( result, columnIndex );
6045 if( !( attr & DBI_DATETIME_DATE )) {
6046 gmtime_r( &_tmp_dt, &gmdt );
6047 strftime( dt_string, sizeof( dt_string ), "%T", &gmdt );
6048 } else if( !( attr & DBI_DATETIME_TIME )) {
6049 localtime_r( &_tmp_dt, &gmdt );
6050 strftime( dt_string, sizeof( dt_string ), "%F", &gmdt );
6052 localtime_r( &_tmp_dt, &gmdt );
6053 strftime( dt_string, sizeof( dt_string ), "%FT%T%z", &gmdt );
6056 jsonObjectSetKey( object, columnName, jsonNewObject( dt_string ));
6059 case DBI_TYPE_BINARY :
6060 osrfLogError( OSRF_LOG_MARK,
6061 "Can't do binary at column %s : index %d", columnName, columnIndex );
6065 } // end while loop traversing result
6070 // Interpret a string as true or false
6071 int str_is_true( const char* str ) {
6072 if( NULL == str || strcasecmp( str, "true" ) )
6078 // Interpret a jsonObject as true or false
6079 static int obj_is_true( const jsonObject* obj ) {
6082 else switch( obj->type )
6090 if( strcasecmp( obj->value.s, "true" ) )
6094 case JSON_NUMBER : // Support 1/0 for perl's sake
6095 if( jsonObjectGetNumber( obj ) == 1.0 )
6104 // Translate a numeric code into a text string identifying a type of
6105 // jsonObject. To be used for building error messages.
6106 static const char* json_type( int code ) {
6112 return "JSON_ARRAY";
6114 return "JSON_STRING";
6116 return "JSON_NUMBER";
6122 return "(unrecognized)";
6126 // Extract the "primitive" attribute from an IDL field definition.
6127 // If we haven't initialized the app, then we must be running in
6128 // some kind of testbed. In that case, default to "string".
6129 static const char* get_primitive( osrfHash* field ) {
6130 const char* s = osrfHashGet( field, "primitive" );
6132 if( child_initialized )
6135 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6137 osrfHashGet( field, "name" )
6145 // Extract the "datatype" attribute from an IDL field definition.
6146 // If we haven't initialized the app, then we must be running in
6147 // some kind of testbed. In that case, default to to NUMERIC,
6148 // since we look at the datatype only for numbers.
6149 static const char* get_datatype( osrfHash* field ) {
6150 const char* s = osrfHashGet( field, "datatype" );
6152 if( child_initialized )
6155 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6157 osrfHashGet( field, "name" )
6166 @brief Determine whether a string is potentially a valid SQL identifier.
6167 @param s The identifier to be tested.
6168 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6170 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6171 need to follow all the rules exactly, such as requiring that the first character not
6174 We allow leading and trailing white space. In between, we do not allow punctuation
6175 (except for underscores and dollar signs), control characters, or embedded white space.
6177 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6178 for the foreseeable future such quoted identifiers are not likely to be an issue.
6180 int is_identifier( const char* s) {
6184 // Skip leading white space
6185 while( isspace( (unsigned char) *s ) )
6189 return 0; // Nothing but white space? Not okay.
6191 // Check each character until we reach white space or
6192 // end-of-string. Letters, digits, underscores, and
6193 // dollar signs are okay. With the exception of periods
6194 // (as in schema.identifier), control characters and other
6195 // punctuation characters are not okay. Anything else
6196 // is okay -- it could for example be part of a multibyte
6197 // UTF8 character such as a letter with diacritical marks,
6198 // and those are allowed.
6200 if( isalnum( (unsigned char) *s )
6204 ; // Fine; keep going
6205 else if( ispunct( (unsigned char) *s )
6206 || iscntrl( (unsigned char) *s ) )
6209 } while( *s && ! isspace( (unsigned char) *s ) );
6211 // If we found any white space in the above loop,
6212 // the rest had better be all white space.
6214 while( isspace( (unsigned char) *s ) )
6218 return 0; // White space was embedded within non-white space
6224 @brief Determine whether to accept a character string as a comparison operator.
6225 @param op The candidate comparison operator.
6226 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6228 We don't validate the operator for real. We just make sure that it doesn't contain
6229 any semicolons or white space (with special exceptions for a few specific operators).
6230 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6231 space but it's still not a valid operator, then the database will complain.
6233 Another approach would be to compare the string against a short list of approved operators.
6234 We don't do that because we want to allow custom operators like ">100*", which at this
6235 writing would be difficult or impossible to express otherwise in a JSON query.
6237 int is_good_operator( const char* op ) {
6238 if( !op ) return 0; // Sanity check
6242 if( isspace( (unsigned char) *s ) ) {
6243 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6244 // and IS NOT DISTINCT FROM.
6245 if( !strcasecmp( op, "similar to" ) )
6247 else if( !strcasecmp( op, "is distinct from" ) )
6249 else if( !strcasecmp( op, "is not distinct from" ) )
6254 else if( ';' == *s )
6262 @name Query Frame Management
6264 The following machinery supports a stack of query frames for use by SELECT().
6266 A query frame caches information about one level of a SELECT query. When we enter
6267 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6269 The query frame stores information about the core class, and about any joined classes
6272 The main purpose is to map table aliases to classes and tables, so that a query can
6273 join to the same table more than once. A secondary goal is to reduce the number of
6274 lookups in the IDL by caching the results.
6278 #define STATIC_CLASS_INFO_COUNT 3
6280 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6283 @brief Allocate a ClassInfo as raw memory.
6284 @return Pointer to the newly allocated ClassInfo.
6286 Except for the in_use flag, which is used only by the allocation and deallocation
6287 logic, we don't initialize the ClassInfo here.
6289 static ClassInfo* allocate_class_info( void ) {
6290 // In order to reduce the number of mallocs and frees, we return a static
6291 // instance of ClassInfo, if we can find one that we're not already using.
6292 // We rely on the fact that the compiler will implicitly initialize the
6293 // static instances so that in_use == 0.
6296 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6297 if( ! static_class_info[ i ].in_use ) {
6298 static_class_info[ i ].in_use = 1;
6299 return static_class_info + i;
6303 // The static ones are all in use. Malloc one.
6305 return safe_malloc( sizeof( ClassInfo ) );
6309 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6310 @param info Pointer to the ClassInfo to be cleared.
6312 static void clear_class_info( ClassInfo* info ) {
6317 // Free any malloc'd strings
6319 if( info->alias != info->alias_store )
6320 free( info->alias );
6322 if( info->class_name != info->class_name_store )
6323 free( info->class_name );
6325 free( info->source_def );
6327 info->alias = info->class_name = info->source_def = NULL;
6332 @brief Free a ClassInfo and everything it owns.
6333 @param info Pointer to the ClassInfo to be freed.
6335 static void free_class_info( ClassInfo* info ) {
6340 clear_class_info( info );
6342 // If it's one of the static instances, just mark it as not in use
6345 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6346 if( info == static_class_info + i ) {
6347 static_class_info[ i ].in_use = 0;
6352 // Otherwise it must have been malloc'd, so free it
6358 @brief Populate an already-allocated ClassInfo.
6359 @param info Pointer to the ClassInfo to be populated.
6360 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6362 @param class Name of the class.
6363 @return Zero if successful, or 1 if not.
6365 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6366 the relevant portions of the IDL for the specified class.
6368 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6371 osrfLogError( OSRF_LOG_MARK,
6372 "%s ERROR: No ClassInfo available to populate", modulename );
6373 info->alias = info->class_name = info->source_def = NULL;
6374 info->class_def = info->fields = info->links = NULL;
6379 osrfLogError( OSRF_LOG_MARK,
6380 "%s ERROR: No class name provided for lookup", modulename );
6381 info->alias = info->class_name = info->source_def = NULL;
6382 info->class_def = info->fields = info->links = NULL;
6386 // Alias defaults to class name if not supplied
6387 if( ! alias || ! alias[ 0 ] )
6390 // Look up class info in the IDL
6391 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6393 osrfLogError( OSRF_LOG_MARK,
6394 "%s ERROR: Class %s not defined in IDL", modulename, class );
6395 info->alias = info->class_name = info->source_def = NULL;
6396 info->class_def = info->fields = info->links = NULL;
6398 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6399 osrfLogError( OSRF_LOG_MARK,
6400 "%s ERROR: Class %s is defined as virtual", modulename, class );
6401 info->alias = info->class_name = info->source_def = NULL;
6402 info->class_def = info->fields = info->links = NULL;
6406 osrfHash* links = osrfHashGet( class_def, "links" );
6408 osrfLogError( OSRF_LOG_MARK,
6409 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6410 info->alias = info->class_name = info->source_def = NULL;
6411 info->class_def = info->fields = info->links = NULL;
6415 osrfHash* fields = osrfHashGet( class_def, "fields" );
6417 osrfLogError( OSRF_LOG_MARK,
6418 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6419 info->alias = info->class_name = info->source_def = NULL;
6420 info->class_def = info->fields = info->links = NULL;
6424 char* source_def = oilsGetRelation( class_def );
6428 // We got everything we need, so populate the ClassInfo
6429 if( strlen( alias ) > ALIAS_STORE_SIZE )
6430 info->alias = strdup( alias );
6432 strcpy( info->alias_store, alias );
6433 info->alias = info->alias_store;
6436 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6437 info->class_name = strdup( class );
6439 strcpy( info->class_name_store, class );
6440 info->class_name = info->class_name_store;
6443 info->source_def = source_def;
6445 info->class_def = class_def;
6446 info->links = links;
6447 info->fields = fields;
6452 #define STATIC_FRAME_COUNT 3
6454 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6457 @brief Allocate a QueryFrame as raw memory.
6458 @return Pointer to the newly allocated QueryFrame.
6460 Except for the in_use flag, which is used only by the allocation and deallocation
6461 logic, we don't initialize the QueryFrame here.
6463 static QueryFrame* allocate_frame( void ) {
6464 // In order to reduce the number of mallocs and frees, we return a static
6465 // instance of QueryFrame, if we can find one that we're not already using.
6466 // We rely on the fact that the compiler will implicitly initialize the
6467 // static instances so that in_use == 0.
6470 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6471 if( ! static_frame[ i ].in_use ) {
6472 static_frame[ i ].in_use = 1;
6473 return static_frame + i;
6477 // The static ones are all in use. Malloc one.
6479 return safe_malloc( sizeof( QueryFrame ) );
6483 @brief Free a QueryFrame, and all the memory it owns.
6484 @param frame Pointer to the QueryFrame to be freed.
6486 static void free_query_frame( QueryFrame* frame ) {
6491 clear_class_info( &frame->core );
6493 // Free the join list
6495 ClassInfo* info = frame->join_list;
6498 free_class_info( info );
6502 frame->join_list = NULL;
6505 // If the frame is a static instance, just mark it as unused
6507 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6508 if( frame == static_frame + i ) {
6509 static_frame[ i ].in_use = 0;
6514 // Otherwise it must have been malloc'd, so free it
6520 @brief Search a given QueryFrame for a specified alias.
6521 @param frame Pointer to the QueryFrame to be searched.
6522 @param target The alias for which to search.
6523 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6525 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6526 if( ! frame || ! target ) {
6530 ClassInfo* found_class = NULL;
6532 if( !strcmp( target, frame->core.alias ) )
6533 return &(frame->core);
6535 ClassInfo* curr_class = frame->join_list;
6536 while( curr_class ) {
6537 if( strcmp( target, curr_class->alias ) )
6538 curr_class = curr_class->next;
6540 found_class = curr_class;
6550 @brief Push a new (blank) QueryFrame onto the stack.
6552 static void push_query_frame( void ) {
6553 QueryFrame* frame = allocate_frame();
6554 frame->join_list = NULL;
6555 frame->next = curr_query;
6557 // Initialize the ClassInfo for the core class
6558 ClassInfo* core = &frame->core;
6559 core->alias = core->class_name = core->source_def = NULL;
6560 core->class_def = core->fields = core->links = NULL;
6566 @brief Pop a QueryFrame off the stack and destroy it.
6568 static void pop_query_frame( void ) {
6573 QueryFrame* popped = curr_query;
6574 curr_query = popped->next;
6576 free_query_frame( popped );
6580 @brief Populate the ClassInfo for the core class.
6581 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6582 class name as an alias.
6583 @param class_name Name of the core class.
6584 @return Zero if successful, or 1 if not.
6586 Populate the ClassInfo of the core class with copies of the alias and class name, and
6587 with pointers to the relevant portions of the IDL for the core class.
6589 static int add_query_core( const char* alias, const char* class_name ) {
6592 if( ! curr_query ) {
6593 osrfLogError( OSRF_LOG_MARK,
6594 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6596 } else if( curr_query->core.alias ) {
6597 osrfLogError( OSRF_LOG_MARK,
6598 "%s ERROR: Core class %s already populated as %s",
6599 modulename, curr_query->core.class_name, curr_query->core.alias );
6603 build_class_info( &curr_query->core, alias, class_name );
6604 if( curr_query->core.alias )
6607 osrfLogError( OSRF_LOG_MARK,
6608 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6614 @brief Search the current QueryFrame for a specified alias.
6615 @param target The alias for which to search.
6616 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6618 static inline ClassInfo* search_alias( const char* target ) {
6619 return search_alias_in_frame( curr_query, target );
6623 @brief Search all levels of query for a specified alias, starting with the current query.
6624 @param target The alias for which to search.
6625 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6627 static ClassInfo* search_all_alias( const char* target ) {
6628 ClassInfo* found_class = NULL;
6629 QueryFrame* curr_frame = curr_query;
6631 while( curr_frame ) {
6632 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6635 curr_frame = curr_frame->next;
6642 @brief Add a class to the list of classes joined to the current query.
6643 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6644 the class name as an alias.
6645 @param classname The name of the class to be added.
6646 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6648 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6650 if( ! classname || ! *classname ) { // sanity check
6651 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6658 const ClassInfo* conflict = search_alias( alias );
6660 osrfLogError( OSRF_LOG_MARK,
6661 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6662 modulename, alias, conflict->class_name );
6666 ClassInfo* info = allocate_class_info();
6668 if( build_class_info( info, alias, classname ) ) {
6669 free_class_info( info );
6673 // Add the new ClassInfo to the join list of the current QueryFrame
6674 info->next = curr_query->join_list;
6675 curr_query->join_list = info;
6681 @brief Destroy all nodes on the query stack.
6683 static void clear_query_stack( void ) {