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"
22 # define MODULENAME "open-ils.reporter-store"
25 # define MODULENAME "open-ils.pcrud"
27 # define MODULENAME "open-ils.cstore"
31 // The next four macros are OR'd together as needed to form a set
32 // of bitflags. SUBCOMBO enables an extra pair of parentheses when
33 // nesting one UNION, INTERSECT or EXCEPT inside another.
34 // SUBSELECT tells us we're in a subquery, so don't add the
35 // terminal semicolon yet.
38 #define DISABLE_I18N 2
39 #define SELECT_DISTINCT 1
44 struct ClassInfoStruct;
45 typedef struct ClassInfoStruct ClassInfo;
47 #define ALIAS_STORE_SIZE 16
48 #define CLASS_NAME_STORE_SIZE 16
50 struct ClassInfoStruct {
54 osrfHash* class_def; // Points into IDL
55 osrfHash* fields; // Points into IDL
56 osrfHash* links; // Points into IDL
58 // The remaining members are private and internal. Client code should not
59 // access them directly.
61 ClassInfo* next; // Supports linked list of joined classes
62 int in_use; // boolean
64 // We usually store the alias and class name in the following arrays, and
65 // point the corresponding pointers at them. When the string is too big
66 // for the array (which will probably never happen in practice), we strdup it.
68 char alias_store[ ALIAS_STORE_SIZE + 1 ];
69 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
72 struct QueryFrameStruct;
73 typedef struct QueryFrameStruct QueryFrame;
75 struct QueryFrameStruct {
77 ClassInfo* join_list; // linked list of classes joined to the core class
78 QueryFrame* next; // implements stack as linked list
79 int in_use; // boolean
82 int osrfAppChildInit();
83 int osrfAppInitialize();
84 void osrfAppChildExit();
86 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
88 static void setXactId( osrfMethodContext* ctx );
89 static inline const char* getXactId( osrfMethodContext* ctx );
90 static inline void clearXactId( osrfMethodContext* ctx );
92 int beginTransaction ( osrfMethodContext* );
93 int commitTransaction ( osrfMethodContext* );
94 int rollbackTransaction ( osrfMethodContext* );
96 int setSavepoint ( osrfMethodContext* );
97 int releaseSavepoint ( osrfMethodContext* );
98 int rollbackSavepoint ( osrfMethodContext* );
100 int doJSONSearch ( osrfMethodContext* );
102 int dispatchCRUDMethod ( osrfMethodContext* );
103 static jsonObject* doCreate ( osrfMethodContext*, int* );
104 static jsonObject* doRetrieve ( osrfMethodContext*, int* );
105 static jsonObject* doUpdate ( osrfMethodContext*, int* );
106 static jsonObject* doDelete ( osrfMethodContext*, int* );
107 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
108 jsonObject* where_hash, jsonObject* query_hash, int* err );
109 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
110 static jsonObject* oilsMakeJSONFromResult( dbi_result );
112 static char* searchSimplePredicate ( const char* op, const char* class_alias,
113 osrfHash* field, const jsonObject* node );
114 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
115 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject*);
116 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*, const char* );
117 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
118 static char* searchINPredicate ( const char*, osrfHash*,
119 jsonObject*, const char*, osrfMethodContext* );
120 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
121 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
122 static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
123 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
124 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
126 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
128 void userDataFree( void* );
129 static void sessionDataFree( char*, void* );
130 static char* getRelation( osrfHash* );
131 static int str_is_true( const char* str );
132 static int obj_is_true( const jsonObject* obj );
133 static const char* json_type( int code );
134 static const char* get_primitive( osrfHash* field );
135 static const char* get_datatype( osrfHash* field );
136 static int is_identifier( const char* s);
137 static int is_good_operator( const char* op );
138 static void pop_query_frame( void );
139 static void push_query_frame( void );
140 static int add_query_core( const char* alias, const char* class_name );
141 static inline ClassInfo* search_alias( const char* target );
142 static ClassInfo* search_all_alias( const char* target );
143 static ClassInfo* add_joined_class( const char* alias, const char* classname );
144 static void clear_query_stack( void );
147 static jsonObject* verifyUserPCRUD( osrfMethodContext* );
148 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
149 static char* org_tree_root( osrfMethodContext* ctx );
150 static jsonObject* single_hash( const char* key, const char* value );
153 static int child_initialized = 0; /* boolean */
155 static dbi_conn writehandle; /* our MASTER db connection */
156 static dbi_conn dbhandle; /* our CURRENT db connection */
157 //static osrfHash * readHandles;
158 static jsonObject* const jsonNULL = NULL; //
159 static int max_flesh_depth = 100;
161 // The following points to the top of a stack of QueryFrames. It's a little
162 // confusing because the top level of the query is at the bottom of the stack.
163 static QueryFrame* curr_query = NULL;
166 @brief Disconnect from the database.
168 This function is called when the server drone is about to terminate.
170 void osrfAppChildExit() {
171 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
174 if (writehandle == dbhandle) same = 1;
176 dbi_conn_query(writehandle, "ROLLBACK;");
177 dbi_conn_close(writehandle);
180 if (dbhandle && !same)
181 dbi_conn_close(dbhandle);
183 // XXX add cleanup of readHandles whenever that gets used
189 @brief Initialize the application.
190 @return Zero if successful, or non-zero if not.
192 Load the IDL file into an internal data structure for future reference. Each non-virtual
193 class in the IDL corresponds to a table or view in the database, or to a subquery defined
194 in the IDL. Ignore all virtual tables and virtual fields.
196 Register a number of methods, some of them general-purpose and others specific for
199 The name of the application is given by the MODULENAME macro, whose value depends on
200 conditional compilation. The method names also incorporate MODULENAME, followed by a
201 dot, as a prefix. Some methods are registered or not registered depending on whether
202 the PCRUD macro is defined, and on whether the IDL includes permacrud entries for the
205 The general-purpose methods are as follows (minus their MODULENAME prefixes):
207 - json_query (not registered for PCRUD)
210 - transaction.rollback
215 For each non-virtual class, create up to eight class-specific methods:
217 - create (not for readonly classes)
219 - update (not for readonly classes)
220 - delete (not for readonly classes
221 - search (atomic and non-atomic versions)
222 - id_list (atomic and non-atomic versions)
224 For PCRUD, the full method names follow the pattern "MODULENAME.method_type.classname".
225 Otherwise they follow the pattern "MODULENAME.direct.XXX.method_type", where XXX is the
226 fieldmapper name from the IDL, with every run of one or more consecutive colons replaced
227 by a period. In addition, the names of atomic methods have a suffix of ".atomic".
229 This function is called when the registering the application, and is executed by the
230 listener before spawning the drones.
232 int osrfAppInitialize() {
234 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
235 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
237 if (!oilsIDLInit( osrf_settings_host_value("/IDL") ))
238 return 1; /* return non-zero to indicate error */
240 growing_buffer* method_name = buffer_init(64);
242 // Generic search thingy
243 buffer_add(method_name, MODULENAME);
244 buffer_add(method_name, ".json_query");
245 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
246 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
249 // first we register all the transaction and savepoint methods
250 buffer_reset(method_name);
251 OSRF_BUFFER_ADD(method_name, MODULENAME);
252 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
253 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
254 "beginTransaction", "", 0, 0 );
256 buffer_reset(method_name);
257 OSRF_BUFFER_ADD(method_name, MODULENAME);
258 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
259 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
260 "commitTransaction", "", 0, 0 );
262 buffer_reset(method_name);
263 OSRF_BUFFER_ADD(method_name, MODULENAME);
264 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
265 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
266 "rollbackTransaction", "", 0, 0 );
268 buffer_reset(method_name);
269 OSRF_BUFFER_ADD(method_name, MODULENAME);
270 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
271 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
272 "setSavepoint", "", 1, 0 );
274 buffer_reset(method_name);
275 OSRF_BUFFER_ADD(method_name, MODULENAME);
276 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
277 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
278 "releaseSavepoint", "", 1, 0 );
280 buffer_reset(method_name);
281 OSRF_BUFFER_ADD(method_name, MODULENAME);
282 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
283 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
284 "rollbackSavepoint", "", 1, 0 );
286 static const char* global_method[] = {
294 const int global_method_count
295 = sizeof( global_method ) / sizeof ( global_method[0] );
297 unsigned long class_count = osrfHashGetCount( oilsIDL() );
298 osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
299 osrfLogDebug(OSRF_LOG_MARK,
300 "At most %lu methods will be generated",
301 (unsigned long) (class_count * global_method_count) );
303 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
304 osrfHash* idlClass = NULL;
306 // For each class in the IDL...
307 while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
309 const char* classname = osrfHashIteratorKey( class_itr );
310 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
312 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
313 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
314 MODULENAME, classname);
318 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
319 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
323 // Look up some other attributes of the current class
324 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
325 if( !idlClass_fieldmapper ) {
326 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
332 // For PCRUD, ignore classes with no permacrud attribute
333 osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
334 if (!idlClass_permacrud) {
335 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no permacrud in IDL", classname );
339 const char* readonly = osrfHashGet(idlClass, "readonly");
342 for( i = 0; i < global_method_count; ++i ) { // for each global method
343 const char* method_type = global_method[ i ];
344 osrfLogDebug(OSRF_LOG_MARK,
345 "Using files to build %s class methods for %s", method_type, classname);
348 // Treat "id_list" or "search" as forms of "retrieve"
349 const char* tmp_method = method_type;
350 if ( *tmp_method == 'i' || *tmp_method == 's') { // "id_list" or "search"
351 tmp_method = "retrieve";
353 // Skip this method if there is no permacrud entry for it
354 if (!osrfHashGet( idlClass_permacrud, tmp_method ))
358 // No create, update, or delete methods for a readonly class
359 if ( str_is_true( readonly )
360 && ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd') )
363 buffer_reset( method_name );
365 // Build the method name
367 // For PCRUD: MODULENAME.method_type.classname
368 buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
370 // For non-PCRUD: MODULENAME.MODULENAME.direct.XXX.method_type
371 // where XXX is the fieldmapper name from the IDL, with every run of
372 // one or more consecutive colons replaced by a period.
375 char* _fm = strdup( idlClass_fieldmapper );
376 part = strtok_r(_fm, ":", &st_tmp);
378 buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
380 while ((part = strtok_r(NULL, ":", &st_tmp))) {
381 OSRF_BUFFER_ADD_CHAR(method_name, '.');
382 OSRF_BUFFER_ADD(method_name, part);
384 OSRF_BUFFER_ADD_CHAR(method_name, '.');
385 OSRF_BUFFER_ADD(method_name, method_type);
389 // For an id_list or search method we specify the OSRF_METHOD_STREAMING option.
390 // The consequence is that we implicitly create an atomic method in addition to
391 // the usual non-atomic method.
393 if (*method_type == 'i' || *method_type == 's') { // id_list or search
394 flags = flags | OSRF_METHOD_STREAMING;
397 osrfHash* method_meta = osrfNewHash();
398 osrfHashSet( method_meta, idlClass, "class");
399 osrfHashSet( method_meta, buffer_data( method_name ), "methodname" );
400 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
402 // Register the method, with a pointer to an osrfHash to tell the method
403 // its name, type, and class.
404 osrfAppRegisterExtendedMethod(
406 OSRF_BUFFER_C_STR( method_name ),
407 "dispatchCRUDMethod",
414 } // end for each global method
415 } // end for each class in IDL
417 buffer_free( method_name );
418 osrfHashIteratorFree( class_itr );
424 @brief Get a table name, view name, or subquery for use in a FROM clause.
425 @param class Pointer to the IDL class entry.
426 @return A table name, a view name, or a subquery in parentheses.
428 In some cases the IDL defines a class, not with a table name or a view name, but with
429 a SELECT statement, which may be used as a subquery.
431 static char* getRelation( osrfHash* class ) {
433 char* source_def = NULL;
434 const char* tabledef = osrfHashGet(class, "tablename");
437 source_def = strdup( tabledef ); // Return the name of a table or view
439 tabledef = osrfHashGet( class, "source_definition" );
441 // Return a subquery, enclosed in parentheses
442 source_def = safe_malloc( strlen( tabledef ) + 3 );
443 source_def[ 0 ] = '(';
444 strcpy( source_def + 1, tabledef );
445 strcat( source_def, ")" );
447 // Not found: return an error
448 const char* classname = osrfHashGet( class, "classname" );
453 "%s ERROR No tablename or source_definition for class \"%s\"",
464 @brief Initialize a server drone.
465 @return Zero if successful, -1 if not.
467 Connect to the database. For each non-virtual class in the IDL, execute a dummy "SELECT * "
468 query to get the datatype of each column. Record the datatypes in the loaded IDL.
470 This function is called by a server drone shortly after it is spawned by the listener.
472 int osrfAppChildInit() {
474 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
475 dbi_initialize(NULL);
476 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
478 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
479 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
480 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
481 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
482 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
483 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
484 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion",
487 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
488 writehandle = dbi_conn_new(driver);
491 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
494 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
496 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
497 "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
499 if(host) dbi_conn_set_option(writehandle, "host", host );
500 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
501 if(user) dbi_conn_set_option(writehandle, "username", user);
502 if(pw) dbi_conn_set_option(writehandle, "password", pw );
503 if(db) dbi_conn_set_option(writehandle, "dbname", db );
505 if(md) max_flesh_depth = atoi(md);
506 if(max_flesh_depth < 0) max_flesh_depth = 1;
507 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
516 if (dbi_conn_connect(writehandle) < 0) {
518 if (dbi_conn_connect(writehandle) < 0) {
519 dbi_conn_error(writehandle, &err);
520 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
525 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
527 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
528 osrfHash* class = NULL;
529 growing_buffer* query_buf = buffer_init( 64 );
531 // For each class in the IDL...
532 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
533 const char* classname = osrfHashIteratorKey( class_itr );
534 osrfHash* fields = osrfHashGet( class, "fields" );
536 // If the class is virtual, ignore it
537 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
538 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
542 char* tabledef = getRelation(class);
544 continue; // No such relation -- a query of it would be doomed to failure
546 buffer_reset( query_buf );
547 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
551 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
552 MODULENAME, OSRF_BUFFER_C_STR( query_buf ) );
554 dbi_result result = dbi_conn_query( writehandle, OSRF_BUFFER_C_STR( query_buf ) );
558 const char* columnName;
560 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
562 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
565 /* fetch the fieldmapper index */
566 if( (_f = osrfHashGet(fields, columnName)) ) {
568 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
570 /* determine the field type and storage attributes */
572 switch( dbi_result_get_field_type_idx(result, columnIndex) ) {
574 case DBI_TYPE_INTEGER : {
576 if ( !osrfHashGet(_f, "primitive") )
577 osrfHashSet(_f, "number", "primitive");
579 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
580 if( attr & DBI_INTEGER_SIZE8 )
581 osrfHashSet(_f, "INT8", "datatype");
583 osrfHashSet(_f, "INT", "datatype");
586 case DBI_TYPE_DECIMAL :
587 if ( !osrfHashGet(_f, "primitive") )
588 osrfHashSet(_f, "number", "primitive");
590 osrfHashSet(_f,"NUMERIC", "datatype");
593 case DBI_TYPE_STRING :
594 if ( !osrfHashGet(_f, "primitive") )
595 osrfHashSet(_f,"string", "primitive");
597 osrfHashSet(_f,"TEXT", "datatype");
600 case DBI_TYPE_DATETIME :
601 if ( !osrfHashGet(_f, "primitive") )
602 osrfHashSet(_f,"string", "primitive");
604 osrfHashSet(_f,"TIMESTAMP", "datatype");
607 case DBI_TYPE_BINARY :
608 if ( !osrfHashGet(_f, "primitive") )
609 osrfHashSet(_f,"string", "primitive");
611 osrfHashSet(_f,"BYTEA", "datatype");
616 "Setting [%s] to primitive [%s] and datatype [%s]...",
618 osrfHashGet(_f, "primitive"),
619 osrfHashGet(_f, "datatype")
623 } // end while loop for traversing columns of result
624 dbi_result_free(result);
626 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", classname);
628 } // end for each class in IDL
630 buffer_free( query_buf );
631 osrfHashIteratorFree( class_itr );
632 child_initialized = 1;
637 @brief Install a database driver.
638 @param conn Pointer to a database driver.
640 The driver is used to process quoted strings correctly.
642 This function is a sleazy hack, intended @em only for testing and debugging without
643 actually connecting to a database. Any real server process should initialize the
644 database connection by calling osrfAppChildInit().
646 void set_cstore_dbi_conn( dbi_conn conn ) {
647 dbhandle = writehandle = conn;
651 @brief Free an osrfHash that stores a transaction ID.
652 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
654 This function is a callback, to be called by the application session when it ends.
655 The application session stores the osrfHash via an opaque pointer.
657 If the osrfHash contains an entry for the key "xact_id", it means that an
658 uncommitted transaction is pending. Roll it back.
660 void userDataFree( void* blob ) {
661 osrfHash* hash = (osrfHash*) blob;
662 if( osrfHashGet( hash, "xact_id" ) && writehandle )
663 dbi_conn_query( writehandle, "ROLLBACK;" );
665 osrfHashFree( hash );
669 @name Managing session data
670 @brief Maintain data stored via the userData pointer of the application session.
672 Currently, session-level data is stored in an osrfHash. Other arrangements are
673 possible, and some would be more efficient. The application session calls a
674 callback function to free userData before terminating.
676 Currently, the only data we store at the session level is the transaction id. By this
677 means we can ensure that any pending transactions are rolled back before the application
683 @brief Free an item in the application session's userData.
684 @param key The name of a key for an osrfHash.
685 @param item An opaque pointer to the item associated with the key.
687 We store an osrfHash as userData with the application session, and arrange (by
688 installing userDataFree() as a different callback) for the session to free that
689 osrfHash before terminating.
691 This function is a callback for freeing items in the osrfHash. If the item has a key
692 of "xact_id", the item is a transaction id for a transaction that is still pending.
693 It is just a character string, so we free it.
695 Currently the transaction ID is the only thing that we store in this osrfHash. If we
696 ever store anything else in it, we will need to revisit this function so that it will
697 free whatever else needs freeing (which may or may not be just a character string).
699 static void sessionDataFree( char* key, void* item ) {
700 if ( !strcmp(key,"xact_id") ) {
706 @brief Save a transaction id.
707 @param ctx Pointer to the method context.
709 Save the session_id of the current application session as a transaction id.
711 static void setXactId( osrfMethodContext* ctx ) {
712 if( ctx && ctx->session ) {
713 osrfAppSession* session = ctx->session;
715 // If the session doesn't already have a hash, create one. Make sure
716 // that the application session frees the hash when it terminates.
717 if( NULL == session->userData ) {
718 session->userData = osrfNewHash();
719 osrfHashSetCallback( (osrfHash*) session->userData, &sessionDataFree );
720 ctx->session->userDataFree = &userDataFree;
723 // Save the transaction id in the hash, with the key "xact_id"
724 osrfHashSet( (osrfHash*) session->userData, strdup( session->session_id ),
730 @brief Get the transaction ID for the current transaction, if any.
731 @param ctx Pointer to the method context.
732 @return Pointer to the transaction ID.
734 The return value points to an internal buffer, and will become invalid upon issuing
735 a commit or rollback.
737 static inline const char* getXactId( osrfMethodContext* ctx ) {
738 if( ctx && ctx->session && ctx->session->userData )
739 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
745 @brief Clear the current transaction id.
746 @param ctx Pointer to the method context.
748 static inline void clearXactId( osrfMethodContext* ctx ) {
749 if( ctx && ctx->session && ctx->session->userData )
750 osrfHashRemove( ctx->session->userData, "xact_id" );
755 @brief Implement the transaction.begin method.
756 @param ctx Pointer to the method context.
757 @return Zero if successful, or -1 upon error.
759 Start a transaction. Save a transaction ID for future reference.
762 - authkey (PCRUD only)
764 Return to client: Transaction ID
766 int beginTransaction ( osrfMethodContext* ctx ) {
767 if(osrfMethodVerifyContext( ctx )) {
768 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
773 jsonObject* user = verifyUserPCRUD( ctx );
776 jsonObjectFree(user);
779 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
781 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
782 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
783 "osrfMethodException", ctx->request, "Error starting transaction" );
787 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
788 osrfAppRespondComplete( ctx, ret );
795 @brief Implement the savepoint.set method.
796 @param ctx Pointer to the method context.
797 @return Zero if successful, or -1 if not.
799 Issue a SAVEPOINT to the database server.
802 - authkey (PCRUD only)
805 Return to client: Savepoint name
807 int setSavepoint ( osrfMethodContext* ctx ) {
808 if(osrfMethodVerifyContext( ctx )) {
809 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
816 jsonObject* user = verifyUserPCRUD( ctx );
819 jsonObjectFree(user);
822 // Verify that a transaction is pending
823 const char* trans_id = getXactId( ctx );
824 if( NULL == trans_id ) {
825 osrfAppSessionStatus(
827 OSRF_STATUS_INTERNALSERVERERROR,
828 "osrfMethodException",
830 "No active transaction -- required for savepoints"
835 // Get the savepoint name from the method params
836 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
838 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
842 "%s: Error creating savepoint %s in transaction %s",
847 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
848 "osrfMethodException", ctx->request, "Error creating savepoint" );
851 jsonObject* ret = jsonNewObject(spName);
852 osrfAppRespondComplete( ctx, ret );
859 @brief Implement the savepoint.release method.
860 @param ctx Pointer to the method context.
861 @return Zero if successful, or -1 if not.
863 Issue a RELEASE SAVEPOINT to the database server.
866 - authkey (PCRUD only)
869 Return to client: Savepoint name
871 int releaseSavepoint ( osrfMethodContext* ctx ) {
872 if(osrfMethodVerifyContext( ctx )) {
873 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
880 jsonObject* user = verifyUserPCRUD( ctx );
883 jsonObjectFree(user);
886 // Verify that a transaction is pending
887 const char* trans_id = getXactId( ctx );
888 if( NULL == trans_id ) {
889 osrfAppSessionStatus(
891 OSRF_STATUS_INTERNALSERVERERROR,
892 "osrfMethodException",
894 "No active transaction -- required for savepoints"
899 // Get the savepoint name from the method params
900 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
902 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
906 "%s: Error releasing savepoint %s in transaction %s",
911 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
912 "osrfMethodException", ctx->request, "Error releasing savepoint" );
915 jsonObject* ret = jsonNewObject(spName);
916 osrfAppRespondComplete( ctx, ret );
923 @brief Implement the savepoint.rollback method.
924 @param ctx Pointer to the method context.
925 @return Zero if successful, or -1 if not.
927 Issue a ROLLBACK TO SAVEPOINT to the database server.
930 - authkey (PCRUD only)
933 Return to client: Savepoint name
935 int rollbackSavepoint ( osrfMethodContext* ctx ) {
936 if(osrfMethodVerifyContext( ctx )) {
937 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
944 jsonObject* user = verifyUserPCRUD( ctx );
947 jsonObjectFree(user);
950 // Verify that a transaction is pending
951 const char* trans_id = getXactId( ctx );
952 if( NULL == trans_id ) {
953 osrfAppSessionStatus(
955 OSRF_STATUS_INTERNALSERVERERROR,
956 "osrfMethodException",
958 "No active transaction -- required for savepoints"
963 // Get the savepoint name from the method params
964 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
966 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
970 "%s: Error rolling back savepoint %s in transaction %s",
975 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
976 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
979 jsonObject* ret = jsonNewObject(spName);
980 osrfAppRespondComplete( ctx, ret );
987 @brief Implement the transaction.commit method.
988 @param ctx Pointer to the method context.
989 @return Zero if successful, or -1 if not.
991 Issue a COMMIT to the database server.
994 - authkey (PCRUD only)
996 Return to client: Transaction ID.
998 int commitTransaction ( osrfMethodContext* ctx ) {
999 if(osrfMethodVerifyContext( ctx )) {
1000 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1005 jsonObject* user = verifyUserPCRUD( ctx );
1008 jsonObjectFree(user);
1011 // Verify that a transaction is pending
1012 const char* trans_id = getXactId( ctx );
1013 if( NULL == trans_id ) {
1014 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1015 "osrfMethodException", ctx->request, "No active transaction to commit" );
1019 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
1021 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
1022 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1023 "osrfMethodException", ctx->request, "Error committing transaction" );
1026 jsonObject* ret = jsonNewObject( trans_id );
1027 osrfAppRespondComplete( ctx, ret );
1028 jsonObjectFree(ret);
1035 @brief Implement the transaction.rollback method.
1036 @param ctx Pointer to the method context.
1037 @return Zero if successful, or -1 if not.
1039 Issue a ROLLBACK to the database server.
1042 - authkey (PCRUD only)
1044 Return to client: Transaction ID
1046 int rollbackTransaction ( osrfMethodContext* ctx ) {
1047 if(osrfMethodVerifyContext( ctx )) {
1048 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1053 jsonObject* user = verifyUserPCRUD( ctx );
1056 jsonObjectFree(user);
1059 // Verify that a transaction is pending
1060 const char* trans_id = getXactId( ctx );
1061 if( NULL == trans_id ) {
1062 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1063 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1067 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
1069 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
1070 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1071 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1074 jsonObject* ret = jsonNewObject( trans_id );
1075 osrfAppRespondComplete( ctx, ret );
1076 jsonObjectFree(ret);
1083 @brief Implement the class-specific methods.
1084 @param ctx Pointer to the method context.
1085 @return Zero if successful, or -1 if not.
1087 Branch on the method type: create, retrieve, update, delete, search, and id_list.
1089 The method parameters and the type of value returned to the client depend on the method
1090 type. However, for PCRUD methods, the first method parameter should always be an
1093 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
1094 if(osrfMethodVerifyContext( ctx )) {
1095 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1099 int err = 0; // to be returned to caller
1100 jsonObject * obj = NULL; // to be returned to client
1102 // Get the method type so that we can branch on it
1103 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1104 const char* methodtype = osrfHashGet( method_meta, "methodtype" );
1106 if (!strcmp(methodtype, "create")) {
1107 obj = doCreate(ctx, &err);
1108 osrfAppRespondComplete( ctx, obj );
1110 else if (!strcmp(methodtype, "retrieve")) {
1111 obj = doRetrieve(ctx, &err);
1112 osrfAppRespondComplete( ctx, obj );
1114 else if (!strcmp(methodtype, "update")) {
1115 obj = doUpdate(ctx, &err);
1116 osrfAppRespondComplete( ctx, obj );
1118 else if (!strcmp(methodtype, "delete")) {
1119 obj = doDelete(ctx, &err);
1120 osrfAppRespondComplete( ctx, obj );
1122 else if (!strcmp(methodtype, "search")) {
1124 // Implement search method: return rows of the specified class that satisfy
1125 // a specified WHERE clause.
1127 // Method parameters:
1128 // - authkey (PCRUD only)
1129 // - WHERE clause, as jsonObject
1130 // - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1132 jsonObject* where_clause;
1133 jsonObject* rest_of_query;
1136 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1137 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1139 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1140 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1144 osrfHash* class_obj = osrfHashGet( method_meta, "class" );
1145 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
1150 // Return each row to the client (except that some may be suppressed by PCRUD)
1151 jsonObject* cur = 0;
1152 unsigned long res_idx = 0;
1153 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1155 if(!verifyObjectPCRUD(ctx, cur))
1158 osrfAppRespond( ctx, cur );
1160 osrfAppRespondComplete( ctx, NULL );
1162 } else if (!strcmp(methodtype, "id_list")) {
1164 // Implement the id_list method. Return the primary key values for all rows of the
1165 // relevant class that satisfy a specified WHERE clause. This method relies on the
1166 // assumption that every class has a primary key consisting of a single column.
1168 // Method parameters:
1169 // - authkey (PCRUD only)
1170 // - WHERE clause, as jsonObject
1171 // - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1173 jsonObject* where_clause;
1174 jsonObject* rest_of_query;
1176 // We use the where clause without change. But we need to massage the rest of the
1177 // query, so we work with a copy of it instead of modifying the original.
1180 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1181 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1183 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1184 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1187 // Eliminate certain SQL clauses, if present
1188 if ( rest_of_query ) {
1189 jsonObjectRemoveKey( rest_of_query, "select" );
1190 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1191 jsonObjectRemoveKey( rest_of_query, "flesh" );
1192 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
1194 rest_of_query = jsonNewObjectType( JSON_HASH );
1197 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1199 // Get the class metadata
1200 osrfHash* class_obj = osrfHashGet( method_meta, "class" );
1202 // Build a SELECT list containing just the primary key,
1203 // i.e. like { "classname":["keyname"] }
1204 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1205 jsonObjectPush( col_list_obj, // Load array with name of primary key
1206 jsonNewObject( osrfHashGet( class_obj, "primarykey" ) ) );
1207 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1208 jsonObjectSetKey( select_clause, osrfHashGet( class_obj, "classname" ), col_list_obj );
1210 jsonObjectSetKey( rest_of_query, "select", select_clause );
1213 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
1215 jsonObjectFree( rest_of_query );
1219 // Return each primary key value to the client
1221 unsigned long res_idx = 0;
1222 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1224 if(!verifyObjectPCRUD(ctx, cur))
1227 osrfAppRespond( ctx,
1228 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
1231 osrfAppRespondComplete( ctx, NULL );
1234 osrfAppRespondComplete( ctx, obj ); // should be unreachable...
1237 jsonObjectFree(obj);
1243 @brief Verify that we have a valid class reference.
1244 @param ctx Pointer to the method context.
1245 @param param Pointer to the method parameters.
1246 @return 1 if the class reference is valid, or zero if it isn't.
1248 The class of the method params must match the class to which the method id devoted.
1249 For PCRUD there are additional restrictions.
1251 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1253 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1254 osrfHash* class = osrfHashGet( method_meta, "class" );
1256 // Compare the method's class to the parameters' class
1257 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1259 // Oops -- they don't match. Complain.
1260 growing_buffer* msg = buffer_init(128);
1263 "%s: %s method for type %s was passed a %s",
1265 osrfHashGet(method_meta, "methodtype"),
1266 osrfHashGet(class, "classname"),
1267 param->classname ? param->classname : "(null)"
1270 char* m = buffer_release(msg);
1271 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1280 ret = verifyObjectPCRUD( ctx, param );
1289 @brief (PCRUD only) Verify that the user is properly logged in.
1290 @param ctx Pointer to the method context.
1291 @return If the user is logged in, a pointer to the user object from the authentication
1292 server; otherwise NULL.
1294 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1296 // Get the authkey (the first method parameter)
1297 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1298 jsonObject* auth_object = jsonNewObject(auth);
1300 // Fetch the user object from the authentication server
1301 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1303 jsonObjectFree(auth_object);
1305 if (!user->classname || strcmp(user->classname, "au")) {
1307 growing_buffer* msg = buffer_init(128);
1310 "%s: permacrud received a bad auth token: %s",
1315 char* m = buffer_release(msg);
1316 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1320 jsonObjectFree(user);
1327 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1329 dbhandle = writehandle;
1331 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1332 osrfHash* class = osrfHashGet( method_metadata, "class" );
1333 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1336 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
1337 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1338 } else if ( *method_type == 'u' || *method_type == 'd' ) {
1339 fetch = 1; // MUST go to the db for the object for update and delete
1342 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
1345 // No permacrud for this method type on this class
1347 growing_buffer* msg = buffer_init(128);
1350 "%s: %s on class %s has no permacrud IDL entry",
1352 osrfHashGet(method_metadata, "methodtype"),
1353 osrfHashGet(class, "classname")
1356 char* m = buffer_release(msg);
1357 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1358 "osrfMethodException", ctx->request, m );
1365 jsonObject* user = verifyUserPCRUD( ctx );
1369 int userid = atoi( oilsFMGetString( user, "id" ) );
1370 jsonObjectFree(user);
1372 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1373 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1374 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1376 osrfStringArray* context_org_array = osrfNewStringArray(1);
1379 char* pkey_value = NULL;
1380 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1381 osrfLogDebug( OSRF_LOG_MARK,
1382 "global-level permissions required, fetching top of the org tree" );
1384 // check for perm at top of org tree
1385 char* org_tree_root_id = org_tree_root( ctx );
1386 if( org_tree_root_id ) {
1387 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1388 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1390 osrfStringArrayFree( context_org_array );
1395 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1396 "fetching context org ids" );
1397 const char* pkey = osrfHashGet(class, "primarykey");
1398 jsonObject *param = NULL;
1400 if (obj->classname) {
1401 pkey_value = oilsFMGetString( obj, pkey );
1403 param = jsonObjectClone(obj);
1404 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1407 pkey_value = jsonObjectToSimpleString( obj );
1409 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1410 "of %s and retrieving from the database", pkey_value );
1414 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1415 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1416 jsonObjectFree(_tmp_params);
1418 param = jsonObjectExtractIndex(_list, 0);
1419 jsonObjectFree(_list);
1423 osrfLogDebug( OSRF_LOG_MARK,
1424 "Object not found in the database with primary key %s of %s",
1427 growing_buffer* msg = buffer_init(128);
1430 "%s: no object found with primary key %s of %s",
1436 char* m = buffer_release(msg);
1437 osrfAppSessionStatus(
1439 OSRF_STATUS_INTERNALSERVERERROR,
1440 "osrfMethodException",
1446 if (pkey_value) free(pkey_value);
1451 if (local_context->size > 0) {
1452 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1453 local_context->size);
1455 const char* lcontext = NULL;
1456 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1457 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1460 "adding class-local field %s (value: %s) to the context org list",
1462 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1467 if (foreign_context) {
1468 unsigned long class_count = osrfHashGetCount( foreign_context );
1469 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1471 if (class_count > 0) {
1473 osrfHash* fcontext = NULL;
1474 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1475 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1476 const char* class_name = osrfHashIteratorKey( class_itr );
1477 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1481 "%d foreign context fields(s) specified for class %s",
1482 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1486 char* foreign_pkey = osrfHashGet(fcontext, "field");
1487 char* foreign_pkey_value =
1488 oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1490 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1492 jsonObject* _list = doFieldmapperSearch(
1493 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1495 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1496 jsonObjectFree(_tmp_params);
1497 jsonObjectFree(_list);
1499 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1501 if (_fparam && jump_list) {
1502 const char* flink = NULL;
1504 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1505 free(foreign_pkey_value);
1507 osrfHash* foreign_link_hash =
1508 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1510 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1511 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1513 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1515 _list = doFieldmapperSearch(
1517 osrfHashGet( oilsIDL(),
1518 osrfHashGet( foreign_link_hash, "class" ) ),
1524 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1525 jsonObjectFree(_tmp_params);
1526 jsonObjectFree(_list);
1532 growing_buffer* msg = buffer_init(128);
1535 "%s: no object found with primary key %s of %s",
1541 char* m = buffer_release(msg);
1542 osrfAppSessionStatus(
1544 OSRF_STATUS_INTERNALSERVERERROR,
1545 "osrfMethodException",
1551 osrfHashIteratorFree(class_itr);
1552 free(foreign_pkey_value);
1553 jsonObjectFree(param);
1558 free(foreign_pkey_value);
1561 const char* foreign_field = NULL;
1562 while ( (foreign_field = osrfStringArrayGetString(
1563 osrfHashGet(fcontext,"context" ), j++)) ) {
1564 osrfStringArrayAdd( context_org_array,
1565 oilsFMGetString( _fparam, foreign_field ) );
1568 "adding foreign class %s field %s (value: %s) to the context org list",
1571 osrfStringArrayGetString(
1572 context_org_array, context_org_array->size - 1)
1576 jsonObjectFree(_fparam);
1579 osrfHashIteratorFree( class_itr );
1583 jsonObjectFree(param);
1586 const char* context_org = NULL;
1587 const char* perm = NULL;
1590 if (permission->size == 0) {
1591 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1596 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1598 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1604 "Checking object permission [%s] for user %d "
1605 "on object %s (class %s) at org %d",
1609 osrfHashGet(class, "classname"),
1613 result = dbi_conn_queryf(
1615 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1618 osrfHashGet(class, "classname"),
1626 "Received a result for object permission [%s] "
1627 "for user %d on object %s (class %s) at org %d",
1631 osrfHashGet(class, "classname"),
1635 if (dbi_result_first_row(result)) {
1636 jsonObject* return_val = oilsMakeJSONFromResult( result );
1637 const char* has_perm = jsonObjectGetString(
1638 jsonObjectGetKeyConst(return_val, "has_perm") );
1642 "Status of object permission [%s] for user %d "
1643 "on object %s (class %s) at org %d is %s",
1647 osrfHashGet(class, "classname"),
1652 if ( *has_perm == 't' ) OK = 1;
1653 jsonObjectFree(return_val);
1656 dbi_result_free(result);
1662 osrfLogDebug( OSRF_LOG_MARK,
1663 "Checking non-object permission [%s] for user %d at org %d",
1664 perm, userid, atoi(context_org) );
1665 result = dbi_conn_queryf(
1667 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1674 osrfLogDebug( OSRF_LOG_MARK,
1675 "Received a result for permission [%s] for user %d at org %d",
1676 perm, userid, atoi(context_org) );
1677 if ( dbi_result_first_row(result) ) {
1678 jsonObject* return_val = oilsMakeJSONFromResult( result );
1679 const char* has_perm = jsonObjectGetString(
1680 jsonObjectGetKeyConst(return_val, "has_perm") );
1681 osrfLogDebug( OSRF_LOG_MARK,
1682 "Status of permission [%s] for user %d at org %d is [%s]",
1683 perm, userid, atoi(context_org), has_perm );
1684 if ( *has_perm == 't' )
1686 jsonObjectFree(return_val);
1689 dbi_result_free(result);
1698 if (pkey_value) free(pkey_value);
1699 osrfStringArrayFree(context_org_array);
1705 @brief Look up the root of the org_unit tree.
1706 @param ctx Pointer to the method context.
1707 @return The id of the root org unit, as a character string.
1709 Query actor.org_unit where parent_ou is null, and return the id as a string.
1711 This function assumes that there is only one root org unit, i.e. that we
1712 have a single tree, not a forest.
1714 The calling code is responsible for freeing the returned string.
1716 static char* org_tree_root( osrfMethodContext* ctx ) {
1718 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1719 static time_t last_lookup_time = 0;
1720 time_t current_time = time( NULL );
1722 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1723 // We successfully looked this up less than an hour ago.
1724 // It's not likely to have changed since then.
1725 return strdup( cached_root_id );
1727 last_lookup_time = current_time;
1730 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1731 jsonObject* result = doFieldmapperSearch(
1732 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1733 jsonObjectFree( where_clause );
1735 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1738 jsonObjectFree( result );
1740 growing_buffer* msg = buffer_init(128);
1741 OSRF_BUFFER_ADD( msg, MODULENAME );
1742 OSRF_BUFFER_ADD( msg,
1743 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1745 char* m = buffer_release(msg);
1746 osrfAppSessionStatus( ctx->session,
1747 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1750 cached_root_id[ 0 ] = '\0';
1754 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1755 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1757 jsonObjectFree( result );
1759 strcpy( cached_root_id, root_org_unit_id );
1760 return root_org_unit_id;
1764 @brief Create a JSON_HASH with a single key/value pair.
1765 @param key The key of the key/value pair.
1766 @param value the value of the key/value pair.
1767 @return Pointer to a newly created jsonObject of type JSON_HASH.
1769 The value of the key/value is either a string or (if @a value is NULL) a null.
1771 static jsonObject* single_hash( const char* key, const char* value ) {
1773 if( ! key ) key = "";
1775 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1776 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1782 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1784 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1786 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1787 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1789 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1790 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1793 if (!verifyObjectClass(ctx, target)) {
1798 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1800 const char* trans_id = getXactId( ctx );
1802 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1804 osrfAppSessionStatus(
1806 OSRF_STATUS_BADREQUEST,
1807 "osrfMethodException",
1809 "No active transaction -- required for CREATE"
1815 // The following test is harmless but redundant. If a class is
1816 // readonly, we don't register a create method for it.
1817 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1818 osrfAppSessionStatus(
1820 OSRF_STATUS_BADREQUEST,
1821 "osrfMethodException",
1823 "Cannot INSERT readonly class"
1829 // Set the last_xact_id
1830 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1832 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
1833 trans_id, target->classname, index);
1834 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1837 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1839 dbhandle = writehandle;
1841 osrfHash* fields = osrfHashGet(meta, "fields");
1842 char* pkey = osrfHashGet(meta, "primarykey");
1843 char* seq = osrfHashGet(meta, "sequence");
1845 growing_buffer* table_buf = buffer_init(128);
1846 growing_buffer* col_buf = buffer_init(128);
1847 growing_buffer* val_buf = buffer_init(128);
1849 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1850 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1851 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1852 buffer_add(val_buf,"VALUES (");
1856 osrfHash* field = NULL;
1857 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1858 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1860 const char* field_name = osrfHashIteratorKey( field_itr );
1862 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1865 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1868 if (field_object && field_object->classname) {
1869 value = oilsFMGetString(
1871 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1873 } else if( field_object && JSON_BOOL == field_object->type ) {
1874 if( jsonBoolIsTrue( field_object ) )
1875 value = strdup( "t" );
1877 value = strdup( "f" );
1879 value = jsonObjectToSimpleString( field_object );
1885 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1886 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1889 buffer_add(col_buf, field_name);
1891 if (!field_object || field_object->type == JSON_NULL) {
1892 buffer_add( val_buf, "DEFAULT" );
1894 } else if ( !strcmp(get_primitive( field ), "number") ) {
1895 const char* numtype = get_datatype( field );
1896 if ( !strcmp( numtype, "INT8") ) {
1897 buffer_fadd( val_buf, "%lld", atoll(value) );
1899 } else if ( !strcmp( numtype, "INT") ) {
1900 buffer_fadd( val_buf, "%d", atoi(value) );
1902 } else if ( !strcmp( numtype, "NUMERIC") ) {
1903 buffer_fadd( val_buf, "%f", atof(value) );
1906 if ( dbi_conn_quote_string(writehandle, &value) ) {
1907 OSRF_BUFFER_ADD( val_buf, value );
1910 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1911 osrfAppSessionStatus(
1913 OSRF_STATUS_INTERNALSERVERERROR,
1914 "osrfMethodException",
1916 "Error quoting string -- please see the error log for more details"
1919 buffer_free(table_buf);
1920 buffer_free(col_buf);
1921 buffer_free(val_buf);
1931 osrfHashIteratorFree( field_itr );
1933 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1934 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1936 char* table_str = buffer_release(table_buf);
1937 char* col_str = buffer_release(col_buf);
1938 char* val_str = buffer_release(val_buf);
1939 growing_buffer* sql = buffer_init(128);
1940 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1945 char* query = buffer_release(sql);
1947 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1950 dbi_result result = dbi_conn_query(writehandle, query);
1952 jsonObject* obj = NULL;
1955 obj = jsonNewObject(NULL);
1958 "%s ERROR inserting %s object using query [%s]",
1960 osrfHashGet(meta, "fieldmapper"),
1963 osrfAppSessionStatus(
1965 OSRF_STATUS_INTERNALSERVERERROR,
1966 "osrfMethodException",
1968 "INSERT error -- please see the error log for more details"
1973 char* id = oilsFMGetString(target, pkey);
1975 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1976 growing_buffer* _id = buffer_init(10);
1977 buffer_fadd(_id, "%lld", new_id);
1978 id = buffer_release(_id);
1981 // Find quietness specification, if present
1982 const char* quiet_str = NULL;
1984 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1986 quiet_str = jsonObjectGetString( quiet_obj );
1989 if( str_is_true( quiet_str ) ) { // if quietness is specified
1990 obj = jsonNewObject(id);
1994 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1995 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1997 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1999 jsonObjectFree( where_clause );
2004 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
2007 jsonObjectFree( list );
2020 @brief Implement the retrieve method.
2021 @param ctx Pointer to the method context.
2022 @param err Pointer through which to return an error code.
2023 @return If successful, a pointer to the result to be returned to the client;
2026 From the method's class, fetch a row with a specified value in the primary key. This
2027 method relies on the database design convention that a primary key consists of a single
2031 - authkey (PCRUD only)
2032 - value of the primary key for the desired row, for building the WHERE clause
2033 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2035 Return to client: One row from the query.
2037 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
2047 // Get the class metadata
2048 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2050 // Get the value of the primary key, from a method parameter
2051 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos);
2055 "%s retrieving %s object with primary key value of %s",
2057 osrfHashGet( class_def, "fieldmapper" ),
2058 jsonObjectGetString( id_obj )
2061 // Build a WHERE clause based on the key value
2062 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2065 osrfHashGet( class_def, "primarykey" ), // name of key column
2066 jsonObjectClone( id_obj ) // value of key column
2069 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
2072 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
2074 jsonObjectFree( where_clause );
2078 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2079 jsonObjectFree( list );
2082 if(!verifyObjectPCRUD(ctx, obj)) {
2083 jsonObjectFree(obj);
2086 growing_buffer* msg = buffer_init(128);
2087 OSRF_BUFFER_ADD( msg, MODULENAME );
2088 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2090 char* m = buffer_release(msg);
2091 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2103 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
2104 growing_buffer* val_buf = buffer_init(32);
2105 const char* numtype = get_datatype( field );
2107 if ( !strncmp( numtype, "INT", 3 ) ) {
2108 if (value->type == JSON_NUMBER)
2109 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2110 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2112 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2115 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
2116 if (value->type == JSON_NUMBER)
2117 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2119 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2123 // Presumably this was really intended ot be a string, so quote it
2124 char* str = jsonObjectToSimpleString( value );
2125 if ( dbi_conn_quote_string(dbhandle, &str) ) {
2126 OSRF_BUFFER_ADD( val_buf, str );
2129 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
2131 buffer_free(val_buf);
2136 return buffer_release(val_buf);
2139 static char* searchINPredicate (const char* class_alias, osrfHash* field,
2140 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2141 growing_buffer* sql_buf = buffer_init(32);
2147 osrfHashGet(field, "name")
2151 buffer_add(sql_buf, "IN (");
2152 } else if (!(strcasecmp(op,"not in"))) {
2153 buffer_add(sql_buf, "NOT IN (");
2155 buffer_add(sql_buf, "IN (");
2158 if (node->type == JSON_HASH) {
2159 // subquery predicate
2160 char* subpred = buildQuery( ctx, node, SUBSELECT );
2162 buffer_free( sql_buf );
2166 buffer_add(sql_buf, subpred);
2169 } else if (node->type == JSON_ARRAY) {
2170 // literal value list
2171 int in_item_index = 0;
2172 int in_item_first = 1;
2173 const jsonObject* in_item;
2174 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
2179 buffer_add(sql_buf, ", ");
2182 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2183 osrfLogError( OSRF_LOG_MARK,
2184 "%s: Expected string or number within IN list; found %s",
2185 MODULENAME, json_type( in_item->type ) );
2186 buffer_free(sql_buf);
2190 // Append the literal value -- quoted if not a number
2191 if ( JSON_NUMBER == in_item->type ) {
2192 char* val = jsonNumberToDBString( field, in_item );
2193 OSRF_BUFFER_ADD( sql_buf, val );
2196 } else if ( !strcmp( get_primitive( field ), "number") ) {
2197 char* val = jsonNumberToDBString( field, in_item );
2198 OSRF_BUFFER_ADD( sql_buf, val );
2202 char* key_string = jsonObjectToSimpleString(in_item);
2203 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
2204 OSRF_BUFFER_ADD( sql_buf, key_string );
2207 osrfLogError(OSRF_LOG_MARK,
2208 "%s: Error quoting key string [%s]", MODULENAME, key_string);
2210 buffer_free(sql_buf);
2216 if( in_item_first ) {
2217 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
2218 buffer_free( sql_buf );
2222 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2223 MODULENAME, json_type( node->type ) );
2224 buffer_free(sql_buf);
2228 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2230 return buffer_release(sql_buf);
2233 // Receive a JSON_ARRAY representing a function call. The first
2234 // entry in the array is the function name. The rest are parameters.
2235 static char* searchValueTransform( const jsonObject* array ) {
2237 if( array->size < 1 ) {
2238 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
2242 // Get the function name
2243 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2244 if( func_item->type != JSON_STRING ) {
2245 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2246 MODULENAME, json_type( func_item->type ) );
2250 growing_buffer* sql_buf = buffer_init(32);
2252 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2253 OSRF_BUFFER_ADD( sql_buf, "( " );
2255 // Get the parameters
2256 int func_item_index = 1; // We already grabbed the zeroth entry
2257 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2259 // Add a separator comma, if we need one
2260 if( func_item_index > 2 )
2261 buffer_add( sql_buf, ", " );
2263 // Add the current parameter
2264 if (func_item->type == JSON_NULL) {
2265 buffer_add( sql_buf, "NULL" );
2267 char* val = jsonObjectToSimpleString(func_item);
2268 if ( dbi_conn_quote_string(dbhandle, &val) ) {
2269 OSRF_BUFFER_ADD( sql_buf, val );
2272 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2273 buffer_free(sql_buf);
2280 buffer_add( sql_buf, " )" );
2282 return buffer_release(sql_buf);
2285 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
2286 const jsonObject* node, const char* op) {
2288 if( ! is_good_operator( op ) ) {
2289 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2293 char* val = searchValueTransform(node);
2297 growing_buffer* sql_buf = buffer_init(32);
2302 osrfHashGet(field, "name"),
2309 return buffer_release(sql_buf);
2312 // class_alias is a class name or other table alias
2313 // field is a field definition as stored in the IDL
2314 // node comes from the method parameter, and may represent an entry in the SELECT list
2315 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
2316 growing_buffer* sql_buf = buffer_init(32);
2318 const char* field_transform = jsonObjectGetString(
2319 jsonObjectGetKeyConst( node, "transform" ) );
2320 const char* transform_subcolumn = jsonObjectGetString(
2321 jsonObjectGetKeyConst( node, "result_field" ) );
2323 if(transform_subcolumn) {
2324 if( ! is_identifier( transform_subcolumn ) ) {
2325 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2326 MODULENAME, transform_subcolumn );
2327 buffer_free( sql_buf );
2330 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2333 if (field_transform) {
2335 if( ! is_identifier( field_transform ) ) {
2336 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2337 MODULENAME, field_transform );
2338 buffer_free( sql_buf );
2342 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2343 field_transform, class_alias, osrfHashGet(field, "name"));
2344 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2347 if( array->type != JSON_ARRAY ) {
2348 osrfLogError( OSRF_LOG_MARK,
2349 "%s: Expected JSON_ARRAY for function params; found %s",
2350 MODULENAME, json_type( array->type ) );
2351 buffer_free( sql_buf );
2354 int func_item_index = 0;
2355 jsonObject* func_item;
2356 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2358 char* val = jsonObjectToSimpleString(func_item);
2361 buffer_add( sql_buf, ",NULL" );
2362 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
2363 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2364 OSRF_BUFFER_ADD( sql_buf, val );
2366 osrfLogError( OSRF_LOG_MARK,
2367 "%s: Error quoting key string [%s]", MODULENAME, val);
2369 buffer_free(sql_buf);
2376 buffer_add( sql_buf, " )" );
2379 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2382 if (transform_subcolumn)
2383 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2385 return buffer_release(sql_buf);
2388 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2389 const jsonObject* node, const char* op ) {
2391 if( ! is_good_operator( op ) ) {
2392 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2396 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2397 if( ! field_transform )
2400 int extra_parens = 0; // boolean
2402 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2403 if ( ! value_obj ) {
2404 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2406 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2408 free(field_transform);
2412 } else if ( value_obj->type == JSON_ARRAY ) {
2413 value = searchValueTransform( value_obj );
2415 osrfLogError(OSRF_LOG_MARK,
2416 "%s: Error building value transform for field transform", MODULENAME);
2417 free( field_transform );
2420 } else if ( value_obj->type == JSON_HASH ) {
2421 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2423 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2425 free(field_transform);
2429 } else if ( value_obj->type == JSON_NUMBER ) {
2430 value = jsonNumberToDBString( field, value_obj );
2431 } else if ( value_obj->type == JSON_NULL ) {
2432 osrfLogError( OSRF_LOG_MARK,
2433 "%s: Error building predicate for field transform: null value", MODULENAME );
2434 free(field_transform);
2436 } else if ( value_obj->type == JSON_BOOL ) {
2437 osrfLogError( OSRF_LOG_MARK,
2438 "%s: Error building predicate for field transform: boolean value", MODULENAME );
2439 free(field_transform);
2442 if ( !strcmp( get_primitive( field ), "number") ) {
2443 value = jsonNumberToDBString( field, value_obj );
2445 value = jsonObjectToSimpleString( value_obj );
2446 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2447 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2450 free(field_transform);
2456 const char* left_parens = "";
2457 const char* right_parens = "";
2459 if( extra_parens ) {
2464 growing_buffer* sql_buf = buffer_init(32);
2468 "%s%s %s %s %s %s%s",
2479 free(field_transform);
2481 return buffer_release(sql_buf);
2484 static char* searchSimplePredicate (const char* op, const char* class_alias,
2485 osrfHash* field, const jsonObject* node) {
2487 if( ! is_good_operator( op ) ) {
2488 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2494 // Get the value to which we are comparing the specified column
2495 if (node->type != JSON_NULL) {
2496 if ( node->type == JSON_NUMBER ) {
2497 val = jsonNumberToDBString( field, node );
2498 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2499 val = jsonNumberToDBString( field, node );
2501 val = jsonObjectToSimpleString(node);
2506 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2507 // Value is not numeric; enclose it in quotes
2508 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2509 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2516 // Compare to a null value
2517 val = strdup( "NULL" );
2518 if (strcmp( op, "=" ))
2524 growing_buffer* sql_buf = buffer_init(32);
2525 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2526 char* pred = buffer_release( sql_buf );
2533 static char* searchBETWEENPredicate (const char* class_alias,
2534 osrfHash* field, const jsonObject* node) {
2536 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2537 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2539 if( NULL == y_node ) {
2540 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2543 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2544 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2551 if ( !strcmp( get_primitive( field ), "number") ) {
2552 x_string = jsonNumberToDBString(field, x_node);
2553 y_string = jsonNumberToDBString(field, y_node);
2556 x_string = jsonObjectToSimpleString(x_node);
2557 y_string = jsonObjectToSimpleString(y_node);
2558 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2559 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2560 MODULENAME, x_string, y_string);
2567 growing_buffer* sql_buf = buffer_init(32);
2568 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2569 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2573 return buffer_release(sql_buf);
2576 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2577 jsonObject* node, osrfMethodContext* ctx ) {
2580 if (node->type == JSON_ARRAY) { // equality IN search
2581 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2582 } else if (node->type == JSON_HASH) { // other search
2583 jsonIterator* pred_itr = jsonNewIterator( node );
2584 if( !jsonIteratorHasNext( pred_itr ) ) {
2585 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2586 MODULENAME, osrfHashGet(field, "name") );
2588 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2590 // Verify that there are no additional predicates
2591 if( jsonIteratorHasNext( pred_itr ) ) {
2592 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2593 MODULENAME, osrfHashGet(field, "name") );
2594 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2595 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2596 else if ( !(strcasecmp( pred_itr->key,"in" ))
2597 || !(strcasecmp( pred_itr->key,"not in" )) )
2598 pred = searchINPredicate(
2599 class_info->alias, field, pred_node, pred_itr->key, ctx );
2600 else if ( pred_node->type == JSON_ARRAY )
2601 pred = searchFunctionPredicate(
2602 class_info->alias, field, pred_node, pred_itr->key );
2603 else if ( pred_node->type == JSON_HASH )
2604 pred = searchFieldTransformPredicate(
2605 class_info, field, pred_node, pred_itr->key );
2607 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2609 jsonIteratorFree(pred_itr);
2611 } else if (node->type == JSON_NULL) { // IS NULL search
2612 growing_buffer* _p = buffer_init(64);
2615 "\"%s\".%s IS NULL",
2616 class_info->class_name,
2617 osrfHashGet(field, "name")
2619 pred = buffer_release(_p);
2620 } else { // equality search
2621 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2640 field : call_number,
2656 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2658 const jsonObject* working_hash;
2659 jsonObject* freeable_hash = NULL;
2661 if (join_hash->type == JSON_HASH) {
2662 working_hash = join_hash;
2663 } else if (join_hash->type == JSON_STRING) {
2664 // turn it into a JSON_HASH by creating a wrapper
2665 // around a copy of the original
2666 const char* _tmp = jsonObjectGetString( join_hash );
2667 freeable_hash = jsonNewObjectType(JSON_HASH);
2668 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2669 working_hash = freeable_hash;
2673 "%s: JOIN failed; expected JSON object type not found",
2679 growing_buffer* join_buf = buffer_init(128);
2680 const char* leftclass = left_info->class_name;
2682 jsonObject* snode = NULL;
2683 jsonIterator* search_itr = jsonNewIterator( working_hash );
2685 while ( (snode = jsonIteratorNext( search_itr )) ) {
2686 const char* right_alias = search_itr->key;
2688 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2690 class = right_alias;
2692 const ClassInfo* right_info = add_joined_class( right_alias, class );
2696 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2700 jsonIteratorFree( search_itr );
2701 buffer_free( join_buf );
2703 jsonObjectFree( freeable_hash );
2706 osrfHash* links = right_info->links;
2707 const char* table = right_info->source_def;
2709 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2710 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2712 if (field && !fkey) {
2713 // Look up the corresponding join column in the IDL.
2714 // The link must be defined in the child table,
2715 // and point to the right parent table.
2716 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2717 const char* reltype = NULL;
2718 const char* other_class = NULL;
2719 reltype = osrfHashGet( idl_link, "reltype" );
2720 if( reltype && strcmp( reltype, "has_many" ) )
2721 other_class = osrfHashGet( idl_link, "class" );
2722 if( other_class && !strcmp( other_class, leftclass ) )
2723 fkey = osrfHashGet( idl_link, "key" );
2727 "%s: JOIN failed. No link defined from %s.%s to %s",
2733 buffer_free(join_buf);
2735 jsonObjectFree(freeable_hash);
2736 jsonIteratorFree(search_itr);
2740 } else if (!field && fkey) {
2741 // Look up the corresponding join column in the IDL.
2742 // The link must be defined in the child table,
2743 // and point to the right parent table.
2744 osrfHash* left_links = left_info->links;
2745 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2746 const char* reltype = NULL;
2747 const char* other_class = NULL;
2748 reltype = osrfHashGet( idl_link, "reltype" );
2749 if( reltype && strcmp( reltype, "has_many" ) )
2750 other_class = osrfHashGet( idl_link, "class" );
2751 if( other_class && !strcmp( other_class, class ) )
2752 field = osrfHashGet( idl_link, "key" );
2756 "%s: JOIN failed. No link defined from %s.%s to %s",
2762 buffer_free(join_buf);
2764 jsonObjectFree(freeable_hash);
2765 jsonIteratorFree(search_itr);
2769 } else if (!field && !fkey) {
2770 osrfHash* left_links = left_info->links;
2772 // For each link defined for the left class:
2773 // see if the link references the joined class
2774 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2775 osrfHash* curr_link = NULL;
2776 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2777 const char* other_class = osrfHashGet( curr_link, "class" );
2778 if( other_class && !strcmp( other_class, class ) ) {
2780 // In the IDL, the parent class doesn't always know then names of the child
2781 // columns that are pointing to it, so don't use that end of the link
2782 const char* reltype = osrfHashGet( curr_link, "reltype" );
2783 if( reltype && strcmp( reltype, "has_many" ) ) {
2784 // Found a link between the classes
2785 fkey = osrfHashIteratorKey( itr );
2786 field = osrfHashGet( curr_link, "key" );
2791 osrfHashIteratorFree( itr );
2793 if (!field || !fkey) {
2794 // Do another such search, with the classes reversed
2796 // For each link defined for the joined class:
2797 // see if the link references the left class
2798 osrfHashIterator* itr = osrfNewHashIterator( links );
2799 osrfHash* curr_link = NULL;
2800 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2801 const char* other_class = osrfHashGet( curr_link, "class" );
2802 if( other_class && !strcmp( other_class, leftclass ) ) {
2804 // In the IDL, the parent class doesn't know then names of the child
2805 // columns that are pointing to it, so don't use that end of the link
2806 const char* reltype = osrfHashGet( curr_link, "reltype" );
2807 if( reltype && strcmp( reltype, "has_many" ) ) {
2808 // Found a link between the classes
2809 field = osrfHashIteratorKey( itr );
2810 fkey = osrfHashGet( curr_link, "key" );
2815 osrfHashIteratorFree( itr );
2818 if (!field || !fkey) {
2821 "%s: JOIN failed. No link defined between %s and %s",
2826 buffer_free(join_buf);
2828 jsonObjectFree(freeable_hash);
2829 jsonIteratorFree(search_itr);
2834 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2836 if ( !strcasecmp(type,"left") ) {
2837 buffer_add(join_buf, " LEFT JOIN");
2838 } else if ( !strcasecmp(type,"right") ) {
2839 buffer_add(join_buf, " RIGHT JOIN");
2840 } else if ( !strcasecmp(type,"full") ) {
2841 buffer_add(join_buf, " FULL JOIN");
2843 buffer_add(join_buf, " INNER JOIN");
2846 buffer_add(join_buf, " INNER JOIN");
2849 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2850 table, right_alias, right_alias, field, left_info->alias, fkey);
2852 // Add any other join conditions as specified by "filter"
2853 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2855 const char* filter_op = jsonObjectGetString(
2856 jsonObjectGetKeyConst( snode, "filter_op" ) );
2857 if ( filter_op && !strcasecmp("or",filter_op) ) {
2858 buffer_add( join_buf, " OR " );
2860 buffer_add( join_buf, " AND " );
2863 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2865 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2866 OSRF_BUFFER_ADD( join_buf, jpred );
2871 "%s: JOIN failed. Invalid conditional expression.",
2874 jsonIteratorFree( search_itr );
2875 buffer_free( join_buf );
2877 jsonObjectFree( freeable_hash );
2882 buffer_add(join_buf, " ) ");
2884 // Recursively add a nested join, if one is present
2885 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2887 char* jpred = searchJOIN( join_filter, right_info );
2889 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2890 OSRF_BUFFER_ADD( join_buf, jpred );
2893 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2894 jsonIteratorFree( search_itr );
2895 buffer_free( join_buf );
2897 jsonObjectFree( freeable_hash );
2904 jsonObjectFree(freeable_hash);
2905 jsonIteratorFree(search_itr);
2907 return buffer_release(join_buf);
2912 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2913 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2914 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2916 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2918 search_hash is the JSON expression of the conditions.
2919 meta is the class definition from the IDL, for the relevant table.
2920 opjoin_type indicates whether multiple conditions, if present, should be
2921 connected by AND or OR.
2922 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2923 to pass it to other functions -- and all they do with it is to use the session
2924 and request members to send error messages back to the client.
2928 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
2929 int opjoin_type, osrfMethodContext* ctx ) {
2933 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
2934 "opjoin_type = %d, ctx addr = %p",
2937 class_info->class_def,
2942 growing_buffer* sql_buf = buffer_init(128);
2944 jsonObject* node = NULL;
2947 if ( search_hash->type == JSON_ARRAY ) {
2948 osrfLogDebug( OSRF_LOG_MARK,
2949 "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME );
2950 if( 0 == search_hash->size ) {
2953 "%s: Invalid predicate structure: empty JSON array",
2956 buffer_free( sql_buf );
2960 unsigned long i = 0;
2961 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
2965 if (opjoin_type == OR_OP_JOIN)
2966 buffer_add(sql_buf, " OR ");
2968 buffer_add(sql_buf, " AND ");
2971 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2973 buffer_free( sql_buf );
2977 buffer_fadd(sql_buf, "( %s )", subpred);
2981 } else if ( search_hash->type == JSON_HASH ) {
2982 osrfLogDebug( OSRF_LOG_MARK,
2983 "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME );
2984 jsonIterator* search_itr = jsonNewIterator( search_hash );
2985 if( !jsonIteratorHasNext( search_itr ) ) {
2988 "%s: Invalid predicate structure: empty JSON object",
2991 jsonIteratorFree( search_itr );
2992 buffer_free( sql_buf );
2996 while ( (node = jsonIteratorNext( search_itr )) ) {
3001 if (opjoin_type == OR_OP_JOIN)
3002 buffer_add(sql_buf, " OR ");
3004 buffer_add(sql_buf, " AND ");
3007 if ( '+' == search_itr->key[ 0 ] ) {
3009 // This plus sign prefixes a class name or other table alias;
3010 // make sure the table alias is in scope
3011 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3012 if( ! alias_info ) {
3015 "%s: Invalid table alias \"%s\" in WHERE clause",
3019 jsonIteratorFree( search_itr );
3020 buffer_free( sql_buf );
3024 if ( node->type == JSON_STRING ) {
3025 // It's the name of a column; make sure it belongs to the class
3026 const char* fieldname = jsonObjectGetString( node );
3027 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3030 "%s: Invalid column name \"%s\" in WHERE clause "
3031 "for table alias \"%s\"",
3036 jsonIteratorFree( search_itr );
3037 buffer_free( sql_buf );
3041 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3043 // It's something more complicated
3044 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3046 jsonIteratorFree( search_itr );
3047 buffer_free( sql_buf );
3051 buffer_fadd(sql_buf, "( %s )", subpred);
3054 } else if ( '-' == search_itr->key[ 0 ] ) {
3055 if ( !strcasecmp("-or",search_itr->key) ) {
3056 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3058 jsonIteratorFree( search_itr );
3059 buffer_free( sql_buf );
3063 buffer_fadd(sql_buf, "( %s )", subpred);
3065 } else if ( !strcasecmp("-and",search_itr->key) ) {
3066 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3068 jsonIteratorFree( search_itr );
3069 buffer_free( sql_buf );
3073 buffer_fadd(sql_buf, "( %s )", subpred);
3075 } else if ( !strcasecmp("-not",search_itr->key) ) {
3076 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3078 jsonIteratorFree( search_itr );
3079 buffer_free( sql_buf );
3083 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
3085 } else if ( !strcasecmp("-exists",search_itr->key) ) {
3086 char* subpred = buildQuery( ctx, node, SUBSELECT );
3088 jsonIteratorFree( search_itr );
3089 buffer_free( sql_buf );
3093 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
3095 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
3096 char* subpred = buildQuery( ctx, node, SUBSELECT );
3098 jsonIteratorFree( search_itr );
3099 buffer_free( sql_buf );
3103 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
3105 } else { // Invalid "minus" operator
3108 "%s: Invalid operator \"%s\" in WHERE clause",
3112 jsonIteratorFree( search_itr );
3113 buffer_free( sql_buf );
3119 const char* class = class_info->class_name;
3120 osrfHash* fields = class_info->fields;
3121 osrfHash* field = osrfHashGet( fields, search_itr->key );
3124 const char* table = class_info->source_def;
3127 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3130 table ? table : "?",
3133 jsonIteratorFree(search_itr);
3134 buffer_free(sql_buf);
3138 char* subpred = searchPredicate( class_info, field, node, ctx );
3140 buffer_free(sql_buf);
3141 jsonIteratorFree(search_itr);
3145 buffer_add( sql_buf, subpred );
3149 jsonIteratorFree(search_itr);
3152 // ERROR ... only hash and array allowed at this level
3153 char* predicate_string = jsonObjectToJSON( search_hash );
3156 "%s: Invalid predicate structure: %s",
3160 buffer_free(sql_buf);
3161 free(predicate_string);
3165 return buffer_release(sql_buf);
3168 /* Build a JSON_ARRAY of field names for a given table alias
3170 static jsonObject* defaultSelectList( const char* table_alias ) {
3175 ClassInfo* class_info = search_all_alias( table_alias );
3176 if( ! class_info ) {
3179 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3186 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3187 osrfHash* field_def = NULL;
3188 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3189 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3190 const char* field_name = osrfHashIteratorKey( field_itr );
3191 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3192 jsonObjectPush( array, jsonNewObject( field_name ) );
3195 osrfHashIteratorFree( field_itr );
3200 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3201 // The jsonObject must be a JSON_HASH with an single entry for "union",
3202 // "intersect", or "except". The data associated with this key must be an
3203 // array of hashes, each hash being a query.
3204 // Also allowed but currently ignored: entries for "order_by" and "alias".
3205 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3207 if( ! combo || combo->type != JSON_HASH )
3208 return NULL; // should be impossible; validated by caller
3210 const jsonObject* query_array = NULL; // array of subordinate queries
3211 const char* op = NULL; // name of operator, e.g. UNION
3212 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3213 int op_count = 0; // for detecting conflicting operators
3214 int excepting = 0; // boolean
3215 int all = 0; // boolean
3216 jsonObject* order_obj = NULL;
3218 // Identify the elements in the hash
3219 jsonIterator* query_itr = jsonNewIterator( combo );
3220 jsonObject* curr_obj = NULL;
3221 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3222 if( ! strcmp( "union", query_itr->key ) ) {
3225 query_array = curr_obj;
3226 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3229 query_array = curr_obj;
3230 } else if( ! strcmp( "except", query_itr->key ) ) {
3234 query_array = curr_obj;
3235 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3238 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3241 order_obj = curr_obj;
3242 } else if( ! strcmp( "alias", query_itr->key ) ) {
3243 if( curr_obj->type != JSON_STRING ) {
3244 jsonIteratorFree( query_itr );
3247 alias = jsonObjectGetString( curr_obj );
3248 } else if( ! strcmp( "all", query_itr->key ) ) {
3249 if( obj_is_true( curr_obj ) )
3253 osrfAppSessionStatus(
3255 OSRF_STATUS_INTERNALSERVERERROR,
3256 "osrfMethodException",
3258 "Malformed query; unexpected entry in query object"
3262 "%s: Unexpected entry for \"%s\" in%squery",
3267 jsonIteratorFree( query_itr );
3271 jsonIteratorFree( query_itr );
3273 // More sanity checks
3274 if( ! query_array ) {
3276 osrfAppSessionStatus(
3278 OSRF_STATUS_INTERNALSERVERERROR,
3279 "osrfMethodException",
3281 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3285 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3288 return NULL; // should be impossible...
3289 } else if( op_count > 1 ) {
3291 osrfAppSessionStatus(
3293 OSRF_STATUS_INTERNALSERVERERROR,
3294 "osrfMethodException",
3296 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3300 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3304 } if( query_array->type != JSON_ARRAY ) {
3306 osrfAppSessionStatus(
3308 OSRF_STATUS_INTERNALSERVERERROR,
3309 "osrfMethodException",
3311 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3315 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3318 json_type( query_array->type )
3321 } if( query_array->size < 2 ) {
3323 osrfAppSessionStatus(
3325 OSRF_STATUS_INTERNALSERVERERROR,
3326 "osrfMethodException",
3328 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3332 "%s:%srequires multiple queries as operands",
3337 } else if( excepting && query_array->size > 2 ) {
3339 osrfAppSessionStatus(
3341 OSRF_STATUS_INTERNALSERVERERROR,
3342 "osrfMethodException",
3344 "EXCEPT operator has too many queries as operands"
3348 "%s:EXCEPT operator has too many queries as operands",
3352 } else if( order_obj && ! alias ) {
3354 osrfAppSessionStatus(
3356 OSRF_STATUS_INTERNALSERVERERROR,
3357 "osrfMethodException",
3359 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3363 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3369 // So far so good. Now build the SQL.
3370 growing_buffer* sql = buffer_init( 256 );
3372 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3373 // Add a layer of parentheses
3374 if( flags & SUBCOMBO )
3375 OSRF_BUFFER_ADD( sql, "( " );
3377 // Traverse the query array. Each entry should be a hash.
3378 int first = 1; // boolean
3380 jsonObject* query = NULL;
3381 while((query = jsonObjectGetIndex( query_array, i++ ) )) {
3382 if( query->type != JSON_HASH ) {
3384 osrfAppSessionStatus(
3386 OSRF_STATUS_INTERNALSERVERERROR,
3387 "osrfMethodException",
3389 "Malformed query under UNION, INTERSECT or EXCEPT"
3393 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3396 json_type( query->type )
3405 OSRF_BUFFER_ADD( sql, op );
3407 OSRF_BUFFER_ADD( sql, "ALL " );
3410 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3414 "%s: Error building query under%s",
3422 OSRF_BUFFER_ADD( sql, query_str );
3425 if( flags & SUBCOMBO )
3426 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3428 if ( !(flags & SUBSELECT) )
3429 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3431 return buffer_release( sql );
3434 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3435 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3436 // or "except" to indicate the type of query.
3437 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3441 osrfAppSessionStatus(
3443 OSRF_STATUS_INTERNALSERVERERROR,
3444 "osrfMethodException",
3446 "Malformed query; no query object"
3448 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", MODULENAME );
3450 } else if( query->type != JSON_HASH ) {
3452 osrfAppSessionStatus(
3454 OSRF_STATUS_INTERNALSERVERERROR,
3455 "osrfMethodException",
3457 "Malformed query object"
3461 "%s: Query object is %s instead of JSON_HASH",
3463 json_type( query->type )
3468 // Determine what kind of query it purports to be, and dispatch accordingly.
3469 if( jsonObjectGetKey( query, "union" ) ||
3470 jsonObjectGetKey( query, "intersect" ) ||
3471 jsonObjectGetKey( query, "except" ) ) {
3472 return doCombo( ctx, query, flags );
3474 // It is presumably a SELECT query
3476 // Push a node onto the stack for the current query. Every level of
3477 // subquery gets its own QueryFrame on the Stack.
3480 // Build an SQL SELECT statement
3483 jsonObjectGetKey( query, "select" ),
3484 jsonObjectGetKey( query, "from" ),
3485 jsonObjectGetKey( query, "where" ),
3486 jsonObjectGetKey( query, "having" ),
3487 jsonObjectGetKey( query, "order_by" ),
3488 jsonObjectGetKey( query, "limit" ),
3489 jsonObjectGetKey( query, "offset" ),
3498 /* method context */ osrfMethodContext* ctx,
3500 /* SELECT */ jsonObject* selhash,
3501 /* FROM */ jsonObject* join_hash,
3502 /* WHERE */ jsonObject* search_hash,
3503 /* HAVING */ jsonObject* having_hash,
3504 /* ORDER BY */ jsonObject* order_hash,
3505 /* LIMIT */ jsonObject* limit,
3506 /* OFFSET */ jsonObject* offset,
3507 /* flags */ int flags
3509 const char* locale = osrf_message_get_last_locale();
3511 // general tmp objects
3512 const jsonObject* tmp_const;
3513 jsonObject* selclass = NULL;
3514 jsonObject* snode = NULL;
3515 jsonObject* onode = NULL;
3517 char* string = NULL;
3518 int from_function = 0;
3523 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3525 // punt if there's no FROM clause
3526 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3529 "%s: FROM clause is missing or empty",
3533 osrfAppSessionStatus(
3535 OSRF_STATUS_INTERNALSERVERERROR,
3536 "osrfMethodException",
3538 "FROM clause is missing or empty in JSON query"
3543 // the core search class
3544 const char* core_class = NULL;
3546 // get the core class -- the only key of the top level FROM clause, or a string
3547 if (join_hash->type == JSON_HASH) {
3548 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3549 snode = jsonIteratorNext( tmp_itr );
3551 // Populate the current QueryFrame with information
3552 // about the core class
3553 if( add_query_core( NULL, tmp_itr->key ) ) {
3555 osrfAppSessionStatus(
3557 OSRF_STATUS_INTERNALSERVERERROR,
3558 "osrfMethodException",
3560 "Unable to look up core class"
3564 core_class = curr_query->core.class_name;
3567 jsonObject* extra = jsonIteratorNext( tmp_itr );
3569 jsonIteratorFree( tmp_itr );
3572 // There shouldn't be more than one entry in join_hash
3576 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3580 osrfAppSessionStatus(
3582 OSRF_STATUS_INTERNALSERVERERROR,
3583 "osrfMethodException",
3585 "Malformed FROM clause in JSON query"
3587 return NULL; // Malformed join_hash; extra entry
3589 } else if (join_hash->type == JSON_ARRAY) {
3590 // We're selecting from a function, not from a table
3592 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
3595 } else if (join_hash->type == JSON_STRING) {
3596 // Populate the current QueryFrame with information
3597 // about the core class
3598 core_class = jsonObjectGetString( join_hash );
3600 if( add_query_core( NULL, core_class ) ) {
3602 osrfAppSessionStatus(
3604 OSRF_STATUS_INTERNALSERVERERROR,
3605 "osrfMethodException",
3607 "Unable to look up core class"
3615 "%s: FROM clause is unexpected JSON type: %s",
3617 json_type( join_hash->type )
3620 osrfAppSessionStatus(
3622 OSRF_STATUS_INTERNALSERVERERROR,
3623 "osrfMethodException",
3625 "Ill-formed FROM clause in JSON query"
3630 // Build the join clause, if any, while filling out the list
3631 // of joined classes in the current QueryFrame.
3632 char* join_clause = NULL;
3633 if( join_hash && ! from_function ) {
3635 join_clause = searchJOIN( join_hash, &curr_query->core );
3636 if( ! join_clause ) {
3638 osrfAppSessionStatus(
3640 OSRF_STATUS_INTERNALSERVERERROR,
3641 "osrfMethodException",
3643 "Unable to construct JOIN clause(s)"
3649 // For in case we don't get a select list
3650 jsonObject* defaultselhash = NULL;
3652 // if there is no select list, build a default select list ...
3653 if (!selhash && !from_function) {
3654 jsonObject* default_list = defaultSelectList( core_class );
3655 if( ! default_list ) {
3657 osrfAppSessionStatus(
3659 OSRF_STATUS_INTERNALSERVERERROR,
3660 "osrfMethodException",
3662 "Unable to build default SELECT clause in JSON query"
3664 free( join_clause );
3669 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3670 jsonObjectSetKey( selhash, core_class, default_list );
3673 // The SELECT clause can be encoded only by a hash
3674 if( !from_function && selhash->type != JSON_HASH ) {
3677 "%s: Expected JSON_HASH for SELECT clause; found %s",
3679 json_type( selhash->type )
3683 osrfAppSessionStatus(
3685 OSRF_STATUS_INTERNALSERVERERROR,
3686 "osrfMethodException",
3688 "Malformed SELECT clause in JSON query"
3690 free( join_clause );
3694 // If you see a null or wild card specifier for the core class, or an
3695 // empty array, replace it with a default SELECT list
3696 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3698 int default_needed = 0; // boolean
3699 if( JSON_STRING == tmp_const->type
3700 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3702 else if( JSON_NULL == tmp_const->type )
3705 if( default_needed ) {
3706 // Build a default SELECT list
3707 jsonObject* default_list = defaultSelectList( core_class );
3708 if( ! default_list ) {
3710 osrfAppSessionStatus(
3712 OSRF_STATUS_INTERNALSERVERERROR,
3713 "osrfMethodException",
3715 "Can't build default SELECT clause in JSON query"
3717 free( join_clause );
3722 jsonObjectSetKey( selhash, core_class, default_list );
3726 // temp buffers for the SELECT list and GROUP BY clause
3727 growing_buffer* select_buf = buffer_init(128);
3728 growing_buffer* group_buf = buffer_init(128);
3730 int aggregate_found = 0; // boolean
3732 // Build a select list
3733 if(from_function) // From a function we select everything
3734 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3737 // Build the SELECT list as SQL
3741 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3742 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3744 const char* cname = selclass_itr->key;
3746 // Make sure the target relation is in the FROM clause.
3748 // At this point join_hash is a step down from the join_hash we
3749 // received as a parameter. If the original was a JSON_STRING,
3750 // then json_hash is now NULL. If the original was a JSON_HASH,
3751 // then json_hash is now the first (and only) entry in it,
3752 // denoting the core class. We've already excluded the
3753 // possibility that the original was a JSON_ARRAY, because in
3754 // that case from_function would be non-NULL, and we wouldn't
3757 // If the current table alias isn't in scope, bail out
3758 ClassInfo* class_info = search_alias( cname );
3759 if( ! class_info ) {
3762 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3767 osrfAppSessionStatus(
3769 OSRF_STATUS_INTERNALSERVERERROR,
3770 "osrfMethodException",
3772 "Selected class not in FROM clause in JSON query"
3774 jsonIteratorFree( selclass_itr );
3775 buffer_free( select_buf );
3776 buffer_free( group_buf );
3777 if( defaultselhash )
3778 jsonObjectFree( defaultselhash );
3779 free( join_clause );
3783 if( selclass->type != JSON_ARRAY ) {
3786 "%s: Malformed SELECT list for class \"%s\"; not an array",
3791 osrfAppSessionStatus(
3793 OSRF_STATUS_INTERNALSERVERERROR,
3794 "osrfMethodException",
3796 "Selected class not in FROM clause in JSON query"
3799 jsonIteratorFree( selclass_itr );
3800 buffer_free( select_buf );
3801 buffer_free( group_buf );
3802 if( defaultselhash )
3803 jsonObjectFree( defaultselhash );
3804 free( join_clause );
3808 // Look up some attributes of the current class
3809 osrfHash* idlClass = class_info->class_def;
3810 osrfHash* class_field_set = class_info->fields;
3811 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3812 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3814 if( 0 == selclass->size ) {
3817 "%s: No columns selected from \"%s\"",
3823 // stitch together the column list for the current table alias...
3824 unsigned long field_idx = 0;
3825 jsonObject* selfield = NULL;
3826 while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3828 // If we need a separator comma, add one
3832 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3835 // if the field specification is a string, add it to the list
3836 if (selfield->type == JSON_STRING) {
3838 // Look up the field in the IDL
3839 const char* col_name = jsonObjectGetString( selfield );
3840 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3842 // No such field in current class
3845 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3851 osrfAppSessionStatus(
3853 OSRF_STATUS_INTERNALSERVERERROR,
3854 "osrfMethodException",
3856 "Selected column not defined in JSON query"
3858 jsonIteratorFree( selclass_itr );
3859 buffer_free( select_buf );
3860 buffer_free( group_buf );
3861 if( defaultselhash )
3862 jsonObjectFree( defaultselhash );
3863 free( join_clause );
3865 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3866 // Virtual field not allowed
3869 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3875 osrfAppSessionStatus(
3877 OSRF_STATUS_INTERNALSERVERERROR,
3878 "osrfMethodException",
3880 "Selected column may not be virtual in JSON query"
3882 jsonIteratorFree( selclass_itr );
3883 buffer_free( select_buf );
3884 buffer_free( group_buf );
3885 if( defaultselhash )
3886 jsonObjectFree( defaultselhash );
3887 free( join_clause );
3893 if (flags & DISABLE_I18N)
3896 i18n = osrfHashGet(field_def, "i18n");
3898 if( str_is_true( i18n ) ) {
3899 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
3900 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3901 class_tname, cname, col_name, class_pkey,
3902 cname, class_pkey, locale, col_name );
3904 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"",
3905 cname, col_name, col_name );
3908 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"",
3909 cname, col_name, col_name );
3912 // ... but it could be an object, in which case we check for a Field Transform
3913 } else if (selfield->type == JSON_HASH) {
3915 const char* col_name = jsonObjectGetString(
3916 jsonObjectGetKeyConst( selfield, "column" ) );
3918 // Get the field definition from the IDL
3919 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3921 // No such field in current class
3924 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3930 osrfAppSessionStatus(
3932 OSRF_STATUS_INTERNALSERVERERROR,
3933 "osrfMethodException",
3935 "Selected column is not defined in JSON query"
3937 jsonIteratorFree( selclass_itr );
3938 buffer_free( select_buf );
3939 buffer_free( group_buf );
3940 if( defaultselhash )
3941 jsonObjectFree( defaultselhash );
3942 free( join_clause );
3944 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3945 // No such field in current class
3948 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3954 osrfAppSessionStatus(
3956 OSRF_STATUS_INTERNALSERVERERROR,
3957 "osrfMethodException",
3959 "Selected column is virtual in JSON query"
3961 jsonIteratorFree( selclass_itr );
3962 buffer_free( select_buf );
3963 buffer_free( group_buf );
3964 if( defaultselhash )
3965 jsonObjectFree( defaultselhash );
3966 free( join_clause );
3970 // Decide what to use as a column alias
3972 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3973 _alias = jsonObjectGetString( tmp_const );
3974 } else { // Use field name as the alias
3978 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3979 char* transform_str = searchFieldTransform(
3980 class_info->alias, field_def, selfield );
3981 if( transform_str ) {
3982 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3983 free(transform_str);
3986 osrfAppSessionStatus(
3988 OSRF_STATUS_INTERNALSERVERERROR,
3989 "osrfMethodException",
3991 "Unable to generate transform function in JSON query"
3993 jsonIteratorFree( selclass_itr );
3994 buffer_free( select_buf );
3995 buffer_free( group_buf );
3996 if( defaultselhash )
3997 jsonObjectFree( defaultselhash );
3998 free( join_clause );
4005 if (flags & DISABLE_I18N)
4008 i18n = osrfHashGet(field_def, "i18n");
4010 if( str_is_true( i18n ) ) {
4011 buffer_fadd( select_buf,
4012 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4013 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4014 class_tname, cname, col_name, class_pkey, cname,
4015 class_pkey, locale, _alias);
4017 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4018 cname, col_name, _alias );
4021 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4022 cname, col_name, _alias);
4029 "%s: Selected item is unexpected JSON type: %s",
4031 json_type( selfield->type )
4034 osrfAppSessionStatus(
4036 OSRF_STATUS_INTERNALSERVERERROR,
4037 "osrfMethodException",
4039 "Ill-formed SELECT item in JSON query"
4041 jsonIteratorFree( selclass_itr );
4042 buffer_free( select_buf );
4043 buffer_free( group_buf );
4044 if( defaultselhash )
4045 jsonObjectFree( defaultselhash );
4046 free( join_clause );
4050 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
4051 if( obj_is_true( agg_obj ) )
4052 aggregate_found = 1;
4054 // Append a comma (except for the first one)
4055 // and add the column to a GROUP BY clause
4059 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4061 buffer_fadd(group_buf, " %d", sel_pos);
4065 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4067 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4068 if ( ! obj_is_true( aggregate_obj ) ) {
4072 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4075 buffer_fadd(group_buf, " %d", sel_pos);
4078 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4082 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4085 _column = searchFieldTransform(class_info->alias, field, selfield);
4086 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4087 OSRF_BUFFER_ADD(group_buf, _column);
4088 _column = searchFieldTransform(class_info->alias, field, selfield);
4095 } // end while -- iterating across SELECT columns
4097 } // end while -- iterating across classes
4099 jsonIteratorFree(selclass_itr);
4103 char* col_list = buffer_release(select_buf);
4105 // Make sure the SELECT list isn't empty. This can happen, for example,
4106 // if we try to build a default SELECT clause from a non-core table.
4109 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
4111 osrfAppSessionStatus(
4113 OSRF_STATUS_INTERNALSERVERERROR,
4114 "osrfMethodException",
4116 "SELECT list is empty"
4119 buffer_free( group_buf );
4120 if( defaultselhash )
4121 jsonObjectFree( defaultselhash );
4122 free( join_clause );
4127 if (from_function) table = searchValueTransform(join_hash);
4128 else table = strdup( curr_query->core.source_def );
4132 osrfAppSessionStatus(
4134 OSRF_STATUS_INTERNALSERVERERROR,
4135 "osrfMethodException",
4137 "Unable to identify table for core class"
4140 buffer_free( group_buf );
4141 if( defaultselhash )
4142 jsonObjectFree( defaultselhash );
4143 free( join_clause );
4147 // Put it all together
4148 growing_buffer* sql_buf = buffer_init(128);
4149 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4153 // Append the join clause, if any
4155 buffer_add(sql_buf, join_clause);
4159 char* order_by_list = NULL;
4160 char* having_buf = NULL;
4162 if (!from_function) {
4164 // Build a WHERE clause, if there is one
4165 if ( search_hash ) {
4166 buffer_add(sql_buf, " WHERE ");
4168 // and it's on the WHERE clause
4169 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4172 osrfAppSessionStatus(
4174 OSRF_STATUS_INTERNALSERVERERROR,
4175 "osrfMethodException",
4177 "Severe query error in WHERE predicate -- see error log for more details"
4180 buffer_free(group_buf);
4181 buffer_free(sql_buf);
4183 jsonObjectFree(defaultselhash);
4187 buffer_add(sql_buf, pred);
4191 // Build a HAVING clause, if there is one
4192 if ( having_hash ) {
4194 // and it's on the the WHERE clause
4195 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4197 if( ! having_buf ) {
4199 osrfAppSessionStatus(
4201 OSRF_STATUS_INTERNALSERVERERROR,
4202 "osrfMethodException",
4204 "Severe query error in HAVING predicate -- see error log for more details"
4207 buffer_free(group_buf);
4208 buffer_free(sql_buf);
4210 jsonObjectFree(defaultselhash);
4215 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4217 // Build an ORDER BY clause, if there is one
4218 if( NULL == order_hash )
4219 ; // No ORDER BY? do nothing
4220 else if( JSON_ARRAY == order_hash->type ) {
4221 // Array of field specifications, each specification being a
4222 // hash to define the class, field, and other details
4224 jsonObject* order_spec;
4225 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4227 if( JSON_HASH != order_spec->type ) {
4228 osrfLogError(OSRF_LOG_MARK,
4229 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4230 MODULENAME, json_type( order_spec->type ) );
4232 osrfAppSessionStatus(
4234 OSRF_STATUS_INTERNALSERVERERROR,
4235 "osrfMethodException",
4237 "Malformed ORDER BY clause -- see error log for more details"
4239 buffer_free( order_buf );
4241 buffer_free(group_buf);
4242 buffer_free(sql_buf);
4244 jsonObjectFree(defaultselhash);
4248 const char* class_alias =
4249 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4251 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4254 OSRF_BUFFER_ADD(order_buf, ", ");
4256 order_buf = buffer_init(128);
4258 if( !field || !class_alias ) {
4259 osrfLogError( OSRF_LOG_MARK,
4260 "%s: Missing class or field name in field specification "
4261 "of ORDER BY clause",
4264 osrfAppSessionStatus(
4266 OSRF_STATUS_INTERNALSERVERERROR,
4267 "osrfMethodException",
4269 "Malformed ORDER BY clause -- see error log for more details"
4271 buffer_free( order_buf );
4273 buffer_free(group_buf);
4274 buffer_free(sql_buf);
4276 jsonObjectFree(defaultselhash);
4280 ClassInfo* order_class_info = search_alias( class_alias );
4281 if( ! order_class_info ) {
4282 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4283 "not in FROM clause", MODULENAME, class_alias );
4285 osrfAppSessionStatus(
4287 OSRF_STATUS_INTERNALSERVERERROR,
4288 "osrfMethodException",
4290 "Invalid class referenced in ORDER BY clause -- "
4291 "see error log for more details"
4294 buffer_free(group_buf);
4295 buffer_free(sql_buf);
4297 jsonObjectFree(defaultselhash);
4301 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4303 osrfLogError( OSRF_LOG_MARK,
4304 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4305 MODULENAME, class_alias, field );
4307 osrfAppSessionStatus(
4309 OSRF_STATUS_INTERNALSERVERERROR,
4310 "osrfMethodException",
4312 "Invalid field referenced in ORDER BY clause -- "
4313 "see error log for more details"
4316 buffer_free(group_buf);
4317 buffer_free(sql_buf);
4319 jsonObjectFree(defaultselhash);
4321 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4322 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4323 MODULENAME, field );
4325 osrfAppSessionStatus(
4327 OSRF_STATUS_INTERNALSERVERERROR,
4328 "osrfMethodException",
4330 "Virtual field in ORDER BY clause -- see error log for more details"
4332 buffer_free( order_buf );
4334 buffer_free(group_buf);
4335 buffer_free(sql_buf);
4337 jsonObjectFree(defaultselhash);
4341 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4342 char* transform_str = searchFieldTransform(
4343 class_alias, field_def, order_spec );
4344 if( ! transform_str ) {
4346 osrfAppSessionStatus(
4348 OSRF_STATUS_INTERNALSERVERERROR,
4349 "osrfMethodException",
4351 "Severe query error in ORDER BY clause -- "
4352 "see error log for more details"
4354 buffer_free( order_buf );
4356 buffer_free(group_buf);
4357 buffer_free(sql_buf);
4359 jsonObjectFree(defaultselhash);
4363 OSRF_BUFFER_ADD( order_buf, transform_str );
4364 free( transform_str );
4367 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4369 const char* direction =
4370 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4372 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4373 OSRF_BUFFER_ADD( order_buf, " DESC" );
4375 OSRF_BUFFER_ADD( order_buf, " ASC" );
4378 } else if( JSON_HASH == order_hash->type ) {
4379 // This hash is keyed on class alias. Each class has either
4380 // an array of field names or a hash keyed on field name.
4381 jsonIterator* class_itr = jsonNewIterator( order_hash );
4382 while ( (snode = jsonIteratorNext( class_itr )) ) {
4384 ClassInfo* order_class_info = search_alias( class_itr->key );
4385 if( ! order_class_info ) {
4386 osrfLogError(OSRF_LOG_MARK,
4387 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4388 MODULENAME, class_itr->key );
4390 osrfAppSessionStatus(
4392 OSRF_STATUS_INTERNALSERVERERROR,
4393 "osrfMethodException",
4395 "Invalid class referenced in ORDER BY clause -- "
4396 "see error log for more details"
4398 jsonIteratorFree( class_itr );
4399 buffer_free( order_buf );
4401 buffer_free(group_buf);
4402 buffer_free(sql_buf);
4404 jsonObjectFree(defaultselhash);
4408 osrfHash* field_list_def = order_class_info->fields;
4410 if ( snode->type == JSON_HASH ) {
4412 // Hash is keyed on field names from the current class. For each field
4413 // there is another layer of hash to define the sorting details, if any,
4414 // or a string to indicate direction of sorting.
4415 jsonIterator* order_itr = jsonNewIterator( snode );
4416 while ( (onode = jsonIteratorNext( order_itr )) ) {
4418 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4420 osrfLogError( OSRF_LOG_MARK,
4421 "%s: Invalid field \"%s\" in ORDER BY clause",
4422 MODULENAME, order_itr->key );
4424 osrfAppSessionStatus(
4426 OSRF_STATUS_INTERNALSERVERERROR,
4427 "osrfMethodException",
4429 "Invalid field in ORDER BY clause -- see error log for more details"
4431 jsonIteratorFree( order_itr );
4432 jsonIteratorFree( class_itr );
4433 buffer_free( order_buf );
4435 buffer_free(group_buf);
4436 buffer_free(sql_buf);
4438 jsonObjectFree(defaultselhash);
4440 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4441 osrfLogError( OSRF_LOG_MARK,
4442 "%s: Virtual field \"%s\" in ORDER BY clause",
4443 MODULENAME, order_itr->key );
4445 osrfAppSessionStatus(
4447 OSRF_STATUS_INTERNALSERVERERROR,
4448 "osrfMethodException",
4450 "Virtual field in ORDER BY clause -- "
4451 "see error log for more details"
4453 jsonIteratorFree( order_itr );
4454 jsonIteratorFree( class_itr );
4455 buffer_free( order_buf );
4457 buffer_free(group_buf);
4458 buffer_free(sql_buf);
4460 jsonObjectFree(defaultselhash);
4464 const char* direction = NULL;
4465 if ( onode->type == JSON_HASH ) {
4466 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4467 string = searchFieldTransform(
4469 osrfHashGet( field_list_def, order_itr->key ),
4473 if( ctx ) osrfAppSessionStatus(
4475 OSRF_STATUS_INTERNALSERVERERROR,
4476 "osrfMethodException",
4478 "Severe query error in ORDER BY clause -- "
4479 "see error log for more details"
4481 jsonIteratorFree( order_itr );
4482 jsonIteratorFree( class_itr );
4484 buffer_free(group_buf);
4485 buffer_free(order_buf);
4486 buffer_free(sql_buf);
4488 jsonObjectFree(defaultselhash);
4492 growing_buffer* field_buf = buffer_init(16);
4493 buffer_fadd( field_buf, "\"%s\".%s",
4494 class_itr->key, order_itr->key );
4495 string = buffer_release(field_buf);
4498 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4499 const char* dir = jsonObjectGetString(tmp_const);
4500 if (!strncasecmp(dir, "d", 1)) {
4501 direction = " DESC";
4507 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4508 osrfLogError( OSRF_LOG_MARK,
4509 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4510 MODULENAME, json_type( onode->type ) );
4512 osrfAppSessionStatus(
4514 OSRF_STATUS_INTERNALSERVERERROR,
4515 "osrfMethodException",
4517 "Malformed ORDER BY clause -- see error log for more details"
4519 jsonIteratorFree( order_itr );
4520 jsonIteratorFree( class_itr );
4522 buffer_free(group_buf);
4523 buffer_free(order_buf);
4524 buffer_free(sql_buf);
4526 jsonObjectFree(defaultselhash);
4530 string = strdup(order_itr->key);
4531 const char* dir = jsonObjectGetString(onode);
4532 if (!strncasecmp(dir, "d", 1)) {
4533 direction = " DESC";
4540 OSRF_BUFFER_ADD(order_buf, ", ");
4542 order_buf = buffer_init(128);
4544 OSRF_BUFFER_ADD(order_buf, string);
4548 OSRF_BUFFER_ADD(order_buf, direction);
4552 jsonIteratorFree(order_itr);
4554 } else if ( snode->type == JSON_ARRAY ) {
4556 // Array is a list of fields from the current class
4557 unsigned long order_idx = 0;
4558 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4560 const char* _f = jsonObjectGetString( onode );
4562 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4564 osrfLogError( OSRF_LOG_MARK,
4565 "%s: Invalid field \"%s\" in ORDER BY clause",
4568 osrfAppSessionStatus(
4570 OSRF_STATUS_INTERNALSERVERERROR,
4571 "osrfMethodException",
4573 "Invalid field in ORDER BY clause -- "
4574 "see error log for more details"
4576 jsonIteratorFree( class_itr );
4577 buffer_free( order_buf );
4579 buffer_free(group_buf);
4580 buffer_free(sql_buf);
4582 jsonObjectFree(defaultselhash);
4584 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4585 osrfLogError( OSRF_LOG_MARK,
4586 "%s: Virtual field \"%s\" in ORDER BY clause",
4589 osrfAppSessionStatus(
4591 OSRF_STATUS_INTERNALSERVERERROR,
4592 "osrfMethodException",
4594 "Virtual field in ORDER BY clause -- "
4595 "see error log for more details"
4597 jsonIteratorFree( class_itr );
4598 buffer_free( order_buf );
4600 buffer_free(group_buf);
4601 buffer_free(sql_buf);
4603 jsonObjectFree(defaultselhash);
4608 OSRF_BUFFER_ADD(order_buf, ", ");
4610 order_buf = buffer_init(128);
4612 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
4616 // IT'S THE OOOOOOOOOOOLD STYLE!
4618 osrfLogError(OSRF_LOG_MARK,
4619 "%s: Possible SQL injection attempt; direct order by is not allowed",
4622 osrfAppSessionStatus(
4624 OSRF_STATUS_INTERNALSERVERERROR,
4625 "osrfMethodException",
4627 "Severe query error -- see error log for more details"
4632 buffer_free(group_buf);
4633 buffer_free(order_buf);
4634 buffer_free(sql_buf);
4636 jsonObjectFree(defaultselhash);
4637 jsonIteratorFree(class_itr);
4641 jsonIteratorFree( class_itr );
4643 osrfLogError(OSRF_LOG_MARK,
4644 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4645 MODULENAME, json_type( order_hash->type ) );
4647 osrfAppSessionStatus(
4649 OSRF_STATUS_INTERNALSERVERERROR,
4650 "osrfMethodException",
4652 "Malformed ORDER BY clause -- see error log for more details"
4654 buffer_free( order_buf );
4656 buffer_free(group_buf);
4657 buffer_free(sql_buf);
4659 jsonObjectFree(defaultselhash);
4664 order_by_list = buffer_release( order_buf );
4668 string = buffer_release(group_buf);
4670 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4671 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4672 OSRF_BUFFER_ADD( sql_buf, string );
4677 if( having_buf && *having_buf ) {
4678 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4679 OSRF_BUFFER_ADD( sql_buf, having_buf );
4683 if( order_by_list ) {
4685 if ( *order_by_list ) {
4686 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4687 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4690 free( order_by_list );
4694 const char* str = jsonObjectGetString(limit);
4695 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
4699 const char* str = jsonObjectGetString(offset);
4700 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
4703 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4706 jsonObjectFree(defaultselhash);
4708 return buffer_release(sql_buf);
4710 } // end of SELECT()
4712 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4714 const char* locale = osrf_message_get_last_locale();
4716 osrfHash* fields = osrfHashGet(meta, "fields");
4717 char* core_class = osrfHashGet(meta, "classname");
4719 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4721 jsonObject* node = NULL;
4722 jsonObject* snode = NULL;
4723 jsonObject* onode = NULL;
4724 const jsonObject* _tmp = NULL;
4725 jsonObject* selhash = NULL;
4726 jsonObject* defaultselhash = NULL;
4728 growing_buffer* sql_buf = buffer_init(128);
4729 growing_buffer* select_buf = buffer_init(128);
4731 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4732 defaultselhash = jsonNewObjectType(JSON_HASH);
4733 selhash = defaultselhash;
4736 // If there's no SELECT list for the core class, build one
4737 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4738 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4740 // Add every non-virtual field to the field list
4741 osrfHash* field_def = NULL;
4742 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4743 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4744 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4745 const char* field = osrfHashIteratorKey( field_itr );
4746 jsonObjectPush( field_list, jsonNewObject( field ) );
4749 osrfHashIteratorFree( field_itr );
4750 jsonObjectSetKey( selhash, core_class, field_list );
4754 jsonIterator* class_itr = jsonNewIterator( selhash );
4755 while ( (snode = jsonIteratorNext( class_itr )) ) {
4757 const char* cname = class_itr->key;
4758 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4762 if (strcmp(core_class,class_itr->key)) {
4766 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4768 jsonObjectFree(found);
4772 jsonObjectFree(found);
4775 jsonIterator* select_itr = jsonNewIterator( snode );
4776 while ( (node = jsonIteratorNext( select_itr )) ) {
4777 const char* item_str = jsonObjectGetString( node );
4778 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4779 char* fname = osrfHashGet(field, "name");
4787 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
4792 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4793 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4796 i18n = osrfHashGet(field, "i18n");
4798 if( str_is_true( i18n ) ) {
4799 char* pkey = osrfHashGet(idlClass, "primarykey");
4800 char* tname = osrfHashGet(idlClass, "tablename");
4802 buffer_fadd(select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4803 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4804 tname, cname, fname, pkey, cname, pkey, locale, fname);
4806 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4809 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4813 jsonIteratorFree(select_itr);
4816 jsonIteratorFree(class_itr);
4818 char* col_list = buffer_release(select_buf);
4819 char* table = getRelation(meta);
4821 table = strdup( "(null)" );
4823 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4827 // Clear the query stack (as a fail-safe precaution against possible
4828 // leftover garbage); then push the first query frame onto the stack.
4829 clear_query_stack();
4831 if( add_query_core( NULL, core_class ) ) {
4833 osrfAppSessionStatus(
4835 OSRF_STATUS_INTERNALSERVERERROR,
4836 "osrfMethodException",
4838 "Unable to build query frame for core class"
4844 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4845 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
4846 OSRF_BUFFER_ADD(sql_buf, join_clause);
4850 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4851 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4853 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4855 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4857 osrfAppSessionStatus(
4859 OSRF_STATUS_INTERNALSERVERERROR,
4860 "osrfMethodException",
4862 "Severe query error -- see error log for more details"
4864 buffer_free(sql_buf);
4866 jsonObjectFree(defaultselhash);
4867 clear_query_stack();
4870 buffer_add(sql_buf, pred);
4875 char* string = NULL;
4876 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4878 growing_buffer* order_buf = buffer_init(128);
4881 jsonIterator* class_itr = jsonNewIterator( _tmp );
4882 while ( (snode = jsonIteratorNext( class_itr )) ) {
4884 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4887 if ( snode->type == JSON_HASH ) {
4889 jsonIterator* order_itr = jsonNewIterator( snode );
4890 while ( (onode = jsonIteratorNext( order_itr )) ) {
4892 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4893 class_itr->key, order_itr->key );
4897 char* direction = NULL;
4898 if ( onode->type == JSON_HASH ) {
4899 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4900 string = searchFieldTransform( class_itr->key, field_def, onode );
4902 osrfAppSessionStatus(
4904 OSRF_STATUS_INTERNALSERVERERROR,
4905 "osrfMethodException",
4907 "Severe query error in ORDER BY clause -- "
4908 "see error log for more details"
4910 jsonIteratorFree( order_itr );
4911 jsonIteratorFree( class_itr );
4912 buffer_free( order_buf );
4913 buffer_free( sql_buf );
4914 if( defaultselhash )
4915 jsonObjectFree( defaultselhash );
4916 clear_query_stack();
4920 growing_buffer* field_buf = buffer_init(16);
4921 buffer_fadd( field_buf, "\"%s\".%s",
4922 class_itr->key, order_itr->key );
4923 string = buffer_release(field_buf);
4926 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4927 const char* dir = jsonObjectGetString(_tmp);
4928 if (!strncasecmp(dir, "d", 1)) {
4929 direction = " DESC";
4935 string = strdup(order_itr->key);
4936 const char* dir = jsonObjectGetString(onode);
4937 if (!strncasecmp(dir, "d", 1)) {
4938 direction = " DESC";
4947 buffer_add(order_buf, ", ");
4950 buffer_add(order_buf, string);
4954 buffer_add(order_buf, direction);
4958 jsonIteratorFree(order_itr);
4961 const char* str = jsonObjectGetString(snode);
4962 buffer_add(order_buf, str);
4968 jsonIteratorFree(class_itr);
4970 string = buffer_release(order_buf);
4973 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4974 OSRF_BUFFER_ADD( sql_buf, string );
4980 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4981 const char* str = jsonObjectGetString(_tmp);
4989 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4991 const char* str = jsonObjectGetString(_tmp);
5001 jsonObjectFree(defaultselhash);
5002 clear_query_stack();
5004 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
5005 return buffer_release(sql_buf);
5008 int doJSONSearch ( osrfMethodContext* ctx ) {
5009 if(osrfMethodVerifyContext( ctx )) {
5010 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5014 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
5019 dbhandle = writehandle;
5021 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
5025 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
5026 flags |= SELECT_DISTINCT;
5028 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
5029 flags |= DISABLE_I18N;
5031 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
5032 clear_query_stack(); // a possibly needless precaution
5033 char* sql = buildQuery( ctx, hash, flags );
5034 clear_query_stack();
5041 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
5042 dbi_result result = dbi_conn_query(dbhandle, sql);
5045 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
5047 if (dbi_result_first_row(result)) {
5048 /* JSONify the result */
5049 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
5052 jsonObject* return_val = oilsMakeJSONFromResult( result );
5053 osrfAppRespond( ctx, return_val );
5054 jsonObjectFree( return_val );
5055 } while (dbi_result_next_row(result));
5058 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
5061 osrfAppRespondComplete( ctx, NULL );
5063 /* clean up the query */
5064 dbi_result_free(result);
5068 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
5069 osrfAppSessionStatus(
5071 OSRF_STATUS_INTERNALSERVERERROR,
5072 "osrfMethodException",
5074 "Severe query error -- see error log for more details"
5082 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
5083 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5086 dbhandle = writehandle;
5088 char* core_class = osrfHashGet( class_meta, "classname" );
5089 char* pkey = osrfHashGet( class_meta, "primarykey" );
5091 const jsonObject* _tmp;
5093 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5095 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
5100 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
5102 dbi_result result = dbi_conn_query(dbhandle, sql);
5103 if( NULL == result ) {
5104 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
5105 MODULENAME, osrfHashGet( class_meta, "fieldmapper" ), sql);
5106 osrfAppSessionStatus(
5108 OSRF_STATUS_INTERNALSERVERERROR,
5109 "osrfMethodException",
5111 "Severe query error -- see error log for more details"
5118 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
5121 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
5122 jsonObject* row_obj = NULL;
5124 if (dbi_result_first_row(result)) {
5126 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5127 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5128 // eliminate the duplicates.
5129 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
5130 osrfHash* dedup = osrfNewHash();
5132 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5133 char* pkey_val = oilsFMGetString( row_obj, pkey );
5134 if ( osrfHashGet( dedup, pkey_val ) ) {
5135 jsonObjectFree( row_obj );
5138 osrfHashSet( dedup, pkey_val, pkey_val );
5139 jsonObjectPush( res_list, row_obj );
5141 } while (dbi_result_next_row(result));
5142 osrfHashFree(dedup);
5145 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
5149 /* clean up the query */
5150 dbi_result_free(result);
5153 // If we're asked to flesh, and there's anything to flesh, then flesh.
5154 if (res_list->size && query_hash) {
5155 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5157 // Get the flesh depth
5158 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5159 if ( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5160 flesh_depth = max_flesh_depth;
5162 // We need a non-zero flesh depth, and a list of fields to flesh
5163 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5164 if ( temp_blob && flesh_depth > 0 ) {
5166 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5167 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5169 osrfStringArray* link_fields = NULL;
5170 osrfHash* links = osrfHashGet( class_meta, "links" );
5172 // Make an osrfStringArray of the names of fields to be fleshed
5174 if (flesh_fields->size == 1) {
5175 const char* _t = jsonObjectGetString(
5176 jsonObjectGetIndex( flesh_fields, 0 ) );
5177 if (!strcmp(_t,"*"))
5178 link_fields = osrfHashKeys( links );
5183 link_fields = osrfNewStringArray(1);
5184 jsonIterator* _i = jsonNewIterator( flesh_fields );
5185 while ((_f = jsonIteratorNext( _i ))) {
5186 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5188 jsonIteratorFree(_i);
5192 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5194 // Iterate over the JSON_ARRAY of rows
5196 unsigned long res_idx = 0;
5197 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5200 const char* link_field;
5202 // Iterate over the list of fleshable fields
5203 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5205 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
5207 osrfHash* kid_link = osrfHashGet(links, link_field);
5209 continue; // Not a link field; skip it
5211 osrfHash* field = osrfHashGet(fields, link_field);
5213 continue; // Not a field at all; skip it (IDL is ill-formed)
5215 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
5217 continue; // The class it links to doesn't exist; skip it
5219 const char* reltype = osrfHashGet( kid_link, "reltype" );
5221 continue; // No reltype; skip it (IDL is ill-formed)
5223 osrfHash* value_field = field;
5225 if ( !strcmp( reltype, "has_many" )
5226 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5227 value_field = osrfHashGet(
5228 fields, osrfHashGet( class_meta, "primarykey" ) );
5231 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5233 if (link_map->size > 0) {
5234 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
5237 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5242 osrfHashGet(kid_link, "class"),
5249 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5250 osrfHashGet(kid_link, "field"),
5251 osrfHashGet(kid_link, "class"),
5252 osrfHashGet(kid_link, "key"),
5253 osrfHashGet(kid_link, "reltype")
5256 const char* search_key = jsonObjectGetString(
5257 jsonObjectGetIndex( cur,
5258 atoi( osrfHashGet(value_field, "array_position") )
5263 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
5267 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
5269 // construct WHERE clause
5270 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
5273 osrfHashGet(kid_link, "key"),
5274 jsonNewObject( search_key )
5277 // construct the rest of the query, mostly
5278 // by copying pieces of the previous level of query
5279 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
5280 jsonObjectSetKey( rest_of_query, "flesh",
5281 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5285 jsonObjectSetKey( rest_of_query, "flesh_fields",
5286 jsonObjectClone(flesh_blob) );
5288 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
5289 jsonObjectSetKey( rest_of_query, "order_by",
5290 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
5294 if (jsonObjectGetKeyConst(query_hash, "select")) {
5295 jsonObjectSetKey( rest_of_query, "select",
5296 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
5300 // do the query, recursively, to expand the fleshable field
5301 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5302 where_clause, rest_of_query, err);
5304 jsonObjectFree( where_clause );
5305 jsonObjectFree( rest_of_query );
5308 osrfStringArrayFree(link_fields);
5309 jsonObjectFree(res_list);
5310 jsonObjectFree(flesh_blob);
5314 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects",
5315 osrfHashGet(kid_link, "class"), kids->size);
5317 // Traverse the result set
5318 jsonObject* X = NULL;
5319 if ( link_map->size > 0 && kids->size > 0 ) {
5321 kids = jsonNewObjectType(JSON_ARRAY);
5323 jsonObject* _k_node;
5324 unsigned long res_idx = 0;
5325 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5331 (unsigned long)atoi(
5337 osrfHashGet(kid_link, "class")
5341 osrfStringArrayGetString( link_map, 0 )
5349 } // end while loop traversing X
5352 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" ))
5353 || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
5354 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5355 osrfHashGet(kid_link, "field"));
5358 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5359 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
5363 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) {
5365 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5366 osrfHashGet( kid_link, "field" ) );
5369 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5370 jsonObjectClone( kids )
5375 jsonObjectFree(kids);
5379 jsonObjectFree( kids );
5381 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5382 osrfHashGet( kid_link, "field" ) );
5383 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
5385 } // end while loop traversing list of fleshable fields
5386 } // end while loop traversing res_list
5387 jsonObjectFree( flesh_blob );
5388 osrfStringArrayFree(link_fields);
5397 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
5399 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5401 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
5403 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
5406 if (!verifyObjectClass(ctx, target)) {
5411 if( getXactId( ctx ) == NULL ) {
5412 osrfAppSessionStatus(
5414 OSRF_STATUS_BADREQUEST,
5415 "osrfMethodException",
5417 "No active transaction -- required for UPDATE"
5423 // The following test is harmless but redundant. If a class is
5424 // readonly, we don't register an update method for it.
5425 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5426 osrfAppSessionStatus(
5428 OSRF_STATUS_BADREQUEST,
5429 "osrfMethodException",
5431 "Cannot UPDATE readonly class"
5437 dbhandle = writehandle;
5438 const char* trans_id = getXactId( ctx );
5440 // Set the last_xact_id
5441 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5443 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5444 trans_id, target->classname, index);
5445 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
5448 char* pkey = osrfHashGet(meta, "primarykey");
5449 osrfHash* fields = osrfHashGet(meta, "fields");
5451 char* id = oilsFMGetString( target, pkey );
5455 "%s updating %s object with %s = %s",
5457 osrfHashGet(meta, "fieldmapper"),
5462 growing_buffer* sql = buffer_init(128);
5463 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
5466 osrfHash* field_def = NULL;
5467 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5468 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
5470 // Skip virtual fields, and the primary key
5471 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5474 const char* field_name = osrfHashIteratorKey( field_itr );
5475 if( ! strcmp( field_name, pkey ) )
5478 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5480 int value_is_numeric = 0; // boolean
5482 if (field_object && field_object->classname) {
5483 value = oilsFMGetString(
5485 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
5487 } else if( field_object && JSON_BOOL == field_object->type ) {
5488 if( jsonBoolIsTrue( field_object ) )
5489 value = strdup( "t" );
5491 value = strdup( "f" );
5493 value = jsonObjectToSimpleString( field_object );
5494 if( field_object && JSON_NUMBER == field_object->type )
5495 value_is_numeric = 1;
5498 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5499 osrfHashGet(meta, "fieldmapper"), field_name, value);
5501 if (!field_object || field_object->type == JSON_NULL) {
5502 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
5503 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5504 if (first) first = 0;
5505 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5506 buffer_fadd( sql, " %s = NULL", field_name );
5509 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5510 if (first) first = 0;
5511 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5513 const char* numtype = get_datatype( field_def );
5514 if ( !strncmp( numtype, "INT", 3 ) ) {
5515 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
5516 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
5517 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
5519 // Must really be intended as a string, so quote it
5520 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5521 buffer_fadd( sql, " %s = %s", field_name, value );
5523 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5525 osrfAppSessionStatus(
5527 OSRF_STATUS_INTERNALSERVERERROR,
5528 "osrfMethodException",
5530 "Error quoting string -- please see the error log for more details"
5534 osrfHashIteratorFree( field_itr );
5541 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5544 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5545 if (first) first = 0;
5546 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5547 buffer_fadd( sql, " %s = %s", field_name, value );
5550 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5551 osrfAppSessionStatus(
5553 OSRF_STATUS_INTERNALSERVERERROR,
5554 "osrfMethodException",
5556 "Error quoting string -- please see the error log for more details"
5560 osrfHashIteratorFree( field_itr );
5571 osrfHashIteratorFree( field_itr );
5573 jsonObject* obj = jsonNewObject(id);
5575 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5576 dbi_conn_quote_string(dbhandle, &id);
5578 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5580 char* query = buffer_release(sql);
5581 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
5583 dbi_result result = dbi_conn_query(dbhandle, query);
5587 jsonObjectFree(obj);
5588 obj = jsonNewObject(NULL);
5591 "%s ERROR updating %s object with %s = %s",
5593 osrfHashGet(meta, "fieldmapper"),
5604 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
5606 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5608 if( getXactId( ctx ) == NULL ) {
5609 osrfAppSessionStatus(
5611 OSRF_STATUS_BADREQUEST,
5612 "osrfMethodException",
5614 "No active transaction -- required for DELETE"
5620 // The following test is harmless but redundant. If a class is
5621 // readonly, we don't register a delete method for it.
5622 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5623 osrfAppSessionStatus(
5625 OSRF_STATUS_BADREQUEST,
5626 "osrfMethodException",
5628 "Cannot DELETE readonly class"
5634 dbhandle = writehandle;
5638 char* pkey = osrfHashGet(meta, "primarykey");
5646 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
5647 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5652 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5655 if (!verifyObjectPCRUD( ctx, NULL )) {
5660 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
5665 "%s deleting %s object with %s = %s",
5667 osrfHashGet(meta, "fieldmapper"),
5672 obj = jsonNewObject(id);
5674 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5675 dbi_conn_quote_string(writehandle, &id);
5677 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
5680 jsonObjectFree(obj);
5681 obj = jsonNewObject(NULL);
5684 "%s ERROR deleting %s object with %s = %s",
5686 osrfHashGet(meta, "fieldmapper"),
5698 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5699 @param result An iterator for a result set; we only look at the current row.
5700 @param @meta Pointer to the class metadata for the core class.
5701 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5703 If a column is not defined in the IDL, or if it has no array_position defined for it in
5704 the IDL, or if it is defined as virtual, ignore it.
5706 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5707 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5708 array_position in the IDL.
5710 A field defined in the IDL but not represented in the returned row will leave a hole
5711 in the JSON_ARRAY. In effect it will be treated as a null value.
5713 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5714 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5715 classname corresponding to the @a meta argument.
5717 The calling code is responsible for freeing the the resulting jsonObject by calling
5720 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5721 if(!(result && meta)) return jsonNULL;
5723 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5724 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
5725 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
5727 osrfHash* fields = osrfHashGet(meta, "fields");
5729 int columnIndex = 1;
5730 const char* columnName;
5732 /* cycle through the columns in the row returned from the database */
5733 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5735 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5737 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5739 /* determine the field type and storage attributes */
5740 unsigned short type = dbi_result_get_field_type_idx(result, columnIndex);
5741 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5743 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5744 // or if it has no sequence number there, or if it's virtual, skip it.
5745 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
5748 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
5749 continue; // skip this column: IDL says it's virtual
5751 const char* pos = (char*)osrfHashGet(_f, "array_position");
5752 if ( !pos ) // IDL has no sequence number for it. This shouldn't happen,
5753 continue; // since we assign sequence numbers dynamically as we load the IDL.
5755 fmIndex = atoi( pos );
5756 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
5758 continue; // This field is not defined in the IDL
5761 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
5762 // sequence number from the IDL (which is likely to be different from the sequence
5763 // of columns in the SELECT clause).
5764 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5765 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
5770 case DBI_TYPE_INTEGER :
5772 if( attr & DBI_INTEGER_SIZE8 )
5773 jsonObjectSetIndex( object, fmIndex,
5774 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
5776 jsonObjectSetIndex( object, fmIndex,
5777 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
5781 case DBI_TYPE_DECIMAL :
5782 jsonObjectSetIndex( object, fmIndex,
5783 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
5786 case DBI_TYPE_STRING :
5791 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
5796 case DBI_TYPE_DATETIME : {
5798 char dt_string[256] = "";
5801 // Fetch the date column as a time_t
5802 time_t _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5804 // Translate the time_t to a human-readable string
5805 if (!(attr & DBI_DATETIME_DATE)) {
5806 gmtime_r( &_tmp_dt, &gmdt );
5807 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5808 } else if (!(attr & DBI_DATETIME_TIME)) {
5809 localtime_r( &_tmp_dt, &gmdt );
5810 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5812 localtime_r( &_tmp_dt, &gmdt );
5813 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5816 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
5820 case DBI_TYPE_BINARY :
5821 osrfLogError( OSRF_LOG_MARK,
5822 "Can't do binary at column %s : index %d", columnName, columnIndex);
5831 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5832 if(!result) return jsonNULL;
5834 jsonObject* object = jsonNewObject(NULL);
5837 char dt_string[256];
5841 int columnIndex = 1;
5843 unsigned short type;
5844 const char* columnName;
5846 /* cycle through the column list */
5847 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5849 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5851 fmIndex = -1; // reset the position
5853 /* determine the field type and storage attributes */
5854 type = dbi_result_get_field_type_idx(result, columnIndex);
5855 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5857 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5858 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
5863 case DBI_TYPE_INTEGER :
5865 if( attr & DBI_INTEGER_SIZE8 )
5866 jsonObjectSetKey( object, columnName,
5867 jsonNewNumberObject(dbi_result_get_longlong_idx(
5868 result, columnIndex)) );
5870 jsonObjectSetKey( object, columnName,
5871 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
5874 case DBI_TYPE_DECIMAL :
5875 jsonObjectSetKey( object, columnName,
5876 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
5879 case DBI_TYPE_STRING :
5880 jsonObjectSetKey( object, columnName,
5881 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
5884 case DBI_TYPE_DATETIME :
5886 memset(dt_string, '\0', sizeof(dt_string));
5887 memset(&gmdt, '\0', sizeof(gmdt));
5889 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5892 if (!(attr & DBI_DATETIME_DATE)) {
5893 gmtime_r( &_tmp_dt, &gmdt );
5894 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5895 } else if (!(attr & DBI_DATETIME_TIME)) {
5896 localtime_r( &_tmp_dt, &gmdt );
5897 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5899 localtime_r( &_tmp_dt, &gmdt );
5900 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5903 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5906 case DBI_TYPE_BINARY :
5907 osrfLogError( OSRF_LOG_MARK,
5908 "Can't do binary at column %s : index %d", columnName, columnIndex );
5912 } // end while loop traversing result
5917 // Interpret a string as true or false
5918 static int str_is_true( const char* str ) {
5919 if( NULL == str || strcasecmp( str, "true" ) )
5925 // Interpret a jsonObject as true or false
5926 static int obj_is_true( const jsonObject* obj ) {
5929 else switch( obj->type )
5937 if( strcasecmp( obj->value.s, "true" ) )
5941 case JSON_NUMBER : // Support 1/0 for perl's sake
5942 if( jsonObjectGetNumber( obj ) == 1.0 )
5951 // Translate a numeric code into a text string identifying a type of
5952 // jsonObject. To be used for building error messages.
5953 static const char* json_type( int code ) {
5959 return "JSON_ARRAY";
5961 return "JSON_STRING";
5963 return "JSON_NUMBER";
5969 return "(unrecognized)";
5973 // Extract the "primitive" attribute from an IDL field definition.
5974 // If we haven't initialized the app, then we must be running in
5975 // some kind of testbed. In that case, default to "string".
5976 static const char* get_primitive( osrfHash* field ) {
5977 const char* s = osrfHashGet( field, "primitive" );
5979 if( child_initialized )
5982 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5984 osrfHashGet( field, "name" )
5992 // Extract the "datatype" attribute from an IDL field definition.
5993 // If we haven't initialized the app, then we must be running in
5994 // some kind of testbed. In that case, default to to NUMERIC,
5995 // since we look at the datatype only for numbers.
5996 static const char* get_datatype( osrfHash* field ) {
5997 const char* s = osrfHashGet( field, "datatype" );
5999 if( child_initialized )
6002 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6004 osrfHashGet( field, "name" )
6013 If the input string is potentially a valid SQL identifier, return 1.
6016 Purpose: to prevent certain kinds of SQL injection. To that end we
6017 don't necessarily need to follow all the rules exactly, such as requiring
6018 that the first character not be a digit.
6020 We allow leading and trailing white space. In between, we do not allow
6021 punctuation (except for underscores and dollar signs), control
6022 characters, or embedded white space.
6024 More pedantically we should allow quoted identifiers containing arbitrary
6025 characters, but for the foreseeable future such quoted identifiers are not
6026 likely to be an issue.
6028 static int is_identifier( const char* s) {
6032 // Skip leading white space
6033 while( isspace( (unsigned char) *s ) )
6037 return 0; // Nothing but white space? Not okay.
6039 // Check each character until we reach white space or
6040 // end-of-string. Letters, digits, underscores, and
6041 // dollar signs are okay. With the exception of periods
6042 // (as in schema.identifier), control characters and other
6043 // punctuation characters are not okay. Anything else
6044 // is okay -- it could for example be part of a multibyte
6045 // UTF8 character such as a letter with diacritical marks,
6046 // and those are allowed.
6048 if( isalnum( (unsigned char) *s )
6052 ; // Fine; keep going
6053 else if( ispunct( (unsigned char) *s )
6054 || iscntrl( (unsigned char) *s ) )
6057 } while( *s && ! isspace( (unsigned char) *s ) );
6059 // If we found any white space in the above loop,
6060 // the rest had better be all white space.
6062 while( isspace( (unsigned char) *s ) )
6066 return 0; // White space was embedded within non-white space
6072 Determine whether to accept a character string as a comparison operator.
6073 Return 1 if it's good, or 0 if it's bad.
6075 We don't validate it for real. We just make sure that it doesn't contain
6076 any semicolons or white space (with special exceptions for a few specific
6077 operators). The idea is to block certain kinds of SQL injection. If it
6078 has no semicolons or white space but it's still not a valid operator, then
6079 the database will complain.
6081 Another approach would be to compare the string against a short list of
6082 approved operators. We don't do that because we want to allow custom
6083 operators like ">100*", which would be difficult or impossible to
6084 express otherwise in a JSON query.
6086 static int is_good_operator( const char* op ) {
6087 if( !op ) return 0; // Sanity check
6091 if( isspace( (unsigned char) *s ) ) {
6092 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6093 // and IS NOT DISTINCT FROM.
6094 if( !strcasecmp( op, "similar to" ) )
6096 else if( !strcasecmp( op, "is distinct from" ) )
6098 else if( !strcasecmp( op, "is not distinct from" ) )
6103 else if( ';' == *s )
6111 @name Query Frame Management
6113 The following machinery supports a stack of query frames for use by SELECT().
6115 A query frame caches information about one level of a SELECT query. When we enter
6116 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6118 The query frame stores information about the core class, and about any joined classes
6121 The main purpose is to map table aliases to classes and tables, so that a query can
6122 join to the same table more than once. A secondary goal is to reduce the number of
6123 lookups in the IDL by caching the results.
6127 #define STATIC_CLASS_INFO_COUNT 3
6129 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6132 @brief Allocate a ClassInfo as raw memory.
6133 @return Pointer to the newly allocated ClassInfo.
6135 Except for the in_use flag, which is used only by the allocation and deallocation
6136 logic, we don't initialize the ClassInfo here.
6138 static ClassInfo* allocate_class_info( void ) {
6139 // In order to reduce the number of mallocs and frees, we return a static
6140 // instance of ClassInfo, if we can find one that we're not already using.
6141 // We rely on the fact that the compiler will implicitly initialize the
6142 // static instances so that in_use == 0.
6145 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6146 if( ! static_class_info[ i ].in_use ) {
6147 static_class_info[ i ].in_use = 1;
6148 return static_class_info + i;
6152 // The static ones are all in use. Malloc one.
6154 return safe_malloc( sizeof( ClassInfo ) );
6158 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6159 @param info Pointer to the ClassInfo to be cleared.
6161 static void clear_class_info( ClassInfo* info ) {
6166 // Free any malloc'd strings
6168 if( info->alias != info->alias_store )
6169 free( info->alias );
6171 if( info->class_name != info->class_name_store )
6172 free( info->class_name );
6174 free( info->source_def );
6176 info->alias = info->class_name = info->source_def = NULL;
6181 @brief Free a ClassInfo and everything it owns.
6182 @param info Pointer to the ClassInfo to be freed.
6184 static void free_class_info( ClassInfo* info ) {
6189 clear_class_info( info );
6191 // If it's one of the static instances, just mark it as not in use
6194 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6195 if( info == static_class_info + i ) {
6196 static_class_info[ i ].in_use = 0;
6201 // Otherwise it must have been malloc'd, so free it
6207 @brief Populate an already-allocated ClassInfo.
6208 @param info Pointer to the ClassInfo to be populated.
6209 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6211 @param class Name of the class.
6212 @return Zero if successful, or 1 if not.
6214 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6215 the relevant portions of the IDL for the specified class.
6217 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6220 osrfLogError( OSRF_LOG_MARK,
6221 "%s ERROR: No ClassInfo available to populate", MODULENAME );
6222 info->alias = info->class_name = info->source_def = NULL;
6223 info->class_def = info->fields = info->links = NULL;
6228 osrfLogError( OSRF_LOG_MARK,
6229 "%s ERROR: No class name provided for lookup", MODULENAME );
6230 info->alias = info->class_name = info->source_def = NULL;
6231 info->class_def = info->fields = info->links = NULL;
6235 // Alias defaults to class name if not supplied
6236 if( ! alias || ! alias[ 0 ] )
6239 // Look up class info in the IDL
6240 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6242 osrfLogError( OSRF_LOG_MARK,
6243 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
6244 info->alias = info->class_name = info->source_def = NULL;
6245 info->class_def = info->fields = info->links = NULL;
6247 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6248 osrfLogError( OSRF_LOG_MARK,
6249 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
6250 info->alias = info->class_name = info->source_def = NULL;
6251 info->class_def = info->fields = info->links = NULL;
6255 osrfHash* links = osrfHashGet( class_def, "links" );
6257 osrfLogError( OSRF_LOG_MARK,
6258 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
6259 info->alias = info->class_name = info->source_def = NULL;
6260 info->class_def = info->fields = info->links = NULL;
6264 osrfHash* fields = osrfHashGet( class_def, "fields" );
6266 osrfLogError( OSRF_LOG_MARK,
6267 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
6268 info->alias = info->class_name = info->source_def = NULL;
6269 info->class_def = info->fields = info->links = NULL;
6273 char* source_def = getRelation( class_def );
6277 // We got everything we need, so populate the ClassInfo
6278 if( strlen( alias ) > ALIAS_STORE_SIZE )
6279 info->alias = strdup( alias );
6281 strcpy( info->alias_store, alias );
6282 info->alias = info->alias_store;
6285 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6286 info->class_name = strdup( class );
6288 strcpy( info->class_name_store, class );
6289 info->class_name = info->class_name_store;
6292 info->source_def = source_def;
6294 info->class_def = class_def;
6295 info->links = links;
6296 info->fields = fields;
6301 #define STATIC_FRAME_COUNT 3
6303 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6306 @brief Allocate a QueryFrame as raw memory.
6307 @return Pointer to the newly allocated QueryFrame.
6309 Except for the in_use flag, which is used only by the allocation and deallocation
6310 logic, we don't initialize the QueryFrame here.
6312 static QueryFrame* allocate_frame( void ) {
6313 // In order to reduce the number of mallocs and frees, we return a static
6314 // instance of QueryFrame, if we can find one that we're not already using.
6315 // We rely on the fact that the compiler will implicitly initialize the
6316 // static instances so that in_use == 0.
6319 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6320 if( ! static_frame[ i ].in_use ) {
6321 static_frame[ i ].in_use = 1;
6322 return static_frame + i;
6326 // The static ones are all in use. Malloc one.
6328 return safe_malloc( sizeof( QueryFrame ) );
6332 @brief Free a QueryFrame, and all the memory it owns.
6333 @param frame Pointer to the QueryFrame to be freed.
6335 static void free_query_frame( QueryFrame* frame ) {
6340 clear_class_info( &frame->core );
6342 // Free the join list
6344 ClassInfo* info = frame->join_list;
6347 free_class_info( info );
6351 frame->join_list = NULL;
6354 // If the frame is a static instance, just mark it as unused
6356 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6357 if( frame == static_frame + i ) {
6358 static_frame[ i ].in_use = 0;
6363 // Otherwise it must have been malloc'd, so free it
6369 @brief Search a given QueryFrame for a specified alias.
6370 @param frame Pointer to the QueryFrame to be searched.
6371 @param target The alias for which to search.
6372 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6374 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6375 if( ! frame || ! target ) {
6379 ClassInfo* found_class = NULL;
6381 if( !strcmp( target, frame->core.alias ) )
6382 return &(frame->core);
6384 ClassInfo* curr_class = frame->join_list;
6385 while( curr_class ) {
6386 if( strcmp( target, curr_class->alias ) )
6387 curr_class = curr_class->next;
6389 found_class = curr_class;
6399 @brief Push a new (blank) QueryFrame onto the stack.
6401 static void push_query_frame( void ) {
6402 QueryFrame* frame = allocate_frame();
6403 frame->join_list = NULL;
6404 frame->next = curr_query;
6406 // Initialize the ClassInfo for the core class
6407 ClassInfo* core = &frame->core;
6408 core->alias = core->class_name = core->source_def = NULL;
6409 core->class_def = core->fields = core->links = NULL;
6415 @brief Pop a QueryFrame off the stack and destroy it.
6417 static void pop_query_frame( void ) {
6422 QueryFrame* popped = curr_query;
6423 curr_query = popped->next;
6425 free_query_frame( popped );
6429 @brief Populate the ClassInfo for the core class.
6430 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6431 class name as an alias.
6432 @param class_name Name of the core class.
6433 @return Zero if successful, or 1 if not.
6435 Populate the ClassInfo of the core class with copies of the alias and class name, and
6436 with pointers to the relevant portions of the IDL for the core class.
6438 static int add_query_core( const char* alias, const char* class_name ) {
6441 if( ! curr_query ) {
6442 osrfLogError( OSRF_LOG_MARK,
6443 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
6445 } else if( curr_query->core.alias ) {
6446 osrfLogError( OSRF_LOG_MARK,
6447 "%s ERROR: Core class %s already populated as %s",
6448 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
6452 build_class_info( &curr_query->core, alias, class_name );
6453 if( curr_query->core.alias )
6456 osrfLogError( OSRF_LOG_MARK,
6457 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
6463 @brief Search the current QueryFrame for a specified alias.
6464 @param target The alias for which to search.
6465 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6467 static inline ClassInfo* search_alias( const char* target ) {
6468 return search_alias_in_frame( curr_query, target );
6472 @brief Search all levels of query for a specified alias, starting with the current query.
6473 @param target The alias for which to search.
6474 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6476 static ClassInfo* search_all_alias( const char* target ) {
6477 ClassInfo* found_class = NULL;
6478 QueryFrame* curr_frame = curr_query;
6480 while( curr_frame ) {
6481 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6484 curr_frame = curr_frame->next;
6491 @brief Add a class to the list of classes joined to the current query.
6492 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6493 the class name as an alias.
6494 @param classname The name of the class to be added.
6495 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6497 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6499 if( ! classname || ! *classname ) { // sanity check
6500 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6507 const ClassInfo* conflict = search_alias( alias );
6509 osrfLogError( OSRF_LOG_MARK,
6510 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6511 MODULENAME, alias, conflict->class_name );
6515 ClassInfo* info = allocate_class_info();
6517 if( build_class_info( info, alias, classname ) ) {
6518 free_class_info( info );
6522 // Add the new ClassInfo to the join list of the current QueryFrame
6523 info->next = curr_query->join_list;
6524 curr_query->join_list = info;
6530 @brief Destroy all nodes on the query stack.
6532 static void clear_query_stack( void ) {