3 @brief As a server, perform database operations at the request of clients.
7 #include "opensrf/osrf_application.h"
8 #include "opensrf/osrf_settings.h"
9 #include "opensrf/osrf_message.h"
10 #include "opensrf/utils.h"
11 #include "opensrf/osrf_json.h"
12 #include "opensrf/log.h"
13 #include "openils/oils_utils.h"
14 #include "openils/oils_constants.h"
23 # define MODULENAME "open-ils.reporter-store"
26 # define MODULENAME "open-ils.pcrud"
28 # define MODULENAME "open-ils.cstore"
32 // The next four macros are OR'd together as needed to form a set
33 // of bitflags. SUBCOMBO enables an extra pair of parentheses when
34 // nesting one UNION, INTERSECT or EXCEPT inside another.
35 // SUBSELECT tells us we're in a subquery, so don't add the
36 // terminal semicolon yet.
39 #define DISABLE_I18N 2
40 #define SELECT_DISTINCT 1
45 struct ClassInfoStruct;
46 typedef struct ClassInfoStruct ClassInfo;
48 #define ALIAS_STORE_SIZE 16
49 #define CLASS_NAME_STORE_SIZE 16
51 struct ClassInfoStruct {
55 osrfHash* class_def; // Points into IDL
56 osrfHash* fields; // Points into IDL
57 osrfHash* links; // Points into IDL
59 // The remaining members are private and internal. Client code should not
60 // access them directly.
62 ClassInfo* next; // Supports linked list of joined classes
63 int in_use; // boolean
65 // We usually store the alias and class name in the following arrays, and
66 // point the corresponding pointers at them. When the string is too big
67 // for the array (which will probably never happen in practice), we strdup it.
69 char alias_store[ ALIAS_STORE_SIZE + 1 ];
70 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
73 struct QueryFrameStruct;
74 typedef struct QueryFrameStruct QueryFrame;
76 struct QueryFrameStruct {
78 ClassInfo* join_list; // linked list of classes joined to the core class
79 QueryFrame* next; // implements stack as linked list
80 int in_use; // boolean
84 static int timeout_needs_resetting;
85 static time_t time_next_reset;
88 int osrfAppChildInit();
89 int osrfAppInitialize();
90 void osrfAppChildExit();
92 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
94 static void setXactId( osrfMethodContext* ctx );
95 static inline const char* getXactId( osrfMethodContext* ctx );
96 static inline void clearXactId( osrfMethodContext* ctx );
98 int beginTransaction ( osrfMethodContext* );
99 int commitTransaction ( osrfMethodContext* );
100 int rollbackTransaction ( osrfMethodContext* );
102 int setSavepoint ( osrfMethodContext* );
103 int releaseSavepoint ( osrfMethodContext* );
104 int rollbackSavepoint ( osrfMethodContext* );
106 int doJSONSearch ( osrfMethodContext* );
108 int dispatchCRUDMethod ( osrfMethodContext* );
109 static jsonObject* doCreate ( osrfMethodContext*, int* );
110 static jsonObject* doRetrieve ( osrfMethodContext*, int* );
111 static jsonObject* doUpdate ( osrfMethodContext*, int* );
112 static jsonObject* doDelete ( osrfMethodContext*, int* );
113 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
114 jsonObject* where_hash, jsonObject* query_hash, int* err );
115 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
116 static jsonObject* oilsMakeJSONFromResult( dbi_result );
118 static char* searchSimplePredicate ( const char* op, const char* class_alias,
119 osrfHash* field, const jsonObject* node );
120 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
121 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject*);
122 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*, const char* );
123 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
124 static char* searchINPredicate ( const char*, osrfHash*,
125 jsonObject*, const char*, osrfMethodContext* );
126 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
127 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
128 static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
129 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
130 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
132 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
134 void userDataFree( void* );
135 static void sessionDataFree( char*, void* );
136 static char* getRelation( osrfHash* );
137 static int str_is_true( const char* str );
138 static int obj_is_true( const jsonObject* obj );
139 static const char* json_type( int code );
140 static const char* get_primitive( osrfHash* field );
141 static const char* get_datatype( osrfHash* field );
142 static int is_identifier( const char* s);
143 static int is_good_operator( const char* op );
144 static void pop_query_frame( void );
145 static void push_query_frame( void );
146 static int add_query_core( const char* alias, const char* class_name );
147 static inline ClassInfo* search_alias( const char* target );
148 static ClassInfo* search_all_alias( const char* target );
149 static ClassInfo* add_joined_class( const char* alias, const char* classname );
150 static void clear_query_stack( void );
153 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
154 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
155 static char* org_tree_root( osrfMethodContext* ctx );
156 static jsonObject* single_hash( const char* key, const char* value );
159 static int child_initialized = 0; /* boolean */
161 static dbi_conn writehandle; /* our MASTER db connection */
162 static dbi_conn dbhandle; /* our CURRENT db connection */
163 //static osrfHash * readHandles;
164 static jsonObject* const jsonNULL = NULL; //
165 static int max_flesh_depth = 100;
167 // The following points to the top of a stack of QueryFrames. It's a little
168 // confusing because the top level of the query is at the bottom of the stack.
169 static QueryFrame* curr_query = NULL;
172 @brief Disconnect from the database.
174 This function is called when the server drone is about to terminate.
176 void osrfAppChildExit() {
177 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
180 if (writehandle == dbhandle)
184 dbi_conn_query(writehandle, "ROLLBACK;");
185 dbi_conn_close(writehandle);
188 if (dbhandle && !same)
189 dbi_conn_close(dbhandle);
191 // XXX add cleanup of readHandles whenever that gets used
197 @brief Initialize the application.
198 @return Zero if successful, or non-zero if not.
200 Load the IDL file into an internal data structure for future reference. Each non-virtual
201 class in the IDL corresponds to a table or view in the database, or to a subquery defined
202 in the IDL. Ignore all virtual tables and virtual fields.
204 Register a number of methods, some of them general-purpose and others specific for
207 The name of the application is given by the MODULENAME macro, whose value depends on
208 conditional compilation. The method names also incorporate MODULENAME, followed by a
209 dot, as a prefix. Some methods are registered or not registered depending on whether
210 the PCRUD macro is defined, and on whether the IDL includes permacrud entries for the
213 The general-purpose methods are as follows (minus their MODULENAME prefixes):
215 - json_query (not registered for PCRUD)
218 - transaction.rollback
223 For each non-virtual class, create up to eight class-specific methods:
225 - create (not for readonly classes)
227 - update (not for readonly classes)
228 - delete (not for readonly classes
229 - search (atomic and non-atomic versions)
230 - id_list (atomic and non-atomic versions)
232 For PCRUD, the full method names follow the pattern "MODULENAME.method_type.classname".
233 Otherwise they follow the pattern "MODULENAME.direct.XXX.method_type", where XXX is the
234 fieldmapper name from the IDL, with every run of one or more consecutive colons replaced
235 by a period. In addition, the names of atomic methods have a suffix of ".atomic".
237 This function is called when the registering the application, and is executed by the
238 listener before spawning the drones.
240 int osrfAppInitialize() {
242 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
243 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
245 if (!oilsIDLInit( osrf_settings_host_value("/IDL") ))
246 return 1; /* return non-zero to indicate error */
248 growing_buffer* method_name = buffer_init(64);
250 // Generic search thingy
251 buffer_add(method_name, MODULENAME);
252 buffer_add(method_name, ".json_query");
253 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
254 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
257 // first we register all the transaction and savepoint methods
258 buffer_reset(method_name);
259 OSRF_BUFFER_ADD(method_name, MODULENAME);
260 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
261 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
262 "beginTransaction", "", 0, 0 );
264 buffer_reset(method_name);
265 OSRF_BUFFER_ADD(method_name, MODULENAME);
266 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
267 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
268 "commitTransaction", "", 0, 0 );
270 buffer_reset(method_name);
271 OSRF_BUFFER_ADD(method_name, MODULENAME);
272 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
273 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
274 "rollbackTransaction", "", 0, 0 );
276 buffer_reset(method_name);
277 OSRF_BUFFER_ADD(method_name, MODULENAME);
278 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
279 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
280 "setSavepoint", "", 1, 0 );
282 buffer_reset(method_name);
283 OSRF_BUFFER_ADD(method_name, MODULENAME);
284 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
285 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
286 "releaseSavepoint", "", 1, 0 );
288 buffer_reset(method_name);
289 OSRF_BUFFER_ADD(method_name, MODULENAME);
290 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
291 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
292 "rollbackSavepoint", "", 1, 0 );
294 static const char* global_method[] = {
302 const int global_method_count
303 = sizeof( global_method ) / sizeof ( global_method[0] );
305 unsigned long class_count = osrfHashGetCount( oilsIDL() );
306 osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
307 osrfLogDebug(OSRF_LOG_MARK,
308 "At most %lu methods will be generated",
309 (unsigned long) (class_count * global_method_count) );
311 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
312 osrfHash* idlClass = NULL;
314 // For each class in the IDL...
315 while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
317 const char* classname = osrfHashIteratorKey( class_itr );
318 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
320 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
321 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
322 MODULENAME, classname);
326 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
327 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
331 // Look up some other attributes of the current class
332 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
333 if( !idlClass_fieldmapper ) {
334 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
340 // For PCRUD, ignore classes with no permacrud attribute
341 osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
342 if (!idlClass_permacrud) {
343 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no permacrud in IDL", classname );
347 const char* readonly = osrfHashGet(idlClass, "readonly");
350 for( i = 0; i < global_method_count; ++i ) { // for each global method
351 const char* method_type = global_method[ i ];
352 osrfLogDebug(OSRF_LOG_MARK,
353 "Using files to build %s class methods for %s", method_type, classname);
356 // Treat "id_list" or "search" as forms of "retrieve"
357 const char* tmp_method = method_type;
358 if ( *tmp_method == 'i' || *tmp_method == 's') { // "id_list" or "search"
359 tmp_method = "retrieve";
361 // Skip this method if there is no permacrud entry for it
362 if (!osrfHashGet( idlClass_permacrud, tmp_method ))
366 // No create, update, or delete methods for a readonly class
367 if ( str_is_true( readonly )
368 && ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd') )
371 buffer_reset( method_name );
373 // Build the method name
375 // For PCRUD: MODULENAME.method_type.classname
376 buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
378 // For non-PCRUD: MODULENAME.MODULENAME.direct.XXX.method_type
379 // where XXX is the fieldmapper name from the IDL, with every run of
380 // one or more consecutive colons replaced by a period.
383 char* _fm = strdup( idlClass_fieldmapper );
384 part = strtok_r(_fm, ":", &st_tmp);
386 buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
388 while ((part = strtok_r(NULL, ":", &st_tmp))) {
389 OSRF_BUFFER_ADD_CHAR(method_name, '.');
390 OSRF_BUFFER_ADD(method_name, part);
392 OSRF_BUFFER_ADD_CHAR(method_name, '.');
393 OSRF_BUFFER_ADD(method_name, method_type);
397 // For an id_list or search method we specify the OSRF_METHOD_STREAMING option.
398 // The consequence is that we implicitly create an atomic method in addition to
399 // the usual non-atomic method.
401 if (*method_type == 'i' || *method_type == 's') { // id_list or search
402 flags = flags | OSRF_METHOD_STREAMING;
405 osrfHash* method_meta = osrfNewHash();
406 osrfHashSet( method_meta, idlClass, "class");
407 osrfHashSet( method_meta, buffer_data( method_name ), "methodname" );
408 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
410 // Register the method, with a pointer to an osrfHash to tell the method
411 // its name, type, and class.
412 osrfAppRegisterExtendedMethod(
414 OSRF_BUFFER_C_STR( method_name ),
415 "dispatchCRUDMethod",
422 } // end for each global method
423 } // end for each class in IDL
425 buffer_free( method_name );
426 osrfHashIteratorFree( class_itr );
432 @brief Get a table name, view name, or subquery for use in a FROM clause.
433 @param class Pointer to the IDL class entry.
434 @return A table name, a view name, or a subquery in parentheses.
436 In some cases the IDL defines a class, not with a table name or a view name, but with
437 a SELECT statement, which may be used as a subquery.
439 static char* getRelation( osrfHash* class ) {
441 char* source_def = NULL;
442 const char* tabledef = osrfHashGet(class, "tablename");
445 source_def = strdup( tabledef ); // Return the name of a table or view
447 tabledef = osrfHashGet( class, "source_definition" );
449 // Return a subquery, enclosed in parentheses
450 source_def = safe_malloc( strlen( tabledef ) + 3 );
451 source_def[ 0 ] = '(';
452 strcpy( source_def + 1, tabledef );
453 strcat( source_def, ")" );
455 // Not found: return an error
456 const char* classname = osrfHashGet( class, "classname" );
461 "%s ERROR No tablename or source_definition for class \"%s\"",
472 @brief Initialize a server drone.
473 @return Zero if successful, -1 if not.
475 Connect to the database. For each non-virtual class in the IDL, execute a dummy "SELECT * "
476 query to get the datatype of each column. Record the datatypes in the loaded IDL.
478 This function is called by a server drone shortly after it is spawned by the listener.
480 int osrfAppChildInit() {
482 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
483 dbi_initialize(NULL);
484 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
486 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
487 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
488 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
489 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
490 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
491 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
492 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion",
495 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
496 writehandle = dbi_conn_new(driver);
499 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
502 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
504 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
505 "port=%s, user=%s, db=%s", MODULENAME, host, port, user, db );
507 if(host) dbi_conn_set_option(writehandle, "host", host );
508 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
509 if(user) dbi_conn_set_option(writehandle, "username", user);
510 if(pw) dbi_conn_set_option(writehandle, "password", pw );
511 if(db) dbi_conn_set_option(writehandle, "dbname", db );
513 if(md) max_flesh_depth = atoi(md);
514 if(max_flesh_depth < 0) max_flesh_depth = 1;
515 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
524 if (dbi_conn_connect(writehandle) < 0) {
526 if (dbi_conn_connect(writehandle) < 0) {
527 dbi_conn_error(writehandle, &err);
528 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
533 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
535 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
536 osrfHash* class = NULL;
537 growing_buffer* query_buf = buffer_init( 64 );
539 // For each class in the IDL...
540 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
541 const char* classname = osrfHashIteratorKey( class_itr );
542 osrfHash* fields = osrfHashGet( class, "fields" );
544 // If the class is virtual, ignore it
545 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
546 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
550 char* tabledef = getRelation(class);
552 continue; // No such relation -- a query of it would be doomed to failure
554 buffer_reset( query_buf );
555 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
559 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
560 MODULENAME, OSRF_BUFFER_C_STR( query_buf ) );
562 dbi_result result = dbi_conn_query( writehandle, OSRF_BUFFER_C_STR( query_buf ) );
566 const char* columnName;
568 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
570 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
573 /* fetch the fieldmapper index */
574 if( (_f = osrfHashGet(fields, columnName)) ) {
576 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
578 /* determine the field type and storage attributes */
580 switch( dbi_result_get_field_type_idx(result, columnIndex) ) {
582 case DBI_TYPE_INTEGER : {
584 if ( !osrfHashGet(_f, "primitive") )
585 osrfHashSet(_f, "number", "primitive");
587 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
588 if( attr & DBI_INTEGER_SIZE8 )
589 osrfHashSet(_f, "INT8", "datatype");
591 osrfHashSet(_f, "INT", "datatype");
594 case DBI_TYPE_DECIMAL :
595 if ( !osrfHashGet(_f, "primitive") )
596 osrfHashSet(_f, "number", "primitive");
598 osrfHashSet(_f,"NUMERIC", "datatype");
601 case DBI_TYPE_STRING :
602 if ( !osrfHashGet(_f, "primitive") )
603 osrfHashSet(_f,"string", "primitive");
605 osrfHashSet(_f,"TEXT", "datatype");
608 case DBI_TYPE_DATETIME :
609 if ( !osrfHashGet(_f, "primitive") )
610 osrfHashSet(_f,"string", "primitive");
612 osrfHashSet(_f,"TIMESTAMP", "datatype");
615 case DBI_TYPE_BINARY :
616 if ( !osrfHashGet(_f, "primitive") )
617 osrfHashSet(_f,"string", "primitive");
619 osrfHashSet(_f,"BYTEA", "datatype");
624 "Setting [%s] to primitive [%s] and datatype [%s]...",
626 osrfHashGet(_f, "primitive"),
627 osrfHashGet(_f, "datatype")
631 } // end while loop for traversing columns of result
632 dbi_result_free(result);
634 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", classname);
636 } // end for each class in IDL
638 buffer_free( query_buf );
639 osrfHashIteratorFree( class_itr );
640 child_initialized = 1;
645 @brief Install a database driver.
646 @param conn Pointer to a database driver.
648 The driver is used to process quoted strings correctly.
650 This function is a sleazy hack, intended @em only for testing and debugging without
651 actually connecting to a database. Any real server process should initialize the
652 database connection by calling osrfAppChildInit().
654 void set_cstore_dbi_conn( dbi_conn conn ) {
655 dbhandle = writehandle = conn;
659 @brief Free an osrfHash that stores a transaction ID.
660 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
662 This function is a callback, to be called by the application session when it ends.
663 The application session stores the osrfHash via an opaque pointer.
665 If the osrfHash contains an entry for the key "xact_id", it means that an
666 uncommitted transaction is pending. Roll it back.
668 void userDataFree( void* blob ) {
669 osrfHash* hash = (osrfHash*) blob;
670 if( osrfHashGet( hash, "xact_id" ) && writehandle )
671 dbi_conn_query( writehandle, "ROLLBACK;" );
673 osrfHashFree( hash );
677 @name Managing session data
678 @brief Maintain data stored via the userData pointer of the application session.
680 Currently, session-level data is stored in an osrfHash. Other arrangements are
681 possible, and some would be more efficient. The application session calls a
682 callback function to free userData before terminating.
684 Currently, the only data we store at the session level is the transaction id. By this
685 means we can ensure that any pending transactions are rolled back before the application
691 @brief Free an item in the application session's userData.
692 @param key The name of a key for an osrfHash.
693 @param item An opaque pointer to the item associated with the key.
695 We store an osrfHash as userData with the application session, and arrange (by
696 installing userDataFree() as a different callback) for the session to free that
697 osrfHash before terminating.
699 This function is a callback for freeing items in the osrfHash. Currently we store
701 - Transaction id of a pending transaction; a character string. Key: "xact_id".
702 - Authkey; a character string. Key: "authkey".
703 - User object from the authentication server; a jsonObject. Key: "user_login".
705 If we ever store anything else in userData, we will need to revisit this function so
706 that it will free whatever else needs freeing.
708 static void sessionDataFree( char* key, void* item ) {
709 if ( !strcmp( key, "xact_id" )
710 || !strcmp( key, "authkey" ) ) {
712 } else if( !strcmp( key, "user_login" ) )
713 jsonObjectFree( (jsonObject*) item );
717 @brief Save a transaction id.
718 @param ctx Pointer to the method context.
720 Save the session_id of the current application session as a transaction id.
722 static void setXactId( osrfMethodContext* ctx ) {
723 if( ctx && ctx->session ) {
724 osrfAppSession* session = ctx->session;
726 osrfHash* cache = session->userData;
728 // If the session doesn't already have a hash, create one. Make sure
729 // that the application session frees the hash when it terminates.
730 if( NULL == cache ) {
731 session->userData = cache = osrfNewHash();
732 osrfHashSetCallback( cache, &sessionDataFree );
733 ctx->session->userDataFree = &userDataFree;
736 // Save the transaction id in the hash, with the key "xact_id"
737 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
742 @brief Get the transaction ID for the current transaction, if any.
743 @param ctx Pointer to the method context.
744 @return Pointer to the transaction ID.
746 The return value points to an internal buffer, and will become invalid upon issuing
747 a commit or rollback.
749 static inline const char* getXactId( osrfMethodContext* ctx ) {
750 if( ctx && ctx->session && ctx->session->userData )
751 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
757 @brief Clear the current transaction id.
758 @param ctx Pointer to the method context.
760 static inline void clearXactId( osrfMethodContext* ctx ) {
761 if( ctx && ctx->session && ctx->session->userData )
762 osrfHashRemove( ctx->session->userData, "xact_id" );
768 @brief Save the user's login in the userData for the current application session.
769 @param ctx Pointer to the method context.
770 @param user_login Pointer to the user login object to be cached (we cache the original,
773 If @a user_login is NULL, remove the user login if one is already cached.
775 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
776 if( ctx && ctx->session ) {
777 osrfAppSession* session = ctx->session;
779 osrfHash* cache = session->userData;
781 // If the session doesn't already have a hash, create one. Make sure
782 // that the application session frees the hash when it terminates.
783 if( NULL == cache ) {
784 session->userData = cache = osrfNewHash();
785 osrfHashSetCallback( cache, &sessionDataFree );
786 ctx->session->userDataFree = &userDataFree;
790 osrfHashSet( cache, user_login, "user_login" );
792 osrfHashRemove( cache, "user_login" );
797 @brief Get the user login object for the current application session, if any.
798 @param ctx Pointer to the method context.
799 @return Pointer to the user login object if found; otherwise NULL.
801 The user login object was returned from the authentication server, and then cached so
802 we don't have to call the authentication server again for the same user.
804 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
805 if( ctx && ctx->session && ctx->session->userData )
806 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
812 @brief Save a copy of an authkey in the userData of the current application session.
813 @param ctx Pointer to the method context.
814 @param authkey The authkey to be saved.
816 If @a authkey is NULL, remove the authkey if one is already cached.
818 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
819 if( ctx && ctx->session && authkey ) {
820 osrfAppSession* session = ctx->session;
821 osrfHash* cache = session->userData;
823 // If the session doesn't already have a hash, create one. Make sure
824 // that the application session frees the hash when it terminates.
825 if( NULL == cache ) {
826 session->userData = cache = osrfNewHash();
827 osrfHashSetCallback( cache, &sessionDataFree );
828 ctx->session->userDataFree = &userDataFree;
831 // Save the transaction id in the hash, with the key "xact_id"
832 if( authkey && *authkey )
833 osrfHashSet( cache, strdup( authkey ), "authkey" );
835 osrfHashRemove( cache, "authkey" );
840 @brief Reset the login timeout.
841 @param authkey The authentication key for the current login session.
842 @param now The current time.
843 @return Zero if successful, or 1 if not.
845 Tell the authentication server to reset the timeout so that the login session won't
846 expire for a while longer.
848 We could dispense with the @a now parameter by calling time(). But we just called
849 time() in order to decide whether to reset the timeout, so we might as well reuse
850 the result instead of calling time() again.
852 static int reset_timeout( const char* authkey, time_t now ) {
853 jsonObject* auth_object = jsonNewObject( authkey );
855 // Ask the authentication server to reset the timeout. It returns an event
856 // indicating success or failure.
857 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
858 "open-ils.auth.session.reset_timeout", auth_object );
859 jsonObjectFree(auth_object);
861 if( !result || result->type != JSON_HASH ) {
862 osrfLogError( OSRF_LOG_MARK,
863 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
864 jsonObjectFree( result );
865 return 1; // Not the right sort of object returned
868 const jsonObject* ilsevent = jsonObjectGetKey( result, "ilsevent" );
869 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
870 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
871 jsonObjectFree( result );
872 return 1; // Return code from method not available
875 if( jsonObjectGetNumber( ilsevent ) != 0.0 ){
876 const char* desc = jsonObjectGetString( jsonObjectGetKey( result, "desc" ) );
878 desc = "(No reason available)"; // failsafe; shouldn't happen
879 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
880 jsonObjectFree( result );
884 // Revise our local proxy for the timeout deadline
885 // by a smallish fraction of the timeout interval
886 const char* timeout = jsonObjectGetString( jsonObjectGetKey( result, "payload" ) );
888 timeout = "1"; // failsafe; shouldn't happen
889 time_next_reset = now + atoi( timeout ) / 15;
891 jsonObjectFree( result );
892 return 0; // Successfully reset timeout
896 @brief Get the authkey string for the current application session, if any.
897 @param ctx Pointer to the method context.
898 @return Pointer to the cached authkey if found; otherwise NULL.
900 If present, the authkey string was cached from a previous method call.
902 static const char* getAuthkey( osrfMethodContext* ctx ) {
903 if( ctx && ctx->session && ctx->session->userData ) {
904 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
906 // Possibly reset the authentication timeout to keep the login alive. We do so
907 // no more than once per method call, and not at all if it has been only a short
908 // time since the last reset.
910 // Here we reset explicitly, if at all. We also implicitly reset the timeout
911 // whenever we call the "open-ils.auth.session.retrieve" method.
912 if( timeout_needs_resetting ) {
913 time_t now = time( NULL );
914 if( now >= time_next_reset && reset_timeout( authkey, now ) )
915 authkey = NULL; // timeout has apparently expired already
918 timeout_needs_resetting = 0;
927 @brief Implement the transaction.begin method.
928 @param ctx Pointer to the method context.
929 @return Zero if successful, or -1 upon error.
931 Start a transaction. Save a transaction ID for future reference.
934 - authkey (PCRUD only)
936 Return to client: Transaction ID
938 int beginTransaction ( osrfMethodContext* ctx ) {
939 if(osrfMethodVerifyContext( ctx )) {
940 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
945 timeout_needs_resetting = 1;
946 const jsonObject* user = verifyUserPCRUD( ctx );
951 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
953 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
954 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
955 "osrfMethodException", ctx->request, "Error starting transaction" );
959 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
960 osrfAppRespondComplete( ctx, ret );
967 @brief Implement the savepoint.set method.
968 @param ctx Pointer to the method context.
969 @return Zero if successful, or -1 if not.
971 Issue a SAVEPOINT to the database server.
974 - authkey (PCRUD only)
977 Return to client: Savepoint name
979 int setSavepoint ( osrfMethodContext* ctx ) {
980 if(osrfMethodVerifyContext( ctx )) {
981 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
987 timeout_needs_resetting = 1;
989 const jsonObject* user = verifyUserPCRUD( ctx );
994 // Verify that a transaction is pending
995 const char* trans_id = getXactId( ctx );
996 if( NULL == trans_id ) {
997 osrfAppSessionStatus(
999 OSRF_STATUS_INTERNALSERVERERROR,
1000 "osrfMethodException",
1002 "No active transaction -- required for savepoints"
1007 // Get the savepoint name from the method params
1008 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1010 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
1014 "%s: Error creating savepoint %s in transaction %s",
1019 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1020 "osrfMethodException", ctx->request, "Error creating savepoint" );
1023 jsonObject* ret = jsonNewObject(spName);
1024 osrfAppRespondComplete( ctx, ret );
1025 jsonObjectFree(ret);
1031 @brief Implement the savepoint.release method.
1032 @param ctx Pointer to the method context.
1033 @return Zero if successful, or -1 if not.
1035 Issue a RELEASE SAVEPOINT to the database server.
1038 - authkey (PCRUD only)
1041 Return to client: Savepoint name
1043 int releaseSavepoint ( osrfMethodContext* ctx ) {
1044 if(osrfMethodVerifyContext( ctx )) {
1045 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1051 timeout_needs_resetting = 1;
1053 const jsonObject* user = verifyUserPCRUD( ctx );
1058 // Verify that a transaction is pending
1059 const char* trans_id = getXactId( ctx );
1060 if( NULL == trans_id ) {
1061 osrfAppSessionStatus(
1063 OSRF_STATUS_INTERNALSERVERERROR,
1064 "osrfMethodException",
1066 "No active transaction -- required for savepoints"
1071 // Get the savepoint name from the method params
1072 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1074 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
1078 "%s: Error releasing savepoint %s in transaction %s",
1083 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1084 "osrfMethodException", ctx->request, "Error releasing savepoint" );
1087 jsonObject* ret = jsonNewObject(spName);
1088 osrfAppRespondComplete( ctx, ret );
1089 jsonObjectFree(ret);
1095 @brief Implement the savepoint.rollback method.
1096 @param ctx Pointer to the method context.
1097 @return Zero if successful, or -1 if not.
1099 Issue a ROLLBACK TO SAVEPOINT to the database server.
1102 - authkey (PCRUD only)
1105 Return to client: Savepoint name
1107 int rollbackSavepoint ( osrfMethodContext* ctx ) {
1108 if(osrfMethodVerifyContext( ctx )) {
1109 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1115 timeout_needs_resetting = 1;
1117 const jsonObject* user = verifyUserPCRUD( ctx );
1122 // Verify that a transaction is pending
1123 const char* trans_id = getXactId( ctx );
1124 if( NULL == trans_id ) {
1125 osrfAppSessionStatus(
1127 OSRF_STATUS_INTERNALSERVERERROR,
1128 "osrfMethodException",
1130 "No active transaction -- required for savepoints"
1135 // Get the savepoint name from the method params
1136 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1138 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
1142 "%s: Error rolling back savepoint %s in transaction %s",
1147 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1148 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1151 jsonObject* ret = jsonNewObject(spName);
1152 osrfAppRespondComplete( ctx, ret );
1153 jsonObjectFree(ret);
1159 @brief Implement the transaction.commit method.
1160 @param ctx Pointer to the method context.
1161 @return Zero if successful, or -1 if not.
1163 Issue a COMMIT to the database server.
1166 - authkey (PCRUD only)
1168 Return to client: Transaction ID.
1170 int commitTransaction ( osrfMethodContext* ctx ) {
1171 if(osrfMethodVerifyContext( ctx )) {
1172 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1177 timeout_needs_resetting = 1;
1178 const jsonObject* user = verifyUserPCRUD( ctx );
1183 // Verify that a transaction is pending
1184 const char* trans_id = getXactId( ctx );
1185 if( NULL == trans_id ) {
1186 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1187 "osrfMethodException", ctx->request, "No active transaction to commit" );
1191 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
1193 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
1194 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1195 "osrfMethodException", ctx->request, "Error committing transaction" );
1198 jsonObject* ret = jsonNewObject( trans_id );
1199 osrfAppRespondComplete( ctx, ret );
1200 jsonObjectFree(ret);
1207 @brief Implement the transaction.rollback method.
1208 @param ctx Pointer to the method context.
1209 @return Zero if successful, or -1 if not.
1211 Issue a ROLLBACK to the database server.
1214 - authkey (PCRUD only)
1216 Return to client: Transaction ID
1218 int rollbackTransaction ( osrfMethodContext* ctx ) {
1219 if(osrfMethodVerifyContext( ctx )) {
1220 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1225 timeout_needs_resetting = 1;
1226 const jsonObject* user = verifyUserPCRUD( ctx );
1231 // Verify that a transaction is pending
1232 const char* trans_id = getXactId( ctx );
1233 if( NULL == trans_id ) {
1234 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1235 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1239 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
1241 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
1242 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1243 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1246 jsonObject* ret = jsonNewObject( trans_id );
1247 osrfAppRespondComplete( ctx, ret );
1248 jsonObjectFree(ret);
1255 @brief Implement the class-specific methods.
1256 @param ctx Pointer to the method context.
1257 @return Zero if successful, or -1 if not.
1259 Branch on the method type: create, retrieve, update, delete, search, and id_list.
1261 The method parameters and the type of value returned to the client depend on the method
1262 type. However, for PCRUD methods, the first method parameter should always be an
1265 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
1266 if(osrfMethodVerifyContext( ctx )) {
1267 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1271 int err = 0; // to be returned to caller
1272 jsonObject * obj = NULL; // to be returned to client
1275 timeout_needs_resetting = 1;
1278 // Get the method type so that we can branch on it
1279 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1280 const char* methodtype = osrfHashGet( method_meta, "methodtype" );
1282 if (!strcmp(methodtype, "create")) {
1283 obj = doCreate(ctx, &err);
1284 osrfAppRespondComplete( ctx, obj );
1286 else if (!strcmp(methodtype, "retrieve")) {
1287 obj = doRetrieve(ctx, &err);
1288 osrfAppRespondComplete( ctx, obj );
1290 else if (!strcmp(methodtype, "update")) {
1291 obj = doUpdate(ctx, &err);
1292 osrfAppRespondComplete( ctx, obj );
1294 else if (!strcmp(methodtype, "delete")) {
1295 obj = doDelete(ctx, &err);
1296 osrfAppRespondComplete( ctx, obj );
1298 else if (!strcmp(methodtype, "search")) {
1300 // Implement search method: return rows of the specified class that satisfy
1301 // a specified WHERE clause.
1303 // Method parameters:
1304 // - authkey (PCRUD only)
1305 // - WHERE clause, as jsonObject
1306 // - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1308 jsonObject* where_clause;
1309 jsonObject* rest_of_query;
1312 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1313 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1315 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1316 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1320 osrfHash* class_obj = osrfHashGet( method_meta, "class" );
1321 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
1326 // Return each row to the client (except that some may be suppressed by PCRUD)
1327 jsonObject* cur = 0;
1328 unsigned long res_idx = 0;
1329 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1331 if(!verifyObjectPCRUD(ctx, cur))
1334 osrfAppRespond( ctx, cur );
1336 osrfAppRespondComplete( ctx, NULL );
1338 } else if (!strcmp(methodtype, "id_list")) {
1340 // Implement the id_list method. Return the primary key values for all rows of the
1341 // relevant class that satisfy a specified WHERE clause. This method relies on the
1342 // assumption that every class has a primary key consisting of a single column.
1344 // Method parameters:
1345 // - authkey (PCRUD only)
1346 // - WHERE clause, as jsonObject
1347 // - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1349 jsonObject* where_clause;
1350 jsonObject* rest_of_query;
1352 // We use the where clause without change. But we need to massage the rest of the
1353 // query, so we work with a copy of it instead of modifying the original.
1356 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1357 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1359 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1360 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1363 // Eliminate certain SQL clauses, if present
1364 if ( rest_of_query ) {
1365 jsonObjectRemoveKey( rest_of_query, "select" );
1366 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1367 jsonObjectRemoveKey( rest_of_query, "flesh" );
1368 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
1370 rest_of_query = jsonNewObjectType( JSON_HASH );
1373 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1375 // Get the class metadata
1376 osrfHash* class_obj = osrfHashGet( method_meta, "class" );
1378 // Build a SELECT list containing just the primary key,
1379 // i.e. like { "classname":["keyname"] }
1380 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1381 jsonObjectPush( col_list_obj, // Load array with name of primary key
1382 jsonNewObject( osrfHashGet( class_obj, "primarykey" ) ) );
1383 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1384 jsonObjectSetKey( select_clause, osrfHashGet( class_obj, "classname" ), col_list_obj );
1386 jsonObjectSetKey( rest_of_query, "select", select_clause );
1389 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
1391 jsonObjectFree( rest_of_query );
1395 // Return each primary key value to the client
1397 unsigned long res_idx = 0;
1398 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1400 if(!verifyObjectPCRUD(ctx, cur))
1403 osrfAppRespond( ctx,
1404 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
1407 osrfAppRespondComplete( ctx, NULL );
1410 osrfAppRespondComplete( ctx, obj ); // should be unreachable...
1413 jsonObjectFree(obj);
1419 @brief Verify that we have a valid class reference.
1420 @param ctx Pointer to the method context.
1421 @param param Pointer to the method parameters.
1422 @return 1 if the class reference is valid, or zero if it isn't.
1424 The class of the method params must match the class to which the method id devoted.
1425 For PCRUD there are additional restrictions.
1427 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1429 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1430 osrfHash* class = osrfHashGet( method_meta, "class" );
1432 // Compare the method's class to the parameters' class
1433 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1435 // Oops -- they don't match. Complain.
1436 growing_buffer* msg = buffer_init(128);
1439 "%s: %s method for type %s was passed a %s",
1441 osrfHashGet(method_meta, "methodtype"),
1442 osrfHashGet(class, "classname"),
1443 param->classname ? param->classname : "(null)"
1446 char* m = buffer_release(msg);
1447 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1456 ret = verifyObjectPCRUD( ctx, param );
1465 @brief (PCRUD only) Verify that the user is properly logged in.
1466 @param ctx Pointer to the method context.
1467 @return If the user is logged in, a pointer to the user object from the authentication
1468 server; otherwise NULL.
1470 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1472 // Get the authkey (the first method parameter)
1473 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1475 // See if we have the same authkey, and a user object,
1476 // locally cached from a previous call
1477 const char* cached_authkey = getAuthkey( ctx );
1478 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1479 const jsonObject* cached_user = getUserLogin( ctx );
1484 // We have no matching authentication data in the cache. Authenticate from scratch.
1485 jsonObject* auth_object = jsonNewObject(auth);
1487 // Fetch the user object from the authentication server
1488 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1490 jsonObjectFree(auth_object);
1492 if (!user->classname || strcmp(user->classname, "au")) {
1494 growing_buffer* msg = buffer_init(128);
1497 "%s: permacrud received a bad auth token: %s",
1502 char* m = buffer_release(msg);
1503 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1507 jsonObjectFree(user);
1511 setUserLogin( ctx, user );
1512 setAuthkey( ctx, auth );
1514 // Allow ourselves up to a second before we have to reset the login timeout.
1515 // It would be nice to use some fraction of the timeout interval enforced by the
1516 // authentication server, but that value is not readily available at this point.
1517 // Instead, we use a conservative default interval.
1518 time_next_reset = time( NULL ) + 1;
1523 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1525 dbhandle = writehandle;
1527 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1528 osrfHash* class = osrfHashGet( method_metadata, "class" );
1529 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1532 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
1533 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1534 } else if ( *method_type == 'u' || *method_type == 'd' ) {
1535 fetch = 1; // MUST go to the db for the object for update and delete
1538 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
1541 // No permacrud for this method type on this class
1543 growing_buffer* msg = buffer_init(128);
1546 "%s: %s on class %s has no permacrud IDL entry",
1548 osrfHashGet(method_metadata, "methodtype"),
1549 osrfHashGet(class, "classname")
1552 char* m = buffer_release(msg);
1553 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1554 "osrfMethodException", ctx->request, m );
1561 const jsonObject* user = verifyUserPCRUD( ctx );
1565 int userid = atoi( oilsFMGetString( user, "id" ) );
1567 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1568 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1569 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1571 osrfStringArray* context_org_array = osrfNewStringArray(1);
1574 char* pkey_value = NULL;
1575 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1576 osrfLogDebug( OSRF_LOG_MARK,
1577 "global-level permissions required, fetching top of the org tree" );
1579 // check for perm at top of org tree
1580 char* org_tree_root_id = org_tree_root( ctx );
1581 if( org_tree_root_id ) {
1582 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1583 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1585 osrfStringArrayFree( context_org_array );
1590 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1591 "fetching context org ids" );
1592 const char* pkey = osrfHashGet(class, "primarykey");
1593 jsonObject *param = NULL;
1595 if (obj->classname) {
1596 pkey_value = oilsFMGetString( obj, pkey );
1598 param = jsonObjectClone(obj);
1599 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1602 pkey_value = jsonObjectToSimpleString( obj );
1604 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1605 "of %s and retrieving from the database", pkey_value );
1609 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1610 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1611 jsonObjectFree(_tmp_params);
1613 param = jsonObjectExtractIndex(_list, 0);
1614 jsonObjectFree(_list);
1618 osrfLogDebug( OSRF_LOG_MARK,
1619 "Object not found in the database with primary key %s of %s",
1622 growing_buffer* msg = buffer_init(128);
1625 "%s: no object found with primary key %s of %s",
1631 char* m = buffer_release(msg);
1632 osrfAppSessionStatus(
1634 OSRF_STATUS_INTERNALSERVERERROR,
1635 "osrfMethodException",
1641 if (pkey_value) free(pkey_value);
1646 if (local_context->size > 0) {
1647 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1648 local_context->size);
1650 const char* lcontext = NULL;
1651 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1652 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1655 "adding class-local field %s (value: %s) to the context org list",
1657 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1662 if (foreign_context) {
1663 unsigned long class_count = osrfHashGetCount( foreign_context );
1664 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1666 if (class_count > 0) {
1668 osrfHash* fcontext = NULL;
1669 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1670 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1671 const char* class_name = osrfHashIteratorKey( class_itr );
1672 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1676 "%d foreign context fields(s) specified for class %s",
1677 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1681 char* foreign_pkey = osrfHashGet(fcontext, "field");
1682 char* foreign_pkey_value =
1683 oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1685 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1687 jsonObject* _list = doFieldmapperSearch(
1688 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1690 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1691 jsonObjectFree(_tmp_params);
1692 jsonObjectFree(_list);
1694 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1696 if (_fparam && jump_list) {
1697 const char* flink = NULL;
1699 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1700 free(foreign_pkey_value);
1702 osrfHash* foreign_link_hash =
1703 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1705 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1706 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1708 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1710 _list = doFieldmapperSearch(
1712 osrfHashGet( oilsIDL(),
1713 osrfHashGet( foreign_link_hash, "class" ) ),
1719 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1720 jsonObjectFree(_tmp_params);
1721 jsonObjectFree(_list);
1727 growing_buffer* msg = buffer_init(128);
1730 "%s: no object found with primary key %s of %s",
1736 char* m = buffer_release(msg);
1737 osrfAppSessionStatus(
1739 OSRF_STATUS_INTERNALSERVERERROR,
1740 "osrfMethodException",
1746 osrfHashIteratorFree(class_itr);
1747 free(foreign_pkey_value);
1748 jsonObjectFree(param);
1753 free(foreign_pkey_value);
1756 const char* foreign_field = NULL;
1757 while ( (foreign_field = osrfStringArrayGetString(
1758 osrfHashGet(fcontext,"context" ), j++)) ) {
1759 osrfStringArrayAdd( context_org_array,
1760 oilsFMGetString( _fparam, foreign_field ) );
1763 "adding foreign class %s field %s (value: %s) to the context org list",
1766 osrfStringArrayGetString(
1767 context_org_array, context_org_array->size - 1)
1771 jsonObjectFree(_fparam);
1774 osrfHashIteratorFree( class_itr );
1778 jsonObjectFree(param);
1781 const char* context_org = NULL;
1782 const char* perm = NULL;
1785 if (permission->size == 0) {
1786 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1791 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1793 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1799 "Checking object permission [%s] for user %d "
1800 "on object %s (class %s) at org %d",
1804 osrfHashGet(class, "classname"),
1808 result = dbi_conn_queryf(
1810 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1813 osrfHashGet(class, "classname"),
1821 "Received a result for object permission [%s] "
1822 "for user %d on object %s (class %s) at org %d",
1826 osrfHashGet(class, "classname"),
1830 if (dbi_result_first_row(result)) {
1831 jsonObject* return_val = oilsMakeJSONFromResult( result );
1832 const char* has_perm = jsonObjectGetString(
1833 jsonObjectGetKeyConst(return_val, "has_perm") );
1837 "Status of object permission [%s] for user %d "
1838 "on object %s (class %s) at org %d is %s",
1842 osrfHashGet(class, "classname"),
1847 if ( *has_perm == 't' ) OK = 1;
1848 jsonObjectFree(return_val);
1851 dbi_result_free(result);
1857 osrfLogDebug( OSRF_LOG_MARK,
1858 "Checking non-object permission [%s] for user %d at org %d",
1859 perm, userid, atoi(context_org) );
1860 result = dbi_conn_queryf(
1862 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1869 osrfLogDebug( OSRF_LOG_MARK,
1870 "Received a result for permission [%s] for user %d at org %d",
1871 perm, userid, atoi(context_org) );
1872 if ( dbi_result_first_row(result) ) {
1873 jsonObject* return_val = oilsMakeJSONFromResult( result );
1874 const char* has_perm = jsonObjectGetString(
1875 jsonObjectGetKeyConst(return_val, "has_perm") );
1876 osrfLogDebug( OSRF_LOG_MARK,
1877 "Status of permission [%s] for user %d at org %d is [%s]",
1878 perm, userid, atoi(context_org), has_perm );
1879 if ( *has_perm == 't' )
1881 jsonObjectFree(return_val);
1884 dbi_result_free(result);
1893 if (pkey_value) free(pkey_value);
1894 osrfStringArrayFree(context_org_array);
1900 @brief Look up the root of the org_unit tree.
1901 @param ctx Pointer to the method context.
1902 @return The id of the root org unit, as a character string.
1904 Query actor.org_unit where parent_ou is null, and return the id as a string.
1906 This function assumes that there is only one root org unit, i.e. that we
1907 have a single tree, not a forest.
1909 The calling code is responsible for freeing the returned string.
1911 static char* org_tree_root( osrfMethodContext* ctx ) {
1913 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1914 static time_t last_lookup_time = 0;
1915 time_t current_time = time( NULL );
1917 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1918 // We successfully looked this up less than an hour ago.
1919 // It's not likely to have changed since then.
1920 return strdup( cached_root_id );
1922 last_lookup_time = current_time;
1925 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1926 jsonObject* result = doFieldmapperSearch(
1927 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1928 jsonObjectFree( where_clause );
1930 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1933 jsonObjectFree( result );
1935 growing_buffer* msg = buffer_init(128);
1936 OSRF_BUFFER_ADD( msg, MODULENAME );
1937 OSRF_BUFFER_ADD( msg,
1938 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1940 char* m = buffer_release(msg);
1941 osrfAppSessionStatus( ctx->session,
1942 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1945 cached_root_id[ 0 ] = '\0';
1949 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1950 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1952 jsonObjectFree( result );
1954 strcpy( cached_root_id, root_org_unit_id );
1955 return root_org_unit_id;
1959 @brief Create a JSON_HASH with a single key/value pair.
1960 @param key The key of the key/value pair.
1961 @param value the value of the key/value pair.
1962 @return Pointer to a newly created jsonObject of type JSON_HASH.
1964 The value of the key/value is either a string or (if @a value is NULL) a null.
1966 static jsonObject* single_hash( const char* key, const char* value ) {
1968 if( ! key ) key = "";
1970 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1971 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1977 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1979 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1981 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1982 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1984 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1985 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1988 if (!verifyObjectClass(ctx, target)) {
1993 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1995 const char* trans_id = getXactId( ctx );
1997 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1999 osrfAppSessionStatus(
2001 OSRF_STATUS_BADREQUEST,
2002 "osrfMethodException",
2004 "No active transaction -- required for CREATE"
2010 // The following test is harmless but redundant. If a class is
2011 // readonly, we don't register a create method for it.
2012 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2013 osrfAppSessionStatus(
2015 OSRF_STATUS_BADREQUEST,
2016 "osrfMethodException",
2018 "Cannot INSERT readonly class"
2024 // Set the last_xact_id
2025 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2027 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2028 trans_id, target->classname, index);
2029 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
2032 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2034 dbhandle = writehandle;
2036 osrfHash* fields = osrfHashGet(meta, "fields");
2037 char* pkey = osrfHashGet(meta, "primarykey");
2038 char* seq = osrfHashGet(meta, "sequence");
2040 growing_buffer* table_buf = buffer_init(128);
2041 growing_buffer* col_buf = buffer_init(128);
2042 growing_buffer* val_buf = buffer_init(128);
2044 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
2045 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
2046 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2047 buffer_add(val_buf,"VALUES (");
2051 osrfHash* field = NULL;
2052 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2053 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2055 const char* field_name = osrfHashIteratorKey( field_itr );
2057 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2060 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2063 if (field_object && field_object->classname) {
2064 value = oilsFMGetString(
2066 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
2068 } else if( field_object && JSON_BOOL == field_object->type ) {
2069 if( jsonBoolIsTrue( field_object ) )
2070 value = strdup( "t" );
2072 value = strdup( "f" );
2074 value = jsonObjectToSimpleString( field_object );
2080 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2081 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2084 buffer_add(col_buf, field_name);
2086 if (!field_object || field_object->type == JSON_NULL) {
2087 buffer_add( val_buf, "DEFAULT" );
2089 } else if ( !strcmp(get_primitive( field ), "number") ) {
2090 const char* numtype = get_datatype( field );
2091 if ( !strcmp( numtype, "INT8") ) {
2092 buffer_fadd( val_buf, "%lld", atoll(value) );
2094 } else if ( !strcmp( numtype, "INT") ) {
2095 buffer_fadd( val_buf, "%d", atoi(value) );
2097 } else if ( !strcmp( numtype, "NUMERIC") ) {
2098 buffer_fadd( val_buf, "%f", atof(value) );
2101 if ( dbi_conn_quote_string(writehandle, &value) ) {
2102 OSRF_BUFFER_ADD( val_buf, value );
2105 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
2106 osrfAppSessionStatus(
2108 OSRF_STATUS_INTERNALSERVERERROR,
2109 "osrfMethodException",
2111 "Error quoting string -- please see the error log for more details"
2114 buffer_free(table_buf);
2115 buffer_free(col_buf);
2116 buffer_free(val_buf);
2126 osrfHashIteratorFree( field_itr );
2128 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2129 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2131 char* table_str = buffer_release(table_buf);
2132 char* col_str = buffer_release(col_buf);
2133 char* val_str = buffer_release(val_buf);
2134 growing_buffer* sql = buffer_init(128);
2135 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2140 char* query = buffer_release(sql);
2142 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
2145 dbi_result result = dbi_conn_query(writehandle, query);
2147 jsonObject* obj = NULL;
2150 obj = jsonNewObject(NULL);
2153 "%s ERROR inserting %s object using query [%s]",
2155 osrfHashGet(meta, "fieldmapper"),
2158 osrfAppSessionStatus(
2160 OSRF_STATUS_INTERNALSERVERERROR,
2161 "osrfMethodException",
2163 "INSERT error -- please see the error log for more details"
2168 char* id = oilsFMGetString(target, pkey);
2170 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
2171 growing_buffer* _id = buffer_init(10);
2172 buffer_fadd(_id, "%lld", new_id);
2173 id = buffer_release(_id);
2176 // Find quietness specification, if present
2177 const char* quiet_str = NULL;
2179 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2181 quiet_str = jsonObjectGetString( quiet_obj );
2184 if( str_is_true( quiet_str ) ) { // if quietness is specified
2185 obj = jsonNewObject(id);
2189 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2190 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
2192 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
2194 jsonObjectFree( where_clause );
2199 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
2202 jsonObjectFree( list );
2215 @brief Implement the retrieve method.
2216 @param ctx Pointer to the method context.
2217 @param err Pointer through which to return an error code.
2218 @return If successful, a pointer to the result to be returned to the client;
2221 From the method's class, fetch a row with a specified value in the primary key. This
2222 method relies on the database design convention that a primary key consists of a single
2226 - authkey (PCRUD only)
2227 - value of the primary key for the desired row, for building the WHERE clause
2228 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2230 Return to client: One row from the query.
2232 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
2242 // Get the class metadata
2243 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2245 // Get the value of the primary key, from a method parameter
2246 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos);
2250 "%s retrieving %s object with primary key value of %s",
2252 osrfHashGet( class_def, "fieldmapper" ),
2253 jsonObjectGetString( id_obj )
2256 // Build a WHERE clause based on the key value
2257 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2260 osrfHashGet( class_def, "primarykey" ), // name of key column
2261 jsonObjectClone( id_obj ) // value of key column
2264 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
2267 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
2269 jsonObjectFree( where_clause );
2273 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2274 jsonObjectFree( list );
2277 if(!verifyObjectPCRUD(ctx, obj)) {
2278 jsonObjectFree(obj);
2281 growing_buffer* msg = buffer_init(128);
2282 OSRF_BUFFER_ADD( msg, MODULENAME );
2283 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2285 char* m = buffer_release(msg);
2286 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2298 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
2299 growing_buffer* val_buf = buffer_init(32);
2300 const char* numtype = get_datatype( field );
2302 if ( !strncmp( numtype, "INT", 3 ) ) {
2303 if (value->type == JSON_NUMBER)
2304 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2305 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2307 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2310 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
2311 if (value->type == JSON_NUMBER)
2312 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2314 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2318 // Presumably this was really intended ot be a string, so quote it
2319 char* str = jsonObjectToSimpleString( value );
2320 if ( dbi_conn_quote_string(dbhandle, &str) ) {
2321 OSRF_BUFFER_ADD( val_buf, str );
2324 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
2326 buffer_free(val_buf);
2331 return buffer_release(val_buf);
2334 static char* searchINPredicate (const char* class_alias, osrfHash* field,
2335 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2336 growing_buffer* sql_buf = buffer_init(32);
2342 osrfHashGet(field, "name")
2346 buffer_add(sql_buf, "IN (");
2347 } else if (!(strcasecmp(op,"not in"))) {
2348 buffer_add(sql_buf, "NOT IN (");
2350 buffer_add(sql_buf, "IN (");
2353 if (node->type == JSON_HASH) {
2354 // subquery predicate
2355 char* subpred = buildQuery( ctx, node, SUBSELECT );
2357 buffer_free( sql_buf );
2361 buffer_add(sql_buf, subpred);
2364 } else if (node->type == JSON_ARRAY) {
2365 // literal value list
2366 int in_item_index = 0;
2367 int in_item_first = 1;
2368 const jsonObject* in_item;
2369 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
2374 buffer_add(sql_buf, ", ");
2377 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2378 osrfLogError( OSRF_LOG_MARK,
2379 "%s: Expected string or number within IN list; found %s",
2380 MODULENAME, json_type( in_item->type ) );
2381 buffer_free(sql_buf);
2385 // Append the literal value -- quoted if not a number
2386 if ( JSON_NUMBER == in_item->type ) {
2387 char* val = jsonNumberToDBString( field, in_item );
2388 OSRF_BUFFER_ADD( sql_buf, val );
2391 } else if ( !strcmp( get_primitive( field ), "number") ) {
2392 char* val = jsonNumberToDBString( field, in_item );
2393 OSRF_BUFFER_ADD( sql_buf, val );
2397 char* key_string = jsonObjectToSimpleString(in_item);
2398 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
2399 OSRF_BUFFER_ADD( sql_buf, key_string );
2402 osrfLogError(OSRF_LOG_MARK,
2403 "%s: Error quoting key string [%s]", MODULENAME, key_string);
2405 buffer_free(sql_buf);
2411 if( in_item_first ) {
2412 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
2413 buffer_free( sql_buf );
2417 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2418 MODULENAME, json_type( node->type ) );
2419 buffer_free(sql_buf);
2423 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2425 return buffer_release(sql_buf);
2428 // Receive a JSON_ARRAY representing a function call. The first
2429 // entry in the array is the function name. The rest are parameters.
2430 static char* searchValueTransform( const jsonObject* array ) {
2432 if( array->size < 1 ) {
2433 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
2437 // Get the function name
2438 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2439 if( func_item->type != JSON_STRING ) {
2440 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2441 MODULENAME, json_type( func_item->type ) );
2445 growing_buffer* sql_buf = buffer_init(32);
2447 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2448 OSRF_BUFFER_ADD( sql_buf, "( " );
2450 // Get the parameters
2451 int func_item_index = 1; // We already grabbed the zeroth entry
2452 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2454 // Add a separator comma, if we need one
2455 if( func_item_index > 2 )
2456 buffer_add( sql_buf, ", " );
2458 // Add the current parameter
2459 if (func_item->type == JSON_NULL) {
2460 buffer_add( sql_buf, "NULL" );
2462 char* val = jsonObjectToSimpleString(func_item);
2463 if ( dbi_conn_quote_string(dbhandle, &val) ) {
2464 OSRF_BUFFER_ADD( sql_buf, val );
2467 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2468 buffer_free(sql_buf);
2475 buffer_add( sql_buf, " )" );
2477 return buffer_release(sql_buf);
2480 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
2481 const jsonObject* node, const char* op) {
2483 if( ! is_good_operator( op ) ) {
2484 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2488 char* val = searchValueTransform(node);
2492 growing_buffer* sql_buf = buffer_init(32);
2497 osrfHashGet(field, "name"),
2504 return buffer_release(sql_buf);
2507 // class_alias is a class name or other table alias
2508 // field is a field definition as stored in the IDL
2509 // node comes from the method parameter, and may represent an entry in the SELECT list
2510 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
2511 growing_buffer* sql_buf = buffer_init(32);
2513 const char* field_transform = jsonObjectGetString(
2514 jsonObjectGetKeyConst( node, "transform" ) );
2515 const char* transform_subcolumn = jsonObjectGetString(
2516 jsonObjectGetKeyConst( node, "result_field" ) );
2518 if(transform_subcolumn) {
2519 if( ! is_identifier( transform_subcolumn ) ) {
2520 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2521 MODULENAME, transform_subcolumn );
2522 buffer_free( sql_buf );
2525 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2528 if (field_transform) {
2530 if( ! is_identifier( field_transform ) ) {
2531 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2532 MODULENAME, field_transform );
2533 buffer_free( sql_buf );
2537 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2538 field_transform, class_alias, osrfHashGet(field, "name"));
2539 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2542 if( array->type != JSON_ARRAY ) {
2543 osrfLogError( OSRF_LOG_MARK,
2544 "%s: Expected JSON_ARRAY for function params; found %s",
2545 MODULENAME, json_type( array->type ) );
2546 buffer_free( sql_buf );
2549 int func_item_index = 0;
2550 jsonObject* func_item;
2551 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2553 char* val = jsonObjectToSimpleString(func_item);
2556 buffer_add( sql_buf, ",NULL" );
2557 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
2558 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2559 OSRF_BUFFER_ADD( sql_buf, val );
2561 osrfLogError( OSRF_LOG_MARK,
2562 "%s: Error quoting key string [%s]", MODULENAME, val);
2564 buffer_free(sql_buf);
2571 buffer_add( sql_buf, " )" );
2574 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2577 if (transform_subcolumn)
2578 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2580 return buffer_release(sql_buf);
2583 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2584 const jsonObject* node, const char* op ) {
2586 if( ! is_good_operator( op ) ) {
2587 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2591 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2592 if( ! field_transform )
2595 int extra_parens = 0; // boolean
2597 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2598 if ( ! value_obj ) {
2599 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2601 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2603 free(field_transform);
2607 } else if ( value_obj->type == JSON_ARRAY ) {
2608 value = searchValueTransform( value_obj );
2610 osrfLogError(OSRF_LOG_MARK,
2611 "%s: Error building value transform for field transform", MODULENAME);
2612 free( field_transform );
2615 } else if ( value_obj->type == JSON_HASH ) {
2616 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2618 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2620 free(field_transform);
2624 } else if ( value_obj->type == JSON_NUMBER ) {
2625 value = jsonNumberToDBString( field, value_obj );
2626 } else if ( value_obj->type == JSON_NULL ) {
2627 osrfLogError( OSRF_LOG_MARK,
2628 "%s: Error building predicate for field transform: null value", MODULENAME );
2629 free(field_transform);
2631 } else if ( value_obj->type == JSON_BOOL ) {
2632 osrfLogError( OSRF_LOG_MARK,
2633 "%s: Error building predicate for field transform: boolean value", MODULENAME );
2634 free(field_transform);
2637 if ( !strcmp( get_primitive( field ), "number") ) {
2638 value = jsonNumberToDBString( field, value_obj );
2640 value = jsonObjectToSimpleString( value_obj );
2641 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2642 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2645 free(field_transform);
2651 const char* left_parens = "";
2652 const char* right_parens = "";
2654 if( extra_parens ) {
2659 growing_buffer* sql_buf = buffer_init(32);
2663 "%s%s %s %s %s %s%s",
2674 free(field_transform);
2676 return buffer_release(sql_buf);
2679 static char* searchSimplePredicate (const char* op, const char* class_alias,
2680 osrfHash* field, const jsonObject* node) {
2682 if( ! is_good_operator( op ) ) {
2683 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2689 // Get the value to which we are comparing the specified column
2690 if (node->type != JSON_NULL) {
2691 if ( node->type == JSON_NUMBER ) {
2692 val = jsonNumberToDBString( field, node );
2693 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2694 val = jsonNumberToDBString( field, node );
2696 val = jsonObjectToSimpleString(node);
2701 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2702 // Value is not numeric; enclose it in quotes
2703 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2704 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2711 // Compare to a null value
2712 val = strdup( "NULL" );
2713 if (strcmp( op, "=" ))
2719 growing_buffer* sql_buf = buffer_init(32);
2720 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2721 char* pred = buffer_release( sql_buf );
2728 static char* searchBETWEENPredicate (const char* class_alias,
2729 osrfHash* field, const jsonObject* node) {
2731 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2732 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2734 if( NULL == y_node ) {
2735 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2738 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2739 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2746 if ( !strcmp( get_primitive( field ), "number") ) {
2747 x_string = jsonNumberToDBString(field, x_node);
2748 y_string = jsonNumberToDBString(field, y_node);
2751 x_string = jsonObjectToSimpleString(x_node);
2752 y_string = jsonObjectToSimpleString(y_node);
2753 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2754 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2755 MODULENAME, x_string, y_string);
2762 growing_buffer* sql_buf = buffer_init(32);
2763 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2764 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2768 return buffer_release(sql_buf);
2771 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2772 jsonObject* node, osrfMethodContext* ctx ) {
2775 if (node->type == JSON_ARRAY) { // equality IN search
2776 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2777 } else if (node->type == JSON_HASH) { // other search
2778 jsonIterator* pred_itr = jsonNewIterator( node );
2779 if( !jsonIteratorHasNext( pred_itr ) ) {
2780 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2781 MODULENAME, osrfHashGet(field, "name") );
2783 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2785 // Verify that there are no additional predicates
2786 if( jsonIteratorHasNext( pred_itr ) ) {
2787 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2788 MODULENAME, osrfHashGet(field, "name") );
2789 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2790 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2791 else if ( !(strcasecmp( pred_itr->key,"in" ))
2792 || !(strcasecmp( pred_itr->key,"not in" )) )
2793 pred = searchINPredicate(
2794 class_info->alias, field, pred_node, pred_itr->key, ctx );
2795 else if ( pred_node->type == JSON_ARRAY )
2796 pred = searchFunctionPredicate(
2797 class_info->alias, field, pred_node, pred_itr->key );
2798 else if ( pred_node->type == JSON_HASH )
2799 pred = searchFieldTransformPredicate(
2800 class_info, field, pred_node, pred_itr->key );
2802 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2804 jsonIteratorFree(pred_itr);
2806 } else if (node->type == JSON_NULL) { // IS NULL search
2807 growing_buffer* _p = buffer_init(64);
2810 "\"%s\".%s IS NULL",
2811 class_info->class_name,
2812 osrfHashGet(field, "name")
2814 pred = buffer_release(_p);
2815 } else { // equality search
2816 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2835 field : call_number,
2851 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2853 const jsonObject* working_hash;
2854 jsonObject* freeable_hash = NULL;
2856 if (join_hash->type == JSON_HASH) {
2857 working_hash = join_hash;
2858 } else if (join_hash->type == JSON_STRING) {
2859 // turn it into a JSON_HASH by creating a wrapper
2860 // around a copy of the original
2861 const char* _tmp = jsonObjectGetString( join_hash );
2862 freeable_hash = jsonNewObjectType(JSON_HASH);
2863 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2864 working_hash = freeable_hash;
2868 "%s: JOIN failed; expected JSON object type not found",
2874 growing_buffer* join_buf = buffer_init(128);
2875 const char* leftclass = left_info->class_name;
2877 jsonObject* snode = NULL;
2878 jsonIterator* search_itr = jsonNewIterator( working_hash );
2880 while ( (snode = jsonIteratorNext( search_itr )) ) {
2881 const char* right_alias = search_itr->key;
2883 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2885 class = right_alias;
2887 const ClassInfo* right_info = add_joined_class( right_alias, class );
2891 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2895 jsonIteratorFree( search_itr );
2896 buffer_free( join_buf );
2898 jsonObjectFree( freeable_hash );
2901 osrfHash* links = right_info->links;
2902 const char* table = right_info->source_def;
2904 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2905 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2907 if (field && !fkey) {
2908 // Look up the corresponding join column in the IDL.
2909 // The link must be defined in the child table,
2910 // and point to the right parent table.
2911 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2912 const char* reltype = NULL;
2913 const char* other_class = NULL;
2914 reltype = osrfHashGet( idl_link, "reltype" );
2915 if( reltype && strcmp( reltype, "has_many" ) )
2916 other_class = osrfHashGet( idl_link, "class" );
2917 if( other_class && !strcmp( other_class, leftclass ) )
2918 fkey = osrfHashGet( idl_link, "key" );
2922 "%s: JOIN failed. No link defined from %s.%s to %s",
2928 buffer_free(join_buf);
2930 jsonObjectFree(freeable_hash);
2931 jsonIteratorFree(search_itr);
2935 } else if (!field && fkey) {
2936 // Look up the corresponding join column in the IDL.
2937 // The link must be defined in the child table,
2938 // and point to the right parent table.
2939 osrfHash* left_links = left_info->links;
2940 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2941 const char* reltype = NULL;
2942 const char* other_class = NULL;
2943 reltype = osrfHashGet( idl_link, "reltype" );
2944 if( reltype && strcmp( reltype, "has_many" ) )
2945 other_class = osrfHashGet( idl_link, "class" );
2946 if( other_class && !strcmp( other_class, class ) )
2947 field = osrfHashGet( idl_link, "key" );
2951 "%s: JOIN failed. No link defined from %s.%s to %s",
2957 buffer_free(join_buf);
2959 jsonObjectFree(freeable_hash);
2960 jsonIteratorFree(search_itr);
2964 } else if (!field && !fkey) {
2965 osrfHash* left_links = left_info->links;
2967 // For each link defined for the left class:
2968 // see if the link references the joined class
2969 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2970 osrfHash* curr_link = NULL;
2971 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2972 const char* other_class = osrfHashGet( curr_link, "class" );
2973 if( other_class && !strcmp( other_class, class ) ) {
2975 // In the IDL, the parent class doesn't always know then names of the child
2976 // columns that are pointing to it, so don't use that end of the link
2977 const char* reltype = osrfHashGet( curr_link, "reltype" );
2978 if( reltype && strcmp( reltype, "has_many" ) ) {
2979 // Found a link between the classes
2980 fkey = osrfHashIteratorKey( itr );
2981 field = osrfHashGet( curr_link, "key" );
2986 osrfHashIteratorFree( itr );
2988 if (!field || !fkey) {
2989 // Do another such search, with the classes reversed
2991 // For each link defined for the joined class:
2992 // see if the link references the left class
2993 osrfHashIterator* itr = osrfNewHashIterator( links );
2994 osrfHash* curr_link = NULL;
2995 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2996 const char* other_class = osrfHashGet( curr_link, "class" );
2997 if( other_class && !strcmp( other_class, leftclass ) ) {
2999 // In the IDL, the parent class doesn't know then names of the child
3000 // columns that are pointing to it, so don't use that end of the link
3001 const char* reltype = osrfHashGet( curr_link, "reltype" );
3002 if( reltype && strcmp( reltype, "has_many" ) ) {
3003 // Found a link between the classes
3004 field = osrfHashIteratorKey( itr );
3005 fkey = osrfHashGet( curr_link, "key" );
3010 osrfHashIteratorFree( itr );
3013 if (!field || !fkey) {
3016 "%s: JOIN failed. No link defined between %s and %s",
3021 buffer_free(join_buf);
3023 jsonObjectFree(freeable_hash);
3024 jsonIteratorFree(search_itr);
3029 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3031 if ( !strcasecmp(type,"left") ) {
3032 buffer_add(join_buf, " LEFT JOIN");
3033 } else if ( !strcasecmp(type,"right") ) {
3034 buffer_add(join_buf, " RIGHT JOIN");
3035 } else if ( !strcasecmp(type,"full") ) {
3036 buffer_add(join_buf, " FULL JOIN");
3038 buffer_add(join_buf, " INNER JOIN");
3041 buffer_add(join_buf, " INNER JOIN");
3044 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3045 table, right_alias, right_alias, field, left_info->alias, fkey);
3047 // Add any other join conditions as specified by "filter"
3048 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3050 const char* filter_op = jsonObjectGetString(
3051 jsonObjectGetKeyConst( snode, "filter_op" ) );
3052 if ( filter_op && !strcasecmp("or",filter_op) ) {
3053 buffer_add( join_buf, " OR " );
3055 buffer_add( join_buf, " AND " );
3058 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3060 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3061 OSRF_BUFFER_ADD( join_buf, jpred );
3066 "%s: JOIN failed. Invalid conditional expression.",
3069 jsonIteratorFree( search_itr );
3070 buffer_free( join_buf );
3072 jsonObjectFree( freeable_hash );
3077 buffer_add(join_buf, " ) ");
3079 // Recursively add a nested join, if one is present
3080 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3082 char* jpred = searchJOIN( join_filter, right_info );
3084 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3085 OSRF_BUFFER_ADD( join_buf, jpred );
3088 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
3089 jsonIteratorFree( search_itr );
3090 buffer_free( join_buf );
3092 jsonObjectFree( freeable_hash );
3099 jsonObjectFree(freeable_hash);
3100 jsonIteratorFree(search_itr);
3102 return buffer_release(join_buf);
3107 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3108 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3109 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3111 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3113 search_hash is the JSON expression of the conditions.
3114 meta is the class definition from the IDL, for the relevant table.
3115 opjoin_type indicates whether multiple conditions, if present, should be
3116 connected by AND or OR.
3117 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3118 to pass it to other functions -- and all they do with it is to use the session
3119 and request members to send error messages back to the client.
3123 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
3124 int opjoin_type, osrfMethodContext* ctx ) {
3128 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3129 "opjoin_type = %d, ctx addr = %p",
3132 class_info->class_def,
3137 growing_buffer* sql_buf = buffer_init(128);
3139 jsonObject* node = NULL;
3142 if ( search_hash->type == JSON_ARRAY ) {
3143 osrfLogDebug( OSRF_LOG_MARK,
3144 "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME );
3145 if( 0 == search_hash->size ) {
3148 "%s: Invalid predicate structure: empty JSON array",
3151 buffer_free( sql_buf );
3155 unsigned long i = 0;
3156 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
3160 if (opjoin_type == OR_OP_JOIN)
3161 buffer_add(sql_buf, " OR ");
3163 buffer_add(sql_buf, " AND ");
3166 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3168 buffer_free( sql_buf );
3172 buffer_fadd(sql_buf, "( %s )", subpred);
3176 } else if ( search_hash->type == JSON_HASH ) {
3177 osrfLogDebug( OSRF_LOG_MARK,
3178 "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME );
3179 jsonIterator* search_itr = jsonNewIterator( search_hash );
3180 if( !jsonIteratorHasNext( search_itr ) ) {
3183 "%s: Invalid predicate structure: empty JSON object",
3186 jsonIteratorFree( search_itr );
3187 buffer_free( sql_buf );
3191 while ( (node = jsonIteratorNext( search_itr )) ) {
3196 if (opjoin_type == OR_OP_JOIN)
3197 buffer_add(sql_buf, " OR ");
3199 buffer_add(sql_buf, " AND ");
3202 if ( '+' == search_itr->key[ 0 ] ) {
3204 // This plus sign prefixes a class name or other table alias;
3205 // make sure the table alias is in scope
3206 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3207 if( ! alias_info ) {
3210 "%s: Invalid table alias \"%s\" in WHERE clause",
3214 jsonIteratorFree( search_itr );
3215 buffer_free( sql_buf );
3219 if ( node->type == JSON_STRING ) {
3220 // It's the name of a column; make sure it belongs to the class
3221 const char* fieldname = jsonObjectGetString( node );
3222 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3225 "%s: Invalid column name \"%s\" in WHERE clause "
3226 "for table alias \"%s\"",
3231 jsonIteratorFree( search_itr );
3232 buffer_free( sql_buf );
3236 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3238 // It's something more complicated
3239 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3241 jsonIteratorFree( search_itr );
3242 buffer_free( sql_buf );
3246 buffer_fadd(sql_buf, "( %s )", subpred);
3249 } else if ( '-' == search_itr->key[ 0 ] ) {
3250 if ( !strcasecmp("-or",search_itr->key) ) {
3251 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3253 jsonIteratorFree( search_itr );
3254 buffer_free( sql_buf );
3258 buffer_fadd(sql_buf, "( %s )", subpred);
3260 } else if ( !strcasecmp("-and",search_itr->key) ) {
3261 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3263 jsonIteratorFree( search_itr );
3264 buffer_free( sql_buf );
3268 buffer_fadd(sql_buf, "( %s )", subpred);
3270 } else if ( !strcasecmp("-not",search_itr->key) ) {
3271 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3273 jsonIteratorFree( search_itr );
3274 buffer_free( sql_buf );
3278 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
3280 } else if ( !strcasecmp("-exists",search_itr->key) ) {
3281 char* subpred = buildQuery( ctx, node, SUBSELECT );
3283 jsonIteratorFree( search_itr );
3284 buffer_free( sql_buf );
3288 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
3290 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
3291 char* subpred = buildQuery( ctx, node, SUBSELECT );
3293 jsonIteratorFree( search_itr );
3294 buffer_free( sql_buf );
3298 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
3300 } else { // Invalid "minus" operator
3303 "%s: Invalid operator \"%s\" in WHERE clause",
3307 jsonIteratorFree( search_itr );
3308 buffer_free( sql_buf );
3314 const char* class = class_info->class_name;
3315 osrfHash* fields = class_info->fields;
3316 osrfHash* field = osrfHashGet( fields, search_itr->key );
3319 const char* table = class_info->source_def;
3322 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3325 table ? table : "?",
3328 jsonIteratorFree(search_itr);
3329 buffer_free(sql_buf);
3333 char* subpred = searchPredicate( class_info, field, node, ctx );
3335 buffer_free(sql_buf);
3336 jsonIteratorFree(search_itr);
3340 buffer_add( sql_buf, subpred );
3344 jsonIteratorFree(search_itr);
3347 // ERROR ... only hash and array allowed at this level
3348 char* predicate_string = jsonObjectToJSON( search_hash );
3351 "%s: Invalid predicate structure: %s",
3355 buffer_free(sql_buf);
3356 free(predicate_string);
3360 return buffer_release(sql_buf);
3363 /* Build a JSON_ARRAY of field names for a given table alias
3365 static jsonObject* defaultSelectList( const char* table_alias ) {
3370 ClassInfo* class_info = search_all_alias( table_alias );
3371 if( ! class_info ) {
3374 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3381 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3382 osrfHash* field_def = NULL;
3383 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3384 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3385 const char* field_name = osrfHashIteratorKey( field_itr );
3386 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3387 jsonObjectPush( array, jsonNewObject( field_name ) );
3390 osrfHashIteratorFree( field_itr );
3395 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3396 // The jsonObject must be a JSON_HASH with an single entry for "union",
3397 // "intersect", or "except". The data associated with this key must be an
3398 // array of hashes, each hash being a query.
3399 // Also allowed but currently ignored: entries for "order_by" and "alias".
3400 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3402 if( ! combo || combo->type != JSON_HASH )
3403 return NULL; // should be impossible; validated by caller
3405 const jsonObject* query_array = NULL; // array of subordinate queries
3406 const char* op = NULL; // name of operator, e.g. UNION
3407 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3408 int op_count = 0; // for detecting conflicting operators
3409 int excepting = 0; // boolean
3410 int all = 0; // boolean
3411 jsonObject* order_obj = NULL;
3413 // Identify the elements in the hash
3414 jsonIterator* query_itr = jsonNewIterator( combo );
3415 jsonObject* curr_obj = NULL;
3416 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3417 if( ! strcmp( "union", query_itr->key ) ) {
3420 query_array = curr_obj;
3421 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3424 query_array = curr_obj;
3425 } else if( ! strcmp( "except", query_itr->key ) ) {
3429 query_array = curr_obj;
3430 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3433 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3436 order_obj = curr_obj;
3437 } else if( ! strcmp( "alias", query_itr->key ) ) {
3438 if( curr_obj->type != JSON_STRING ) {
3439 jsonIteratorFree( query_itr );
3442 alias = jsonObjectGetString( curr_obj );
3443 } else if( ! strcmp( "all", query_itr->key ) ) {
3444 if( obj_is_true( curr_obj ) )
3448 osrfAppSessionStatus(
3450 OSRF_STATUS_INTERNALSERVERERROR,
3451 "osrfMethodException",
3453 "Malformed query; unexpected entry in query object"
3457 "%s: Unexpected entry for \"%s\" in%squery",
3462 jsonIteratorFree( query_itr );
3466 jsonIteratorFree( query_itr );
3468 // More sanity checks
3469 if( ! query_array ) {
3471 osrfAppSessionStatus(
3473 OSRF_STATUS_INTERNALSERVERERROR,
3474 "osrfMethodException",
3476 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3480 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3483 return NULL; // should be impossible...
3484 } else if( op_count > 1 ) {
3486 osrfAppSessionStatus(
3488 OSRF_STATUS_INTERNALSERVERERROR,
3489 "osrfMethodException",
3491 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3495 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3499 } if( query_array->type != JSON_ARRAY ) {
3501 osrfAppSessionStatus(
3503 OSRF_STATUS_INTERNALSERVERERROR,
3504 "osrfMethodException",
3506 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3510 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3513 json_type( query_array->type )
3516 } if( query_array->size < 2 ) {
3518 osrfAppSessionStatus(
3520 OSRF_STATUS_INTERNALSERVERERROR,
3521 "osrfMethodException",
3523 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3527 "%s:%srequires multiple queries as operands",
3532 } else if( excepting && query_array->size > 2 ) {
3534 osrfAppSessionStatus(
3536 OSRF_STATUS_INTERNALSERVERERROR,
3537 "osrfMethodException",
3539 "EXCEPT operator has too many queries as operands"
3543 "%s:EXCEPT operator has too many queries as operands",
3547 } else if( order_obj && ! alias ) {
3549 osrfAppSessionStatus(
3551 OSRF_STATUS_INTERNALSERVERERROR,
3552 "osrfMethodException",
3554 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3558 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3564 // So far so good. Now build the SQL.
3565 growing_buffer* sql = buffer_init( 256 );
3567 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3568 // Add a layer of parentheses
3569 if( flags & SUBCOMBO )
3570 OSRF_BUFFER_ADD( sql, "( " );
3572 // Traverse the query array. Each entry should be a hash.
3573 int first = 1; // boolean
3575 jsonObject* query = NULL;
3576 while((query = jsonObjectGetIndex( query_array, i++ ) )) {
3577 if( query->type != JSON_HASH ) {
3579 osrfAppSessionStatus(
3581 OSRF_STATUS_INTERNALSERVERERROR,
3582 "osrfMethodException",
3584 "Malformed query under UNION, INTERSECT or EXCEPT"
3588 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3591 json_type( query->type )
3600 OSRF_BUFFER_ADD( sql, op );
3602 OSRF_BUFFER_ADD( sql, "ALL " );
3605 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3609 "%s: Error building query under%s",
3617 OSRF_BUFFER_ADD( sql, query_str );
3620 if( flags & SUBCOMBO )
3621 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3623 if ( !(flags & SUBSELECT) )
3624 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3626 return buffer_release( sql );
3629 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3630 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3631 // or "except" to indicate the type of query.
3632 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3636 osrfAppSessionStatus(
3638 OSRF_STATUS_INTERNALSERVERERROR,
3639 "osrfMethodException",
3641 "Malformed query; no query object"
3643 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", MODULENAME );
3645 } else if( query->type != JSON_HASH ) {
3647 osrfAppSessionStatus(
3649 OSRF_STATUS_INTERNALSERVERERROR,
3650 "osrfMethodException",
3652 "Malformed query object"
3656 "%s: Query object is %s instead of JSON_HASH",
3658 json_type( query->type )
3663 // Determine what kind of query it purports to be, and dispatch accordingly.
3664 if( jsonObjectGetKey( query, "union" ) ||
3665 jsonObjectGetKey( query, "intersect" ) ||
3666 jsonObjectGetKey( query, "except" ) ) {
3667 return doCombo( ctx, query, flags );
3669 // It is presumably a SELECT query
3671 // Push a node onto the stack for the current query. Every level of
3672 // subquery gets its own QueryFrame on the Stack.
3675 // Build an SQL SELECT statement
3678 jsonObjectGetKey( query, "select" ),
3679 jsonObjectGetKey( query, "from" ),
3680 jsonObjectGetKey( query, "where" ),
3681 jsonObjectGetKey( query, "having" ),
3682 jsonObjectGetKey( query, "order_by" ),
3683 jsonObjectGetKey( query, "limit" ),
3684 jsonObjectGetKey( query, "offset" ),
3693 /* method context */ osrfMethodContext* ctx,
3695 /* SELECT */ jsonObject* selhash,
3696 /* FROM */ jsonObject* join_hash,
3697 /* WHERE */ jsonObject* search_hash,
3698 /* HAVING */ jsonObject* having_hash,
3699 /* ORDER BY */ jsonObject* order_hash,
3700 /* LIMIT */ jsonObject* limit,
3701 /* OFFSET */ jsonObject* offset,
3702 /* flags */ int flags
3704 const char* locale = osrf_message_get_last_locale();
3706 // general tmp objects
3707 const jsonObject* tmp_const;
3708 jsonObject* selclass = NULL;
3709 jsonObject* snode = NULL;
3710 jsonObject* onode = NULL;
3712 char* string = NULL;
3713 int from_function = 0;
3718 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3720 // punt if there's no FROM clause
3721 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3724 "%s: FROM clause is missing or empty",
3728 osrfAppSessionStatus(
3730 OSRF_STATUS_INTERNALSERVERERROR,
3731 "osrfMethodException",
3733 "FROM clause is missing or empty in JSON query"
3738 // the core search class
3739 const char* core_class = NULL;
3741 // get the core class -- the only key of the top level FROM clause, or a string
3742 if (join_hash->type == JSON_HASH) {
3743 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3744 snode = jsonIteratorNext( tmp_itr );
3746 // Populate the current QueryFrame with information
3747 // about the core class
3748 if( add_query_core( NULL, tmp_itr->key ) ) {
3750 osrfAppSessionStatus(
3752 OSRF_STATUS_INTERNALSERVERERROR,
3753 "osrfMethodException",
3755 "Unable to look up core class"
3759 core_class = curr_query->core.class_name;
3762 jsonObject* extra = jsonIteratorNext( tmp_itr );
3764 jsonIteratorFree( tmp_itr );
3767 // There shouldn't be more than one entry in join_hash
3771 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3775 osrfAppSessionStatus(
3777 OSRF_STATUS_INTERNALSERVERERROR,
3778 "osrfMethodException",
3780 "Malformed FROM clause in JSON query"
3782 return NULL; // Malformed join_hash; extra entry
3784 } else if (join_hash->type == JSON_ARRAY) {
3785 // We're selecting from a function, not from a table
3787 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
3790 } else if (join_hash->type == JSON_STRING) {
3791 // Populate the current QueryFrame with information
3792 // about the core class
3793 core_class = jsonObjectGetString( join_hash );
3795 if( add_query_core( NULL, core_class ) ) {
3797 osrfAppSessionStatus(
3799 OSRF_STATUS_INTERNALSERVERERROR,
3800 "osrfMethodException",
3802 "Unable to look up core class"
3810 "%s: FROM clause is unexpected JSON type: %s",
3812 json_type( join_hash->type )
3815 osrfAppSessionStatus(
3817 OSRF_STATUS_INTERNALSERVERERROR,
3818 "osrfMethodException",
3820 "Ill-formed FROM clause in JSON query"
3825 // Build the join clause, if any, while filling out the list
3826 // of joined classes in the current QueryFrame.
3827 char* join_clause = NULL;
3828 if( join_hash && ! from_function ) {
3830 join_clause = searchJOIN( join_hash, &curr_query->core );
3831 if( ! join_clause ) {
3833 osrfAppSessionStatus(
3835 OSRF_STATUS_INTERNALSERVERERROR,
3836 "osrfMethodException",
3838 "Unable to construct JOIN clause(s)"
3844 // For in case we don't get a select list
3845 jsonObject* defaultselhash = NULL;
3847 // if there is no select list, build a default select list ...
3848 if (!selhash && !from_function) {
3849 jsonObject* default_list = defaultSelectList( core_class );
3850 if( ! default_list ) {
3852 osrfAppSessionStatus(
3854 OSRF_STATUS_INTERNALSERVERERROR,
3855 "osrfMethodException",
3857 "Unable to build default SELECT clause in JSON query"
3859 free( join_clause );
3864 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3865 jsonObjectSetKey( selhash, core_class, default_list );
3868 // The SELECT clause can be encoded only by a hash
3869 if( !from_function && selhash->type != JSON_HASH ) {
3872 "%s: Expected JSON_HASH for SELECT clause; found %s",
3874 json_type( selhash->type )
3878 osrfAppSessionStatus(
3880 OSRF_STATUS_INTERNALSERVERERROR,
3881 "osrfMethodException",
3883 "Malformed SELECT clause in JSON query"
3885 free( join_clause );
3889 // If you see a null or wild card specifier for the core class, or an
3890 // empty array, replace it with a default SELECT list
3891 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3893 int default_needed = 0; // boolean
3894 if( JSON_STRING == tmp_const->type
3895 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3897 else if( JSON_NULL == tmp_const->type )
3900 if( default_needed ) {
3901 // Build a default SELECT list
3902 jsonObject* default_list = defaultSelectList( core_class );
3903 if( ! default_list ) {
3905 osrfAppSessionStatus(
3907 OSRF_STATUS_INTERNALSERVERERROR,
3908 "osrfMethodException",
3910 "Can't build default SELECT clause in JSON query"
3912 free( join_clause );
3917 jsonObjectSetKey( selhash, core_class, default_list );
3921 // temp buffers for the SELECT list and GROUP BY clause
3922 growing_buffer* select_buf = buffer_init(128);
3923 growing_buffer* group_buf = buffer_init(128);
3925 int aggregate_found = 0; // boolean
3927 // Build a select list
3928 if(from_function) // From a function we select everything
3929 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3932 // Build the SELECT list as SQL
3936 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3937 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3939 const char* cname = selclass_itr->key;
3941 // Make sure the target relation is in the FROM clause.
3943 // At this point join_hash is a step down from the join_hash we
3944 // received as a parameter. If the original was a JSON_STRING,
3945 // then json_hash is now NULL. If the original was a JSON_HASH,
3946 // then json_hash is now the first (and only) entry in it,
3947 // denoting the core class. We've already excluded the
3948 // possibility that the original was a JSON_ARRAY, because in
3949 // that case from_function would be non-NULL, and we wouldn't
3952 // If the current table alias isn't in scope, bail out
3953 ClassInfo* class_info = search_alias( cname );
3954 if( ! class_info ) {
3957 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3962 osrfAppSessionStatus(
3964 OSRF_STATUS_INTERNALSERVERERROR,
3965 "osrfMethodException",
3967 "Selected class not in FROM clause in JSON query"
3969 jsonIteratorFree( selclass_itr );
3970 buffer_free( select_buf );
3971 buffer_free( group_buf );
3972 if( defaultselhash )
3973 jsonObjectFree( defaultselhash );
3974 free( join_clause );
3978 if( selclass->type != JSON_ARRAY ) {
3981 "%s: Malformed SELECT list for class \"%s\"; not an array",
3986 osrfAppSessionStatus(
3988 OSRF_STATUS_INTERNALSERVERERROR,
3989 "osrfMethodException",
3991 "Selected class not in FROM clause in JSON query"
3994 jsonIteratorFree( selclass_itr );
3995 buffer_free( select_buf );
3996 buffer_free( group_buf );
3997 if( defaultselhash )
3998 jsonObjectFree( defaultselhash );
3999 free( join_clause );
4003 // Look up some attributes of the current class
4004 osrfHash* idlClass = class_info->class_def;
4005 osrfHash* class_field_set = class_info->fields;
4006 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4007 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4009 if( 0 == selclass->size ) {
4012 "%s: No columns selected from \"%s\"",
4018 // stitch together the column list for the current table alias...
4019 unsigned long field_idx = 0;
4020 jsonObject* selfield = NULL;
4021 while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4023 // If we need a separator comma, add one
4027 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4030 // if the field specification is a string, add it to the list
4031 if (selfield->type == JSON_STRING) {
4033 // Look up the field in the IDL
4034 const char* col_name = jsonObjectGetString( selfield );
4035 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4037 // No such field in current class
4040 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4046 osrfAppSessionStatus(
4048 OSRF_STATUS_INTERNALSERVERERROR,
4049 "osrfMethodException",
4051 "Selected column not defined in JSON query"
4053 jsonIteratorFree( selclass_itr );
4054 buffer_free( select_buf );
4055 buffer_free( group_buf );
4056 if( defaultselhash )
4057 jsonObjectFree( defaultselhash );
4058 free( join_clause );
4060 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4061 // Virtual field not allowed
4064 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4070 osrfAppSessionStatus(
4072 OSRF_STATUS_INTERNALSERVERERROR,
4073 "osrfMethodException",
4075 "Selected column may not be virtual in JSON query"
4077 jsonIteratorFree( selclass_itr );
4078 buffer_free( select_buf );
4079 buffer_free( group_buf );
4080 if( defaultselhash )
4081 jsonObjectFree( defaultselhash );
4082 free( join_clause );
4088 if (flags & DISABLE_I18N)
4091 i18n = osrfHashGet(field_def, "i18n");
4093 if( str_is_true( i18n ) ) {
4094 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4095 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4096 class_tname, cname, col_name, class_pkey,
4097 cname, class_pkey, locale, col_name );
4099 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"",
4100 cname, col_name, col_name );
4103 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"",
4104 cname, col_name, col_name );
4107 // ... but it could be an object, in which case we check for a Field Transform
4108 } else if (selfield->type == JSON_HASH) {
4110 const char* col_name = jsonObjectGetString(
4111 jsonObjectGetKeyConst( selfield, "column" ) );
4113 // Get the field definition from the IDL
4114 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4116 // No such field in current class
4119 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4125 osrfAppSessionStatus(
4127 OSRF_STATUS_INTERNALSERVERERROR,
4128 "osrfMethodException",
4130 "Selected column is not defined in JSON query"
4132 jsonIteratorFree( selclass_itr );
4133 buffer_free( select_buf );
4134 buffer_free( group_buf );
4135 if( defaultselhash )
4136 jsonObjectFree( defaultselhash );
4137 free( join_clause );
4139 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4140 // No such field in current class
4143 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4149 osrfAppSessionStatus(
4151 OSRF_STATUS_INTERNALSERVERERROR,
4152 "osrfMethodException",
4154 "Selected column is virtual in JSON query"
4156 jsonIteratorFree( selclass_itr );
4157 buffer_free( select_buf );
4158 buffer_free( group_buf );
4159 if( defaultselhash )
4160 jsonObjectFree( defaultselhash );
4161 free( join_clause );
4165 // Decide what to use as a column alias
4167 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4168 _alias = jsonObjectGetString( tmp_const );
4169 } else { // Use field name as the alias
4173 if (jsonObjectGetKeyConst( selfield, "transform" )) {
4174 char* transform_str = searchFieldTransform(
4175 class_info->alias, field_def, selfield );
4176 if( transform_str ) {
4177 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
4178 free(transform_str);
4181 osrfAppSessionStatus(
4183 OSRF_STATUS_INTERNALSERVERERROR,
4184 "osrfMethodException",
4186 "Unable to generate transform function in JSON query"
4188 jsonIteratorFree( selclass_itr );
4189 buffer_free( select_buf );
4190 buffer_free( group_buf );
4191 if( defaultselhash )
4192 jsonObjectFree( defaultselhash );
4193 free( join_clause );
4200 if (flags & DISABLE_I18N)
4203 i18n = osrfHashGet(field_def, "i18n");
4205 if( str_is_true( i18n ) ) {
4206 buffer_fadd( select_buf,
4207 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4208 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4209 class_tname, cname, col_name, class_pkey, cname,
4210 class_pkey, locale, _alias);
4212 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4213 cname, col_name, _alias );
4216 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4217 cname, col_name, _alias);
4224 "%s: Selected item is unexpected JSON type: %s",
4226 json_type( selfield->type )
4229 osrfAppSessionStatus(
4231 OSRF_STATUS_INTERNALSERVERERROR,
4232 "osrfMethodException",
4234 "Ill-formed SELECT item in JSON query"
4236 jsonIteratorFree( selclass_itr );
4237 buffer_free( select_buf );
4238 buffer_free( group_buf );
4239 if( defaultselhash )
4240 jsonObjectFree( defaultselhash );
4241 free( join_clause );
4245 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
4246 if( obj_is_true( agg_obj ) )
4247 aggregate_found = 1;
4249 // Append a comma (except for the first one)
4250 // and add the column to a GROUP BY clause
4254 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4256 buffer_fadd(group_buf, " %d", sel_pos);
4260 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4262 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4263 if ( ! obj_is_true( aggregate_obj ) ) {
4267 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4270 buffer_fadd(group_buf, " %d", sel_pos);
4273 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4277 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4280 _column = searchFieldTransform(class_info->alias, field, selfield);
4281 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4282 OSRF_BUFFER_ADD(group_buf, _column);
4283 _column = searchFieldTransform(class_info->alias, field, selfield);
4290 } // end while -- iterating across SELECT columns
4292 } // end while -- iterating across classes
4294 jsonIteratorFree(selclass_itr);
4298 char* col_list = buffer_release(select_buf);
4300 // Make sure the SELECT list isn't empty. This can happen, for example,
4301 // if we try to build a default SELECT clause from a non-core table.
4304 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
4306 osrfAppSessionStatus(
4308 OSRF_STATUS_INTERNALSERVERERROR,
4309 "osrfMethodException",
4311 "SELECT list is empty"
4314 buffer_free( group_buf );
4315 if( defaultselhash )
4316 jsonObjectFree( defaultselhash );
4317 free( join_clause );
4322 if (from_function) table = searchValueTransform(join_hash);
4323 else table = strdup( curr_query->core.source_def );
4327 osrfAppSessionStatus(
4329 OSRF_STATUS_INTERNALSERVERERROR,
4330 "osrfMethodException",
4332 "Unable to identify table for core class"
4335 buffer_free( group_buf );
4336 if( defaultselhash )
4337 jsonObjectFree( defaultselhash );
4338 free( join_clause );
4342 // Put it all together
4343 growing_buffer* sql_buf = buffer_init(128);
4344 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4348 // Append the join clause, if any
4350 buffer_add(sql_buf, join_clause);
4354 char* order_by_list = NULL;
4355 char* having_buf = NULL;
4357 if (!from_function) {
4359 // Build a WHERE clause, if there is one
4360 if ( search_hash ) {
4361 buffer_add(sql_buf, " WHERE ");
4363 // and it's on the WHERE clause
4364 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4367 osrfAppSessionStatus(
4369 OSRF_STATUS_INTERNALSERVERERROR,
4370 "osrfMethodException",
4372 "Severe query error in WHERE predicate -- see error log for more details"
4375 buffer_free(group_buf);
4376 buffer_free(sql_buf);
4378 jsonObjectFree(defaultselhash);
4382 buffer_add(sql_buf, pred);
4386 // Build a HAVING clause, if there is one
4387 if ( having_hash ) {
4389 // and it's on the the WHERE clause
4390 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4392 if( ! having_buf ) {
4394 osrfAppSessionStatus(
4396 OSRF_STATUS_INTERNALSERVERERROR,
4397 "osrfMethodException",
4399 "Severe query error in HAVING predicate -- see error log for more details"
4402 buffer_free(group_buf);
4403 buffer_free(sql_buf);
4405 jsonObjectFree(defaultselhash);
4410 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4412 // Build an ORDER BY clause, if there is one
4413 if( NULL == order_hash )
4414 ; // No ORDER BY? do nothing
4415 else if( JSON_ARRAY == order_hash->type ) {
4416 // Array of field specifications, each specification being a
4417 // hash to define the class, field, and other details
4419 jsonObject* order_spec;
4420 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4422 if( JSON_HASH != order_spec->type ) {
4423 osrfLogError(OSRF_LOG_MARK,
4424 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4425 MODULENAME, json_type( order_spec->type ) );
4427 osrfAppSessionStatus(
4429 OSRF_STATUS_INTERNALSERVERERROR,
4430 "osrfMethodException",
4432 "Malformed ORDER BY clause -- see error log for more details"
4434 buffer_free( order_buf );
4436 buffer_free(group_buf);
4437 buffer_free(sql_buf);
4439 jsonObjectFree(defaultselhash);
4443 const char* class_alias =
4444 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4446 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4449 OSRF_BUFFER_ADD(order_buf, ", ");
4451 order_buf = buffer_init(128);
4453 if( !field || !class_alias ) {
4454 osrfLogError( OSRF_LOG_MARK,
4455 "%s: Missing class or field name in field specification "
4456 "of ORDER BY clause",
4459 osrfAppSessionStatus(
4461 OSRF_STATUS_INTERNALSERVERERROR,
4462 "osrfMethodException",
4464 "Malformed ORDER BY clause -- see error log for more details"
4466 buffer_free( order_buf );
4468 buffer_free(group_buf);
4469 buffer_free(sql_buf);
4471 jsonObjectFree(defaultselhash);
4475 ClassInfo* order_class_info = search_alias( class_alias );
4476 if( ! order_class_info ) {
4477 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4478 "not in FROM clause", MODULENAME, class_alias );
4480 osrfAppSessionStatus(
4482 OSRF_STATUS_INTERNALSERVERERROR,
4483 "osrfMethodException",
4485 "Invalid class referenced in ORDER BY clause -- "
4486 "see error log for more details"
4489 buffer_free(group_buf);
4490 buffer_free(sql_buf);
4492 jsonObjectFree(defaultselhash);
4496 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4498 osrfLogError( OSRF_LOG_MARK,
4499 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4500 MODULENAME, class_alias, field );
4502 osrfAppSessionStatus(
4504 OSRF_STATUS_INTERNALSERVERERROR,
4505 "osrfMethodException",
4507 "Invalid field referenced in ORDER BY clause -- "
4508 "see error log for more details"
4511 buffer_free(group_buf);
4512 buffer_free(sql_buf);
4514 jsonObjectFree(defaultselhash);
4516 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4517 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4518 MODULENAME, field );
4520 osrfAppSessionStatus(
4522 OSRF_STATUS_INTERNALSERVERERROR,
4523 "osrfMethodException",
4525 "Virtual field in ORDER BY clause -- see error log for more details"
4527 buffer_free( order_buf );
4529 buffer_free(group_buf);
4530 buffer_free(sql_buf);
4532 jsonObjectFree(defaultselhash);
4536 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4537 char* transform_str = searchFieldTransform(
4538 class_alias, field_def, order_spec );
4539 if( ! transform_str ) {
4541 osrfAppSessionStatus(
4543 OSRF_STATUS_INTERNALSERVERERROR,
4544 "osrfMethodException",
4546 "Severe query error in ORDER BY clause -- "
4547 "see error log for more details"
4549 buffer_free( order_buf );
4551 buffer_free(group_buf);
4552 buffer_free(sql_buf);
4554 jsonObjectFree(defaultselhash);
4558 OSRF_BUFFER_ADD( order_buf, transform_str );
4559 free( transform_str );
4562 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4564 const char* direction =
4565 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4567 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4568 OSRF_BUFFER_ADD( order_buf, " DESC" );
4570 OSRF_BUFFER_ADD( order_buf, " ASC" );
4573 } else if( JSON_HASH == order_hash->type ) {
4574 // This hash is keyed on class alias. Each class has either
4575 // an array of field names or a hash keyed on field name.
4576 jsonIterator* class_itr = jsonNewIterator( order_hash );
4577 while ( (snode = jsonIteratorNext( class_itr )) ) {
4579 ClassInfo* order_class_info = search_alias( class_itr->key );
4580 if( ! order_class_info ) {
4581 osrfLogError(OSRF_LOG_MARK,
4582 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4583 MODULENAME, class_itr->key );
4585 osrfAppSessionStatus(
4587 OSRF_STATUS_INTERNALSERVERERROR,
4588 "osrfMethodException",
4590 "Invalid class referenced in ORDER BY clause -- "
4591 "see error log for more details"
4593 jsonIteratorFree( class_itr );
4594 buffer_free( order_buf );
4596 buffer_free(group_buf);
4597 buffer_free(sql_buf);
4599 jsonObjectFree(defaultselhash);
4603 osrfHash* field_list_def = order_class_info->fields;
4605 if ( snode->type == JSON_HASH ) {
4607 // Hash is keyed on field names from the current class. For each field
4608 // there is another layer of hash to define the sorting details, if any,
4609 // or a string to indicate direction of sorting.
4610 jsonIterator* order_itr = jsonNewIterator( snode );
4611 while ( (onode = jsonIteratorNext( order_itr )) ) {
4613 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4615 osrfLogError( OSRF_LOG_MARK,
4616 "%s: Invalid field \"%s\" in ORDER BY clause",
4617 MODULENAME, order_itr->key );
4619 osrfAppSessionStatus(
4621 OSRF_STATUS_INTERNALSERVERERROR,
4622 "osrfMethodException",
4624 "Invalid field in ORDER BY clause -- see error log for more details"
4626 jsonIteratorFree( order_itr );
4627 jsonIteratorFree( class_itr );
4628 buffer_free( order_buf );
4630 buffer_free(group_buf);
4631 buffer_free(sql_buf);
4633 jsonObjectFree(defaultselhash);
4635 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4636 osrfLogError( OSRF_LOG_MARK,
4637 "%s: Virtual field \"%s\" in ORDER BY clause",
4638 MODULENAME, order_itr->key );
4640 osrfAppSessionStatus(
4642 OSRF_STATUS_INTERNALSERVERERROR,
4643 "osrfMethodException",
4645 "Virtual field in ORDER BY clause -- "
4646 "see error log for more details"
4648 jsonIteratorFree( order_itr );
4649 jsonIteratorFree( class_itr );
4650 buffer_free( order_buf );
4652 buffer_free(group_buf);
4653 buffer_free(sql_buf);
4655 jsonObjectFree(defaultselhash);
4659 const char* direction = NULL;
4660 if ( onode->type == JSON_HASH ) {
4661 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4662 string = searchFieldTransform(
4664 osrfHashGet( field_list_def, order_itr->key ),
4668 if( ctx ) osrfAppSessionStatus(
4670 OSRF_STATUS_INTERNALSERVERERROR,
4671 "osrfMethodException",
4673 "Severe query error in ORDER BY clause -- "
4674 "see error log for more details"
4676 jsonIteratorFree( order_itr );
4677 jsonIteratorFree( class_itr );
4679 buffer_free(group_buf);
4680 buffer_free(order_buf);
4681 buffer_free(sql_buf);
4683 jsonObjectFree(defaultselhash);
4687 growing_buffer* field_buf = buffer_init(16);
4688 buffer_fadd( field_buf, "\"%s\".%s",
4689 class_itr->key, order_itr->key );
4690 string = buffer_release(field_buf);
4693 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4694 const char* dir = jsonObjectGetString(tmp_const);
4695 if (!strncasecmp(dir, "d", 1)) {
4696 direction = " DESC";
4702 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4703 osrfLogError( OSRF_LOG_MARK,
4704 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4705 MODULENAME, json_type( onode->type ) );
4707 osrfAppSessionStatus(
4709 OSRF_STATUS_INTERNALSERVERERROR,
4710 "osrfMethodException",
4712 "Malformed ORDER BY clause -- see error log for more details"
4714 jsonIteratorFree( order_itr );
4715 jsonIteratorFree( class_itr );
4717 buffer_free(group_buf);
4718 buffer_free(order_buf);
4719 buffer_free(sql_buf);
4721 jsonObjectFree(defaultselhash);
4725 string = strdup(order_itr->key);
4726 const char* dir = jsonObjectGetString(onode);
4727 if (!strncasecmp(dir, "d", 1)) {
4728 direction = " DESC";
4735 OSRF_BUFFER_ADD(order_buf, ", ");
4737 order_buf = buffer_init(128);
4739 OSRF_BUFFER_ADD(order_buf, string);
4743 OSRF_BUFFER_ADD(order_buf, direction);
4747 jsonIteratorFree(order_itr);
4749 } else if ( snode->type == JSON_ARRAY ) {
4751 // Array is a list of fields from the current class
4752 unsigned long order_idx = 0;
4753 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4755 const char* _f = jsonObjectGetString( onode );
4757 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4759 osrfLogError( OSRF_LOG_MARK,
4760 "%s: Invalid field \"%s\" in ORDER BY clause",
4763 osrfAppSessionStatus(
4765 OSRF_STATUS_INTERNALSERVERERROR,
4766 "osrfMethodException",
4768 "Invalid field in ORDER BY clause -- "
4769 "see error log for more details"
4771 jsonIteratorFree( class_itr );
4772 buffer_free( order_buf );
4774 buffer_free(group_buf);
4775 buffer_free(sql_buf);
4777 jsonObjectFree(defaultselhash);
4779 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4780 osrfLogError( OSRF_LOG_MARK,
4781 "%s: Virtual field \"%s\" in ORDER BY clause",
4784 osrfAppSessionStatus(
4786 OSRF_STATUS_INTERNALSERVERERROR,
4787 "osrfMethodException",
4789 "Virtual field in ORDER BY clause -- "
4790 "see error log for more details"
4792 jsonIteratorFree( class_itr );
4793 buffer_free( order_buf );
4795 buffer_free(group_buf);
4796 buffer_free(sql_buf);
4798 jsonObjectFree(defaultselhash);
4803 OSRF_BUFFER_ADD(order_buf, ", ");
4805 order_buf = buffer_init(128);
4807 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
4811 // IT'S THE OOOOOOOOOOOLD STYLE!
4813 osrfLogError(OSRF_LOG_MARK,
4814 "%s: Possible SQL injection attempt; direct order by is not allowed",
4817 osrfAppSessionStatus(
4819 OSRF_STATUS_INTERNALSERVERERROR,
4820 "osrfMethodException",
4822 "Severe query error -- see error log for more details"
4827 buffer_free(group_buf);
4828 buffer_free(order_buf);
4829 buffer_free(sql_buf);
4831 jsonObjectFree(defaultselhash);
4832 jsonIteratorFree(class_itr);
4836 jsonIteratorFree( class_itr );
4838 osrfLogError(OSRF_LOG_MARK,
4839 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4840 MODULENAME, json_type( order_hash->type ) );
4842 osrfAppSessionStatus(
4844 OSRF_STATUS_INTERNALSERVERERROR,
4845 "osrfMethodException",
4847 "Malformed ORDER BY clause -- see error log for more details"
4849 buffer_free( order_buf );
4851 buffer_free(group_buf);
4852 buffer_free(sql_buf);
4854 jsonObjectFree(defaultselhash);
4859 order_by_list = buffer_release( order_buf );
4863 string = buffer_release(group_buf);
4865 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4866 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4867 OSRF_BUFFER_ADD( sql_buf, string );
4872 if( having_buf && *having_buf ) {
4873 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4874 OSRF_BUFFER_ADD( sql_buf, having_buf );
4878 if( order_by_list ) {
4880 if ( *order_by_list ) {
4881 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4882 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4885 free( order_by_list );
4889 const char* str = jsonObjectGetString(limit);
4890 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
4894 const char* str = jsonObjectGetString(offset);
4895 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
4898 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4901 jsonObjectFree(defaultselhash);
4903 return buffer_release(sql_buf);
4905 } // end of SELECT()
4907 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4909 const char* locale = osrf_message_get_last_locale();
4911 osrfHash* fields = osrfHashGet(meta, "fields");
4912 char* core_class = osrfHashGet(meta, "classname");
4914 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4916 jsonObject* node = NULL;
4917 jsonObject* snode = NULL;
4918 jsonObject* onode = NULL;
4919 const jsonObject* _tmp = NULL;
4920 jsonObject* selhash = NULL;
4921 jsonObject* defaultselhash = NULL;
4923 growing_buffer* sql_buf = buffer_init(128);
4924 growing_buffer* select_buf = buffer_init(128);
4926 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4927 defaultselhash = jsonNewObjectType(JSON_HASH);
4928 selhash = defaultselhash;
4931 // If there's no SELECT list for the core class, build one
4932 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4933 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4935 // Add every non-virtual field to the field list
4936 osrfHash* field_def = NULL;
4937 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4938 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4939 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4940 const char* field = osrfHashIteratorKey( field_itr );
4941 jsonObjectPush( field_list, jsonNewObject( field ) );
4944 osrfHashIteratorFree( field_itr );
4945 jsonObjectSetKey( selhash, core_class, field_list );
4949 jsonIterator* class_itr = jsonNewIterator( selhash );
4950 while ( (snode = jsonIteratorNext( class_itr )) ) {
4952 const char* cname = class_itr->key;
4953 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4957 if (strcmp(core_class,class_itr->key)) {
4961 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4963 jsonObjectFree(found);
4967 jsonObjectFree(found);
4970 jsonIterator* select_itr = jsonNewIterator( snode );
4971 while ( (node = jsonIteratorNext( select_itr )) ) {
4972 const char* item_str = jsonObjectGetString( node );
4973 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4974 char* fname = osrfHashGet(field, "name");
4982 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
4987 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4988 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4991 i18n = osrfHashGet(field, "i18n");
4993 if( str_is_true( i18n ) ) {
4994 char* pkey = osrfHashGet(idlClass, "primarykey");
4995 char* tname = osrfHashGet(idlClass, "tablename");
4997 buffer_fadd(select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4998 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4999 tname, cname, fname, pkey, cname, pkey, locale, fname);
5001 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
5004 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
5008 jsonIteratorFree(select_itr);
5011 jsonIteratorFree(class_itr);
5013 char* col_list = buffer_release(select_buf);
5014 char* table = getRelation(meta);
5016 table = strdup( "(null)" );
5018 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5022 // Clear the query stack (as a fail-safe precaution against possible
5023 // leftover garbage); then push the first query frame onto the stack.
5024 clear_query_stack();
5026 if( add_query_core( NULL, core_class ) ) {
5028 osrfAppSessionStatus(
5030 OSRF_STATUS_INTERNALSERVERERROR,
5031 "osrfMethodException",
5033 "Unable to build query frame for core class"
5039 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5040 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
5041 OSRF_BUFFER_ADD(sql_buf, join_clause);
5045 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5046 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
5048 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
5050 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5052 osrfAppSessionStatus(
5054 OSRF_STATUS_INTERNALSERVERERROR,
5055 "osrfMethodException",
5057 "Severe query error -- see error log for more details"
5059 buffer_free(sql_buf);
5061 jsonObjectFree(defaultselhash);
5062 clear_query_stack();
5065 buffer_add(sql_buf, pred);
5070 char* string = NULL;
5071 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
5073 growing_buffer* order_buf = buffer_init(128);
5076 jsonIterator* class_itr = jsonNewIterator( _tmp );
5077 while ( (snode = jsonIteratorNext( class_itr )) ) {
5079 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
5082 if ( snode->type == JSON_HASH ) {
5084 jsonIterator* order_itr = jsonNewIterator( snode );
5085 while ( (onode = jsonIteratorNext( order_itr )) ) {
5087 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
5088 class_itr->key, order_itr->key );
5092 char* direction = NULL;
5093 if ( onode->type == JSON_HASH ) {
5094 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
5095 string = searchFieldTransform( class_itr->key, field_def, onode );
5097 osrfAppSessionStatus(
5099 OSRF_STATUS_INTERNALSERVERERROR,
5100 "osrfMethodException",
5102 "Severe query error in ORDER BY clause -- "
5103 "see error log for more details"
5105 jsonIteratorFree( order_itr );
5106 jsonIteratorFree( class_itr );
5107 buffer_free( order_buf );
5108 buffer_free( sql_buf );
5109 if( defaultselhash )
5110 jsonObjectFree( defaultselhash );
5111 clear_query_stack();
5115 growing_buffer* field_buf = buffer_init(16);
5116 buffer_fadd( field_buf, "\"%s\".%s",
5117 class_itr->key, order_itr->key );
5118 string = buffer_release(field_buf);
5121 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
5122 const char* dir = jsonObjectGetString(_tmp);
5123 if (!strncasecmp(dir, "d", 1)) {
5124 direction = " DESC";
5130 string = strdup(order_itr->key);
5131 const char* dir = jsonObjectGetString(onode);
5132 if (!strncasecmp(dir, "d", 1)) {
5133 direction = " DESC";
5142 buffer_add(order_buf, ", ");
5145 buffer_add(order_buf, string);
5149 buffer_add(order_buf, direction);
5153 jsonIteratorFree(order_itr);
5156 const char* str = jsonObjectGetString(snode);
5157 buffer_add(order_buf, str);
5163 jsonIteratorFree(class_itr);
5165 string = buffer_release(order_buf);
5168 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5169 OSRF_BUFFER_ADD( sql_buf, string );
5175 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
5176 const char* str = jsonObjectGetString(_tmp);
5184 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
5186 const char* str = jsonObjectGetString(_tmp);
5196 jsonObjectFree(defaultselhash);
5197 clear_query_stack();
5199 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
5200 return buffer_release(sql_buf);
5203 int doJSONSearch ( osrfMethodContext* ctx ) {
5204 if(osrfMethodVerifyContext( ctx )) {
5205 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5209 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
5214 dbhandle = writehandle;
5216 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
5220 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
5221 flags |= SELECT_DISTINCT;
5223 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
5224 flags |= DISABLE_I18N;
5226 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
5227 clear_query_stack(); // a possibly needless precaution
5228 char* sql = buildQuery( ctx, hash, flags );
5229 clear_query_stack();
5236 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
5237 dbi_result result = dbi_conn_query(dbhandle, sql);
5240 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
5242 if (dbi_result_first_row(result)) {
5243 /* JSONify the result */
5244 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
5247 jsonObject* return_val = oilsMakeJSONFromResult( result );
5248 osrfAppRespond( ctx, return_val );
5249 jsonObjectFree( return_val );
5250 } while (dbi_result_next_row(result));
5253 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
5256 osrfAppRespondComplete( ctx, NULL );
5258 /* clean up the query */
5259 dbi_result_free(result);
5263 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
5264 osrfAppSessionStatus(
5266 OSRF_STATUS_INTERNALSERVERERROR,
5267 "osrfMethodException",
5269 "Severe query error -- see error log for more details"
5277 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
5278 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5281 dbhandle = writehandle;
5283 char* core_class = osrfHashGet( class_meta, "classname" );
5284 char* pkey = osrfHashGet( class_meta, "primarykey" );
5286 const jsonObject* _tmp;
5288 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5290 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
5295 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
5297 dbi_result result = dbi_conn_query(dbhandle, sql);
5298 if( NULL == result ) {
5299 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
5300 MODULENAME, osrfHashGet( class_meta, "fieldmapper" ), sql);
5301 osrfAppSessionStatus(
5303 OSRF_STATUS_INTERNALSERVERERROR,
5304 "osrfMethodException",
5306 "Severe query error -- see error log for more details"
5313 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
5316 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
5317 jsonObject* row_obj = NULL;
5319 if (dbi_result_first_row(result)) {
5321 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5322 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5323 // eliminate the duplicates.
5324 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
5325 osrfHash* dedup = osrfNewHash();
5327 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5328 char* pkey_val = oilsFMGetString( row_obj, pkey );
5329 if ( osrfHashGet( dedup, pkey_val ) ) {
5330 jsonObjectFree( row_obj );
5333 osrfHashSet( dedup, pkey_val, pkey_val );
5334 jsonObjectPush( res_list, row_obj );
5336 } while (dbi_result_next_row(result));
5337 osrfHashFree(dedup);
5340 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
5344 /* clean up the query */
5345 dbi_result_free(result);
5348 // If we're asked to flesh, and there's anything to flesh, then flesh.
5349 if (res_list->size && query_hash) {
5350 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5352 // Get the flesh depth
5353 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5354 if ( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5355 flesh_depth = max_flesh_depth;
5357 // We need a non-zero flesh depth, and a list of fields to flesh
5358 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5359 if ( temp_blob && flesh_depth > 0 ) {
5361 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5362 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5364 osrfStringArray* link_fields = NULL;
5365 osrfHash* links = osrfHashGet( class_meta, "links" );
5367 // Make an osrfStringArray of the names of fields to be fleshed
5369 if (flesh_fields->size == 1) {
5370 const char* _t = jsonObjectGetString(
5371 jsonObjectGetIndex( flesh_fields, 0 ) );
5372 if (!strcmp(_t,"*"))
5373 link_fields = osrfHashKeys( links );
5378 link_fields = osrfNewStringArray(1);
5379 jsonIterator* _i = jsonNewIterator( flesh_fields );
5380 while ((_f = jsonIteratorNext( _i ))) {
5381 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5383 jsonIteratorFree(_i);
5387 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5389 // Iterate over the JSON_ARRAY of rows
5391 unsigned long res_idx = 0;
5392 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5395 const char* link_field;
5397 // Iterate over the list of fleshable fields
5398 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5400 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
5402 osrfHash* kid_link = osrfHashGet(links, link_field);
5404 continue; // Not a link field; skip it
5406 osrfHash* field = osrfHashGet(fields, link_field);
5408 continue; // Not a field at all; skip it (IDL is ill-formed)
5410 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
5412 continue; // The class it links to doesn't exist; skip it
5414 const char* reltype = osrfHashGet( kid_link, "reltype" );
5416 continue; // No reltype; skip it (IDL is ill-formed)
5418 osrfHash* value_field = field;
5420 if ( !strcmp( reltype, "has_many" )
5421 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5422 value_field = osrfHashGet(
5423 fields, osrfHashGet( class_meta, "primarykey" ) );
5426 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5428 if (link_map->size > 0) {
5429 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
5432 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5437 osrfHashGet(kid_link, "class"),
5444 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5445 osrfHashGet(kid_link, "field"),
5446 osrfHashGet(kid_link, "class"),
5447 osrfHashGet(kid_link, "key"),
5448 osrfHashGet(kid_link, "reltype")
5451 const char* search_key = jsonObjectGetString(
5452 jsonObjectGetIndex( cur,
5453 atoi( osrfHashGet(value_field, "array_position") )
5458 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
5462 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
5464 // construct WHERE clause
5465 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
5468 osrfHashGet(kid_link, "key"),
5469 jsonNewObject( search_key )
5472 // construct the rest of the query, mostly
5473 // by copying pieces of the previous level of query
5474 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
5475 jsonObjectSetKey( rest_of_query, "flesh",
5476 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5480 jsonObjectSetKey( rest_of_query, "flesh_fields",
5481 jsonObjectClone(flesh_blob) );
5483 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
5484 jsonObjectSetKey( rest_of_query, "order_by",
5485 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
5489 if (jsonObjectGetKeyConst(query_hash, "select")) {
5490 jsonObjectSetKey( rest_of_query, "select",
5491 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
5495 // do the query, recursively, to expand the fleshable field
5496 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5497 where_clause, rest_of_query, err);
5499 jsonObjectFree( where_clause );
5500 jsonObjectFree( rest_of_query );
5503 osrfStringArrayFree(link_fields);
5504 jsonObjectFree(res_list);
5505 jsonObjectFree(flesh_blob);
5509 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects",
5510 osrfHashGet(kid_link, "class"), kids->size);
5512 // Traverse the result set
5513 jsonObject* X = NULL;
5514 if ( link_map->size > 0 && kids->size > 0 ) {
5516 kids = jsonNewObjectType(JSON_ARRAY);
5518 jsonObject* _k_node;
5519 unsigned long res_idx = 0;
5520 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5526 (unsigned long)atoi(
5532 osrfHashGet(kid_link, "class")
5536 osrfStringArrayGetString( link_map, 0 )
5544 } // end while loop traversing X
5547 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" ))
5548 || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
5549 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5550 osrfHashGet(kid_link, "field"));
5553 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5554 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
5558 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) {
5560 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5561 osrfHashGet( kid_link, "field" ) );
5564 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5565 jsonObjectClone( kids )
5570 jsonObjectFree(kids);
5574 jsonObjectFree( kids );
5576 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5577 osrfHashGet( kid_link, "field" ) );
5578 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
5580 } // end while loop traversing list of fleshable fields
5581 } // end while loop traversing res_list
5582 jsonObjectFree( flesh_blob );
5583 osrfStringArrayFree(link_fields);
5592 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
5594 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5596 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
5598 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
5601 if (!verifyObjectClass(ctx, target)) {
5606 if( getXactId( ctx ) == NULL ) {
5607 osrfAppSessionStatus(
5609 OSRF_STATUS_BADREQUEST,
5610 "osrfMethodException",
5612 "No active transaction -- required for UPDATE"
5618 // The following test is harmless but redundant. If a class is
5619 // readonly, we don't register an update method for it.
5620 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5621 osrfAppSessionStatus(
5623 OSRF_STATUS_BADREQUEST,
5624 "osrfMethodException",
5626 "Cannot UPDATE readonly class"
5632 dbhandle = writehandle;
5633 const char* trans_id = getXactId( ctx );
5635 // Set the last_xact_id
5636 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5638 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5639 trans_id, target->classname, index);
5640 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
5643 char* pkey = osrfHashGet(meta, "primarykey");
5644 osrfHash* fields = osrfHashGet(meta, "fields");
5646 char* id = oilsFMGetString( target, pkey );
5650 "%s updating %s object with %s = %s",
5652 osrfHashGet(meta, "fieldmapper"),
5657 growing_buffer* sql = buffer_init(128);
5658 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
5661 osrfHash* field_def = NULL;
5662 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5663 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
5665 // Skip virtual fields, and the primary key
5666 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5669 const char* field_name = osrfHashIteratorKey( field_itr );
5670 if( ! strcmp( field_name, pkey ) )
5673 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5675 int value_is_numeric = 0; // boolean
5677 if (field_object && field_object->classname) {
5678 value = oilsFMGetString(
5680 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
5682 } else if( field_object && JSON_BOOL == field_object->type ) {
5683 if( jsonBoolIsTrue( field_object ) )
5684 value = strdup( "t" );
5686 value = strdup( "f" );
5688 value = jsonObjectToSimpleString( field_object );
5689 if( field_object && JSON_NUMBER == field_object->type )
5690 value_is_numeric = 1;
5693 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5694 osrfHashGet(meta, "fieldmapper"), field_name, value);
5696 if (!field_object || field_object->type == JSON_NULL) {
5697 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
5698 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5699 if (first) first = 0;
5700 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5701 buffer_fadd( sql, " %s = NULL", field_name );
5704 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5705 if (first) first = 0;
5706 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5708 const char* numtype = get_datatype( field_def );
5709 if ( !strncmp( numtype, "INT", 3 ) ) {
5710 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
5711 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
5712 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
5714 // Must really be intended as a string, so quote it
5715 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5716 buffer_fadd( sql, " %s = %s", field_name, value );
5718 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5720 osrfAppSessionStatus(
5722 OSRF_STATUS_INTERNALSERVERERROR,
5723 "osrfMethodException",
5725 "Error quoting string -- please see the error log for more details"
5729 osrfHashIteratorFree( field_itr );
5736 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5739 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5740 if (first) first = 0;
5741 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5742 buffer_fadd( sql, " %s = %s", field_name, value );
5745 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5746 osrfAppSessionStatus(
5748 OSRF_STATUS_INTERNALSERVERERROR,
5749 "osrfMethodException",
5751 "Error quoting string -- please see the error log for more details"
5755 osrfHashIteratorFree( field_itr );
5766 osrfHashIteratorFree( field_itr );
5768 jsonObject* obj = jsonNewObject(id);
5770 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5771 dbi_conn_quote_string(dbhandle, &id);
5773 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5775 char* query = buffer_release(sql);
5776 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
5778 dbi_result result = dbi_conn_query(dbhandle, query);
5782 jsonObjectFree(obj);
5783 obj = jsonNewObject(NULL);
5786 "%s ERROR updating %s object with %s = %s",
5788 osrfHashGet(meta, "fieldmapper"),
5799 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
5801 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5803 if( getXactId( ctx ) == NULL ) {
5804 osrfAppSessionStatus(
5806 OSRF_STATUS_BADREQUEST,
5807 "osrfMethodException",
5809 "No active transaction -- required for DELETE"
5815 // The following test is harmless but redundant. If a class is
5816 // readonly, we don't register a delete method for it.
5817 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5818 osrfAppSessionStatus(
5820 OSRF_STATUS_BADREQUEST,
5821 "osrfMethodException",
5823 "Cannot DELETE readonly class"
5829 dbhandle = writehandle;
5833 char* pkey = osrfHashGet(meta, "primarykey");
5841 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
5842 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5847 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5850 if (!verifyObjectPCRUD( ctx, NULL )) {
5855 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
5860 "%s deleting %s object with %s = %s",
5862 osrfHashGet(meta, "fieldmapper"),
5867 obj = jsonNewObject(id);
5869 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5870 dbi_conn_quote_string(writehandle, &id);
5872 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;",
5873 osrfHashGet(meta, "tablename"), pkey, id);
5876 jsonObjectFree(obj);
5877 obj = jsonNewObject(NULL);
5880 "%s ERROR deleting %s object with %s = %s",
5882 osrfHashGet(meta, "fieldmapper"),
5894 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5895 @param result An iterator for a result set; we only look at the current row.
5896 @param @meta Pointer to the class metadata for the core class.
5897 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5899 If a column is not defined in the IDL, or if it has no array_position defined for it in
5900 the IDL, or if it is defined as virtual, ignore it.
5902 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5903 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5904 array_position in the IDL.
5906 A field defined in the IDL but not represented in the returned row will leave a hole
5907 in the JSON_ARRAY. In effect it will be treated as a null value.
5909 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5910 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5911 classname corresponding to the @a meta argument.
5913 The calling code is responsible for freeing the the resulting jsonObject by calling
5916 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5917 if(!(result && meta)) return jsonNULL;
5919 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5920 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
5921 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
5923 osrfHash* fields = osrfHashGet(meta, "fields");
5925 int columnIndex = 1;
5926 const char* columnName;
5928 /* cycle through the columns in the row returned from the database */
5929 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5931 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5933 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5935 /* determine the field type and storage attributes */
5936 unsigned short type = dbi_result_get_field_type_idx(result, columnIndex);
5937 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5939 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5940 // or if it has no sequence number there, or if it's virtual, skip it.
5941 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
5944 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
5945 continue; // skip this column: IDL says it's virtual
5947 const char* pos = (char*)osrfHashGet(_f, "array_position");
5948 if ( !pos ) // IDL has no sequence number for it. This shouldn't happen,
5949 continue; // since we assign sequence numbers dynamically as we load the IDL.
5951 fmIndex = atoi( pos );
5952 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
5954 continue; // This field is not defined in the IDL
5957 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
5958 // sequence number from the IDL (which is likely to be different from the sequence
5959 // of columns in the SELECT clause).
5960 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5961 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
5966 case DBI_TYPE_INTEGER :
5968 if( attr & DBI_INTEGER_SIZE8 )
5969 jsonObjectSetIndex( object, fmIndex,
5970 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
5972 jsonObjectSetIndex( object, fmIndex,
5973 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
5977 case DBI_TYPE_DECIMAL :
5978 jsonObjectSetIndex( object, fmIndex,
5979 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
5982 case DBI_TYPE_STRING :
5987 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
5992 case DBI_TYPE_DATETIME : {
5994 char dt_string[256] = "";
5997 // Fetch the date column as a time_t
5998 time_t _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
6000 // Translate the time_t to a human-readable string
6001 if (!(attr & DBI_DATETIME_DATE)) {
6002 gmtime_r( &_tmp_dt, &gmdt );
6003 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
6004 } else if (!(attr & DBI_DATETIME_TIME)) {
6005 localtime_r( &_tmp_dt, &gmdt );
6006 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
6008 localtime_r( &_tmp_dt, &gmdt );
6009 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
6012 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
6016 case DBI_TYPE_BINARY :
6017 osrfLogError( OSRF_LOG_MARK,
6018 "Can't do binary at column %s : index %d", columnName, columnIndex);
6027 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6028 if(!result) return jsonNULL;
6030 jsonObject* object = jsonNewObject(NULL);
6033 char dt_string[256];
6037 int columnIndex = 1;
6039 unsigned short type;
6040 const char* columnName;
6042 /* cycle through the column list */
6043 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
6045 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
6047 fmIndex = -1; // reset the position
6049 /* determine the field type and storage attributes */
6050 type = dbi_result_get_field_type_idx(result, columnIndex);
6051 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
6053 if (dbi_result_field_is_null_idx(result, columnIndex)) {
6054 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
6059 case DBI_TYPE_INTEGER :
6061 if( attr & DBI_INTEGER_SIZE8 )
6062 jsonObjectSetKey( object, columnName,
6063 jsonNewNumberObject(dbi_result_get_longlong_idx(
6064 result, columnIndex)) );
6066 jsonObjectSetKey( object, columnName,
6067 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
6070 case DBI_TYPE_DECIMAL :
6071 jsonObjectSetKey( object, columnName,
6072 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
6075 case DBI_TYPE_STRING :
6076 jsonObjectSetKey( object, columnName,
6077 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
6080 case DBI_TYPE_DATETIME :
6082 memset(dt_string, '\0', sizeof(dt_string));
6083 memset(&gmdt, '\0', sizeof(gmdt));
6085 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
6088 if (!(attr & DBI_DATETIME_DATE)) {
6089 gmtime_r( &_tmp_dt, &gmdt );
6090 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
6091 } else if (!(attr & DBI_DATETIME_TIME)) {
6092 localtime_r( &_tmp_dt, &gmdt );
6093 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
6095 localtime_r( &_tmp_dt, &gmdt );
6096 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
6099 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
6102 case DBI_TYPE_BINARY :
6103 osrfLogError( OSRF_LOG_MARK,
6104 "Can't do binary at column %s : index %d", columnName, columnIndex );
6108 } // end while loop traversing result
6113 // Interpret a string as true or false
6114 static int str_is_true( const char* str ) {
6115 if( NULL == str || strcasecmp( str, "true" ) )
6121 // Interpret a jsonObject as true or false
6122 static int obj_is_true( const jsonObject* obj ) {
6125 else switch( obj->type )
6133 if( strcasecmp( obj->value.s, "true" ) )
6137 case JSON_NUMBER : // Support 1/0 for perl's sake
6138 if( jsonObjectGetNumber( obj ) == 1.0 )
6147 // Translate a numeric code into a text string identifying a type of
6148 // jsonObject. To be used for building error messages.
6149 static const char* json_type( int code ) {
6155 return "JSON_ARRAY";
6157 return "JSON_STRING";
6159 return "JSON_NUMBER";
6165 return "(unrecognized)";
6169 // Extract the "primitive" attribute from an IDL field definition.
6170 // If we haven't initialized the app, then we must be running in
6171 // some kind of testbed. In that case, default to "string".
6172 static const char* get_primitive( osrfHash* field ) {
6173 const char* s = osrfHashGet( field, "primitive" );
6175 if( child_initialized )
6178 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6180 osrfHashGet( field, "name" )
6188 // Extract the "datatype" attribute from an IDL field definition.
6189 // If we haven't initialized the app, then we must be running in
6190 // some kind of testbed. In that case, default to to NUMERIC,
6191 // since we look at the datatype only for numbers.
6192 static const char* get_datatype( osrfHash* field ) {
6193 const char* s = osrfHashGet( field, "datatype" );
6195 if( child_initialized )
6198 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6200 osrfHashGet( field, "name" )
6209 If the input string is potentially a valid SQL identifier, return 1.
6212 Purpose: to prevent certain kinds of SQL injection. To that end we
6213 don't necessarily need to follow all the rules exactly, such as requiring
6214 that the first character not be a digit.
6216 We allow leading and trailing white space. In between, we do not allow
6217 punctuation (except for underscores and dollar signs), control
6218 characters, or embedded white space.
6220 More pedantically we should allow quoted identifiers containing arbitrary
6221 characters, but for the foreseeable future such quoted identifiers are not
6222 likely to be an issue.
6224 static int is_identifier( const char* s) {
6228 // Skip leading white space
6229 while( isspace( (unsigned char) *s ) )
6233 return 0; // Nothing but white space? Not okay.
6235 // Check each character until we reach white space or
6236 // end-of-string. Letters, digits, underscores, and
6237 // dollar signs are okay. With the exception of periods
6238 // (as in schema.identifier), control characters and other
6239 // punctuation characters are not okay. Anything else
6240 // is okay -- it could for example be part of a multibyte
6241 // UTF8 character such as a letter with diacritical marks,
6242 // and those are allowed.
6244 if( isalnum( (unsigned char) *s )
6248 ; // Fine; keep going
6249 else if( ispunct( (unsigned char) *s )
6250 || iscntrl( (unsigned char) *s ) )
6253 } while( *s && ! isspace( (unsigned char) *s ) );
6255 // If we found any white space in the above loop,
6256 // the rest had better be all white space.
6258 while( isspace( (unsigned char) *s ) )
6262 return 0; // White space was embedded within non-white space
6268 Determine whether to accept a character string as a comparison operator.
6269 Return 1 if it's good, or 0 if it's bad.
6271 We don't validate it for real. We just make sure that it doesn't contain
6272 any semicolons or white space (with special exceptions for a few specific
6273 operators). The idea is to block certain kinds of SQL injection. If it
6274 has no semicolons or white space but it's still not a valid operator, then
6275 the database will complain.
6277 Another approach would be to compare the string against a short list of
6278 approved operators. We don't do that because we want to allow custom
6279 operators like ">100*", which would be difficult or impossible to
6280 express otherwise in a JSON query.
6282 static int is_good_operator( const char* op ) {
6283 if( !op ) return 0; // Sanity check
6287 if( isspace( (unsigned char) *s ) ) {
6288 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6289 // and IS NOT DISTINCT FROM.
6290 if( !strcasecmp( op, "similar to" ) )
6292 else if( !strcasecmp( op, "is distinct from" ) )
6294 else if( !strcasecmp( op, "is not distinct from" ) )
6299 else if( ';' == *s )
6307 @name Query Frame Management
6309 The following machinery supports a stack of query frames for use by SELECT().
6311 A query frame caches information about one level of a SELECT query. When we enter
6312 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6314 The query frame stores information about the core class, and about any joined classes
6317 The main purpose is to map table aliases to classes and tables, so that a query can
6318 join to the same table more than once. A secondary goal is to reduce the number of
6319 lookups in the IDL by caching the results.
6323 #define STATIC_CLASS_INFO_COUNT 3
6325 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6328 @brief Allocate a ClassInfo as raw memory.
6329 @return Pointer to the newly allocated ClassInfo.
6331 Except for the in_use flag, which is used only by the allocation and deallocation
6332 logic, we don't initialize the ClassInfo here.
6334 static ClassInfo* allocate_class_info( void ) {
6335 // In order to reduce the number of mallocs and frees, we return a static
6336 // instance of ClassInfo, if we can find one that we're not already using.
6337 // We rely on the fact that the compiler will implicitly initialize the
6338 // static instances so that in_use == 0.
6341 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6342 if( ! static_class_info[ i ].in_use ) {
6343 static_class_info[ i ].in_use = 1;
6344 return static_class_info + i;
6348 // The static ones are all in use. Malloc one.
6350 return safe_malloc( sizeof( ClassInfo ) );
6354 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6355 @param info Pointer to the ClassInfo to be cleared.
6357 static void clear_class_info( ClassInfo* info ) {
6362 // Free any malloc'd strings
6364 if( info->alias != info->alias_store )
6365 free( info->alias );
6367 if( info->class_name != info->class_name_store )
6368 free( info->class_name );
6370 free( info->source_def );
6372 info->alias = info->class_name = info->source_def = NULL;
6377 @brief Free a ClassInfo and everything it owns.
6378 @param info Pointer to the ClassInfo to be freed.
6380 static void free_class_info( ClassInfo* info ) {
6385 clear_class_info( info );
6387 // If it's one of the static instances, just mark it as not in use
6390 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6391 if( info == static_class_info + i ) {
6392 static_class_info[ i ].in_use = 0;
6397 // Otherwise it must have been malloc'd, so free it
6403 @brief Populate an already-allocated ClassInfo.
6404 @param info Pointer to the ClassInfo to be populated.
6405 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6407 @param class Name of the class.
6408 @return Zero if successful, or 1 if not.
6410 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6411 the relevant portions of the IDL for the specified class.
6413 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6416 osrfLogError( OSRF_LOG_MARK,
6417 "%s ERROR: No ClassInfo available to populate", MODULENAME );
6418 info->alias = info->class_name = info->source_def = NULL;
6419 info->class_def = info->fields = info->links = NULL;
6424 osrfLogError( OSRF_LOG_MARK,
6425 "%s ERROR: No class name provided for lookup", MODULENAME );
6426 info->alias = info->class_name = info->source_def = NULL;
6427 info->class_def = info->fields = info->links = NULL;
6431 // Alias defaults to class name if not supplied
6432 if( ! alias || ! alias[ 0 ] )
6435 // Look up class info in the IDL
6436 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6438 osrfLogError( OSRF_LOG_MARK,
6439 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
6440 info->alias = info->class_name = info->source_def = NULL;
6441 info->class_def = info->fields = info->links = NULL;
6443 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6444 osrfLogError( OSRF_LOG_MARK,
6445 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
6446 info->alias = info->class_name = info->source_def = NULL;
6447 info->class_def = info->fields = info->links = NULL;
6451 osrfHash* links = osrfHashGet( class_def, "links" );
6453 osrfLogError( OSRF_LOG_MARK,
6454 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
6455 info->alias = info->class_name = info->source_def = NULL;
6456 info->class_def = info->fields = info->links = NULL;
6460 osrfHash* fields = osrfHashGet( class_def, "fields" );
6462 osrfLogError( OSRF_LOG_MARK,
6463 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
6464 info->alias = info->class_name = info->source_def = NULL;
6465 info->class_def = info->fields = info->links = NULL;
6469 char* source_def = getRelation( class_def );
6473 // We got everything we need, so populate the ClassInfo
6474 if( strlen( alias ) > ALIAS_STORE_SIZE )
6475 info->alias = strdup( alias );
6477 strcpy( info->alias_store, alias );
6478 info->alias = info->alias_store;
6481 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6482 info->class_name = strdup( class );
6484 strcpy( info->class_name_store, class );
6485 info->class_name = info->class_name_store;
6488 info->source_def = source_def;
6490 info->class_def = class_def;
6491 info->links = links;
6492 info->fields = fields;
6497 #define STATIC_FRAME_COUNT 3
6499 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6502 @brief Allocate a QueryFrame as raw memory.
6503 @return Pointer to the newly allocated QueryFrame.
6505 Except for the in_use flag, which is used only by the allocation and deallocation
6506 logic, we don't initialize the QueryFrame here.
6508 static QueryFrame* allocate_frame( void ) {
6509 // In order to reduce the number of mallocs and frees, we return a static
6510 // instance of QueryFrame, if we can find one that we're not already using.
6511 // We rely on the fact that the compiler will implicitly initialize the
6512 // static instances so that in_use == 0.
6515 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6516 if( ! static_frame[ i ].in_use ) {
6517 static_frame[ i ].in_use = 1;
6518 return static_frame + i;
6522 // The static ones are all in use. Malloc one.
6524 return safe_malloc( sizeof( QueryFrame ) );
6528 @brief Free a QueryFrame, and all the memory it owns.
6529 @param frame Pointer to the QueryFrame to be freed.
6531 static void free_query_frame( QueryFrame* frame ) {
6536 clear_class_info( &frame->core );
6538 // Free the join list
6540 ClassInfo* info = frame->join_list;
6543 free_class_info( info );
6547 frame->join_list = NULL;
6550 // If the frame is a static instance, just mark it as unused
6552 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6553 if( frame == static_frame + i ) {
6554 static_frame[ i ].in_use = 0;
6559 // Otherwise it must have been malloc'd, so free it
6565 @brief Search a given QueryFrame for a specified alias.
6566 @param frame Pointer to the QueryFrame to be searched.
6567 @param target The alias for which to search.
6568 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6570 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6571 if( ! frame || ! target ) {
6575 ClassInfo* found_class = NULL;
6577 if( !strcmp( target, frame->core.alias ) )
6578 return &(frame->core);
6580 ClassInfo* curr_class = frame->join_list;
6581 while( curr_class ) {
6582 if( strcmp( target, curr_class->alias ) )
6583 curr_class = curr_class->next;
6585 found_class = curr_class;
6595 @brief Push a new (blank) QueryFrame onto the stack.
6597 static void push_query_frame( void ) {
6598 QueryFrame* frame = allocate_frame();
6599 frame->join_list = NULL;
6600 frame->next = curr_query;
6602 // Initialize the ClassInfo for the core class
6603 ClassInfo* core = &frame->core;
6604 core->alias = core->class_name = core->source_def = NULL;
6605 core->class_def = core->fields = core->links = NULL;
6611 @brief Pop a QueryFrame off the stack and destroy it.
6613 static void pop_query_frame( void ) {
6618 QueryFrame* popped = curr_query;
6619 curr_query = popped->next;
6621 free_query_frame( popped );
6625 @brief Populate the ClassInfo for the core class.
6626 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6627 class name as an alias.
6628 @param class_name Name of the core class.
6629 @return Zero if successful, or 1 if not.
6631 Populate the ClassInfo of the core class with copies of the alias and class name, and
6632 with pointers to the relevant portions of the IDL for the core class.
6634 static int add_query_core( const char* alias, const char* class_name ) {
6637 if( ! curr_query ) {
6638 osrfLogError( OSRF_LOG_MARK,
6639 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
6641 } else if( curr_query->core.alias ) {
6642 osrfLogError( OSRF_LOG_MARK,
6643 "%s ERROR: Core class %s already populated as %s",
6644 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
6648 build_class_info( &curr_query->core, alias, class_name );
6649 if( curr_query->core.alias )
6652 osrfLogError( OSRF_LOG_MARK,
6653 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
6659 @brief Search the current QueryFrame for a specified alias.
6660 @param target The alias for which to search.
6661 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6663 static inline ClassInfo* search_alias( const char* target ) {
6664 return search_alias_in_frame( curr_query, target );
6668 @brief Search all levels of query for a specified alias, starting with the current query.
6669 @param target The alias for which to search.
6670 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6672 static ClassInfo* search_all_alias( const char* target ) {
6673 ClassInfo* found_class = NULL;
6674 QueryFrame* curr_frame = curr_query;
6676 while( curr_frame ) {
6677 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6680 curr_frame = curr_frame->next;
6687 @brief Add a class to the list of classes joined to the current query.
6688 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6689 the class name as an alias.
6690 @param classname The name of the class to be added.
6691 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6693 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6695 if( ! classname || ! *classname ) { // sanity check
6696 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6703 const ClassInfo* conflict = search_alias( alias );
6705 osrfLogError( OSRF_LOG_MARK,
6706 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6707 MODULENAME, alias, conflict->class_name );
6711 ClassInfo* info = allocate_class_info();
6713 if( build_class_info( info, alias, classname ) ) {
6714 free_class_info( info );
6718 // Add the new ClassInfo to the join list of the current QueryFrame
6719 info->next = curr_query->join_list;
6720 curr_query->join_list = info;
6726 @brief Destroy all nodes on the query stack.
6728 static void clear_query_stack( void ) {