3 @brief As a server, perform database operations at the request of clients.
7 #include "opensrf/osrf_application.h"
8 #include "opensrf/osrf_settings.h"
9 #include "opensrf/osrf_message.h"
10 #include "opensrf/utils.h"
11 #include "opensrf/osrf_json.h"
12 #include "opensrf/log.h"
13 #include "openils/oils_utils.h"
14 #include "openils/oils_constants.h"
23 # define MODULENAME "open-ils.reporter-store"
24 # define ENFORCE_PCRUD 0
27 # define MODULENAME "open-ils.pcrud"
28 # define ENFORCE_PCRUD 1
30 # define MODULENAME "open-ils.cstore"
31 # define ENFORCE_PCRUD 0
35 // The next four macros are OR'd together as needed to form a set
36 // of bitflags. SUBCOMBO enables an extra pair of parentheses when
37 // nesting one UNION, INTERSECT or EXCEPT inside another.
38 // SUBSELECT tells us we're in a subquery, so don't add the
39 // terminal semicolon yet.
42 #define DISABLE_I18N 2
43 #define SELECT_DISTINCT 1
48 struct ClassInfoStruct;
49 typedef struct ClassInfoStruct ClassInfo;
51 #define ALIAS_STORE_SIZE 16
52 #define CLASS_NAME_STORE_SIZE 16
54 struct ClassInfoStruct {
58 osrfHash* class_def; // Points into IDL
59 osrfHash* fields; // Points into IDL
60 osrfHash* links; // Points into IDL
62 // The remaining members are private and internal. Client code should not
63 // access them directly.
65 ClassInfo* next; // Supports linked list of joined classes
66 int in_use; // boolean
68 // We usually store the alias and class name in the following arrays, and
69 // point the corresponding pointers at them. When the string is too big
70 // for the array (which will probably never happen in practice), we strdup it.
72 char alias_store[ ALIAS_STORE_SIZE + 1 ];
73 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
76 struct QueryFrameStruct;
77 typedef struct QueryFrameStruct QueryFrame;
79 struct QueryFrameStruct {
81 ClassInfo* join_list; // linked list of classes joined to the core class
82 QueryFrame* next; // implements stack as linked list
83 int in_use; // boolean
86 static int timeout_needs_resetting;
87 static time_t time_next_reset;
89 int osrfAppChildInit();
90 int osrfAppInitialize();
91 void osrfAppChildExit();
93 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
95 static void setXactId( osrfMethodContext* ctx );
96 static inline const char* getXactId( osrfMethodContext* ctx );
97 static inline void clearXactId( osrfMethodContext* ctx );
99 int beginTransaction ( osrfMethodContext* );
100 int commitTransaction ( osrfMethodContext* );
101 int rollbackTransaction ( osrfMethodContext* );
103 int setSavepoint ( osrfMethodContext* );
104 int releaseSavepoint ( osrfMethodContext* );
105 int rollbackSavepoint ( osrfMethodContext* );
107 int doJSONSearch ( osrfMethodContext* );
109 int dispatchCRUDMethod ( osrfMethodContext* );
110 static int doSearch( osrfMethodContext* ctx );
111 static int doIdList( osrfMethodContext* ctx );
112 static jsonObject* doCreate ( osrfMethodContext*, int* );
113 static jsonObject* doRetrieve ( osrfMethodContext*, int* );
114 static jsonObject* doUpdate ( osrfMethodContext*, int* );
115 static jsonObject* doDelete ( osrfMethodContext*, int* );
116 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
117 jsonObject* where_hash, jsonObject* query_hash, int* err );
118 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
119 static jsonObject* oilsMakeJSONFromResult( dbi_result );
121 static char* searchSimplePredicate ( const char* op, const char* class_alias,
122 osrfHash* field, const jsonObject* node );
123 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
124 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject*);
125 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*, const char* );
126 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
127 static char* searchINPredicate ( const char*, osrfHash*,
128 jsonObject*, const char*, osrfMethodContext* );
129 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
130 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
131 static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
132 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
133 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
135 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
137 void userDataFree( void* );
138 static void sessionDataFree( char*, void* );
139 static char* getRelation( osrfHash* );
140 static int str_is_true( const char* str );
141 static int obj_is_true( const jsonObject* obj );
142 static const char* json_type( int code );
143 static const char* get_primitive( osrfHash* field );
144 static const char* get_datatype( osrfHash* field );
145 static int is_identifier( const char* s);
146 static int is_good_operator( const char* op );
147 static void pop_query_frame( void );
148 static void push_query_frame( void );
149 static int add_query_core( const char* alias, const char* class_name );
150 static inline ClassInfo* search_alias( const char* target );
151 static ClassInfo* search_all_alias( const char* target );
152 static ClassInfo* add_joined_class( const char* alias, const char* classname );
153 static void clear_query_stack( void );
155 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
156 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
157 static char* org_tree_root( osrfMethodContext* ctx );
158 static jsonObject* single_hash( const char* key, const char* value );
160 static const int enforce_pcrud = ENFORCE_PCRUD; // Boolean
161 static const char modulename[] = MODULENAME;
163 static int child_initialized = 0; /* boolean */
165 static dbi_conn writehandle; /* our MASTER db connection */
166 static dbi_conn dbhandle; /* our CURRENT db connection */
167 //static osrfHash * readHandles;
168 static jsonObject* const jsonNULL = NULL; //
169 static int max_flesh_depth = 100;
171 // The following points to the top of a stack of QueryFrames. It's a little
172 // confusing because the top level of the query is at the bottom of the stack.
173 static QueryFrame* curr_query = NULL;
176 @brief Disconnect from the database.
178 This function is called when the server drone is about to terminate.
180 void osrfAppChildExit() {
181 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
184 if (writehandle == dbhandle)
188 dbi_conn_query(writehandle, "ROLLBACK;");
189 dbi_conn_close(writehandle);
192 if (dbhandle && !same)
193 dbi_conn_close(dbhandle);
195 // XXX add cleanup of readHandles whenever that gets used
201 @brief Initialize the application.
202 @return Zero if successful, or non-zero if not.
204 Load the IDL file into an internal data structure for future reference. Each non-virtual
205 class in the IDL corresponds to a table or view in the database, or to a subquery defined
206 in the IDL. Ignore all virtual tables and virtual fields.
208 Register a number of methods, some of them general-purpose and others specific for
211 The name of the application is given by the MODULENAME macro, whose value depends on
212 conditional compilation. The method names also incorporate MODULENAME, followed by a
213 dot, as a prefix. Some methods are registered or not registered depending on whether
214 the PCRUD macro is defined, and on whether the IDL includes permacrud entries for the
217 The general-purpose methods are as follows (minus their MODULENAME prefixes):
219 - json_query (not registered for PCRUD)
222 - transaction.rollback
227 For each non-virtual class, create up to eight class-specific methods:
229 - create (not for readonly classes)
231 - update (not for readonly classes)
232 - delete (not for readonly classes
233 - search (atomic and non-atomic versions)
234 - id_list (atomic and non-atomic versions)
236 For PCRUD, the full method names follow the pattern "MODULENAME.method_type.classname".
237 Otherwise they follow the pattern "MODULENAME.direct.XXX.method_type", where XXX is the
238 fieldmapper name from the IDL, with every run of one or more consecutive colons replaced
239 by a period. In addition, the names of atomic methods have a suffix of ".atomic".
241 This function is called when the registering the application, and is executed by the
242 listener before spawning the drones.
244 int osrfAppInitialize() {
246 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
247 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
249 if (!oilsIDLInit( osrf_settings_host_value("/IDL") ))
250 return 1; /* return non-zero to indicate error */
252 growing_buffer* method_name = buffer_init(64);
254 if( ! enforce_pcrud ) {
255 // Generic search thingy (not for PCRUD)
256 buffer_add(method_name, MODULENAME);
257 buffer_add(method_name, ".json_query");
258 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
259 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
262 // first we register all the transaction and savepoint methods
263 buffer_reset(method_name);
264 OSRF_BUFFER_ADD(method_name, MODULENAME);
265 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
266 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
267 "beginTransaction", "", 0, 0 );
269 buffer_reset(method_name);
270 OSRF_BUFFER_ADD(method_name, MODULENAME);
271 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
272 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
273 "commitTransaction", "", 0, 0 );
275 buffer_reset(method_name);
276 OSRF_BUFFER_ADD(method_name, MODULENAME);
277 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
278 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
279 "rollbackTransaction", "", 0, 0 );
281 buffer_reset(method_name);
282 OSRF_BUFFER_ADD(method_name, MODULENAME);
283 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
284 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
285 "setSavepoint", "", 1, 0 );
287 buffer_reset(method_name);
288 OSRF_BUFFER_ADD(method_name, MODULENAME);
289 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
290 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
291 "releaseSavepoint", "", 1, 0 );
293 buffer_reset(method_name);
294 OSRF_BUFFER_ADD(method_name, MODULENAME);
295 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
296 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
297 "rollbackSavepoint", "", 1, 0 );
299 static const char* global_method[] = {
307 const int global_method_count
308 = sizeof( global_method ) / sizeof ( global_method[0] );
310 unsigned long class_count = osrfHashGetCount( oilsIDL() );
311 osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
312 osrfLogDebug(OSRF_LOG_MARK,
313 "At most %lu methods will be generated",
314 (unsigned long) (class_count * global_method_count) );
316 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
317 osrfHash* idlClass = NULL;
319 // For each class in the IDL...
320 while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
322 const char* classname = osrfHashIteratorKey( class_itr );
323 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
325 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
326 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
327 MODULENAME, classname);
331 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
332 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
336 // Look up some other attributes of the current class
337 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
338 if( !idlClass_fieldmapper ) {
339 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
344 osrfHash* idlClass_permacrud = NULL;
345 if( enforce_pcrud ) {
346 // For PCRUD, ignore classes with no permacrud section
347 idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
348 if (!idlClass_permacrud) {
349 osrfLogDebug( OSRF_LOG_MARK,
350 "Skipping class \"%s\"; no permacrud in IDL", classname );
355 const char* readonly = osrfHashGet(idlClass, "readonly");
358 for( i = 0; i < global_method_count; ++i ) { // for each global method
359 const char* method_type = global_method[ i ];
360 osrfLogDebug(OSRF_LOG_MARK,
361 "Using files to build %s class methods for %s", method_type, classname);
363 if( enforce_pcrud ) {
364 // Treat "id_list" or "search" as forms of "retrieve"
365 const char* tmp_method = method_type;
366 if ( *tmp_method == 'i' || *tmp_method == 's') { // "id_list" or "search"
367 tmp_method = "retrieve";
369 // Skip this method if there is no permacrud entry for it
370 if (!osrfHashGet( idlClass_permacrud, tmp_method ))
374 // No create, update, or delete methods for a readonly class
375 if ( str_is_true( readonly )
376 && ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd') )
379 buffer_reset( method_name );
381 // Build the method name
382 if( enforce_pcrud ) {
383 // For PCRUD: MODULENAME.method_type.classname
384 buffer_fadd(method_name, "%s.%s.%s", modulename, method_type, classname);
386 // For non-PCRUD: MODULENAME.MODULENAME.direct.XXX.method_type
387 // where XXX is the fieldmapper name from the IDL, with every run of
388 // one or more consecutive colons replaced by a period.
391 char* _fm = strdup( idlClass_fieldmapper );
392 part = strtok_r(_fm, ":", &st_tmp);
394 buffer_fadd(method_name, "%s.direct.%s", modulename, part);
396 while ((part = strtok_r(NULL, ":", &st_tmp))) {
397 OSRF_BUFFER_ADD_CHAR(method_name, '.');
398 OSRF_BUFFER_ADD(method_name, part);
400 OSRF_BUFFER_ADD_CHAR(method_name, '.');
401 OSRF_BUFFER_ADD(method_name, method_type);
405 // For an id_list or search method we specify the OSRF_METHOD_STREAMING option.
406 // The consequence is that we implicitly create an atomic method in addition to
407 // the usual non-atomic method.
409 if (*method_type == 'i' || *method_type == 's') { // id_list or search
410 flags = flags | OSRF_METHOD_STREAMING;
413 osrfHash* method_meta = osrfNewHash();
414 osrfHashSet( method_meta, idlClass, "class");
415 osrfHashSet( method_meta, buffer_data( method_name ), "methodname" );
416 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
418 // Register the method, with a pointer to an osrfHash to tell the method
419 // its name, type, and class.
420 osrfAppRegisterExtendedMethod(
422 OSRF_BUFFER_C_STR( method_name ),
423 "dispatchCRUDMethod",
430 } // end for each global method
431 } // end for each class in IDL
433 buffer_free( method_name );
434 osrfHashIteratorFree( class_itr );
440 @brief Get a table name, view name, or subquery for use in a FROM clause.
441 @param class Pointer to the IDL class entry.
442 @return A table name, a view name, or a subquery in parentheses.
444 In some cases the IDL defines a class, not with a table name or a view name, but with
445 a SELECT statement, which may be used as a subquery.
447 static char* getRelation( osrfHash* class ) {
449 char* source_def = NULL;
450 const char* tabledef = osrfHashGet(class, "tablename");
453 source_def = strdup( tabledef ); // Return the name of a table or view
455 tabledef = osrfHashGet( class, "source_definition" );
457 // Return a subquery, enclosed in parentheses
458 source_def = safe_malloc( strlen( tabledef ) + 3 );
459 source_def[ 0 ] = '(';
460 strcpy( source_def + 1, tabledef );
461 strcat( source_def, ")" );
463 // Not found: return an error
464 const char* classname = osrfHashGet( class, "classname" );
469 "%s ERROR No tablename or source_definition for class \"%s\"",
480 @brief Initialize a server drone.
481 @return Zero if successful, -1 if not.
483 Connect to the database. For each non-virtual class in the IDL, execute a dummy "SELECT * "
484 query to get the datatype of each column. Record the datatypes in the loaded IDL.
486 This function is called by a server drone shortly after it is spawned by the listener.
488 int osrfAppChildInit() {
490 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
491 dbi_initialize(NULL);
492 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
494 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
495 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
496 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
497 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
498 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
499 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
500 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion",
503 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
504 writehandle = dbi_conn_new(driver);
507 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
510 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
512 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
513 "port=%s, user=%s, db=%s", MODULENAME, host, port, user, db );
515 if(host) dbi_conn_set_option(writehandle, "host", host );
516 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
517 if(user) dbi_conn_set_option(writehandle, "username", user);
518 if(pw) dbi_conn_set_option(writehandle, "password", pw );
519 if(db) dbi_conn_set_option(writehandle, "dbname", db );
521 if(md) max_flesh_depth = atoi(md);
522 if(max_flesh_depth < 0) max_flesh_depth = 1;
523 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
532 if (dbi_conn_connect(writehandle) < 0) {
534 if (dbi_conn_connect(writehandle) < 0) {
535 dbi_conn_error(writehandle, &err);
536 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
541 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
543 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
544 osrfHash* class = NULL;
545 growing_buffer* query_buf = buffer_init( 64 );
547 // For each class in the IDL...
548 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
549 const char* classname = osrfHashIteratorKey( class_itr );
550 osrfHash* fields = osrfHashGet( class, "fields" );
552 // If the class is virtual, ignore it
553 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
554 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
558 char* tabledef = getRelation(class);
560 continue; // No such relation -- a query of it would be doomed to failure
562 buffer_reset( query_buf );
563 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
567 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
568 MODULENAME, OSRF_BUFFER_C_STR( query_buf ) );
570 dbi_result result = dbi_conn_query( writehandle, OSRF_BUFFER_C_STR( query_buf ) );
574 const char* columnName;
576 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
578 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
581 /* fetch the fieldmapper index */
582 if( (_f = osrfHashGet(fields, columnName)) ) {
584 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
586 /* determine the field type and storage attributes */
588 switch( dbi_result_get_field_type_idx(result, columnIndex) ) {
590 case DBI_TYPE_INTEGER : {
592 if ( !osrfHashGet(_f, "primitive") )
593 osrfHashSet(_f, "number", "primitive");
595 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
596 if( attr & DBI_INTEGER_SIZE8 )
597 osrfHashSet(_f, "INT8", "datatype");
599 osrfHashSet(_f, "INT", "datatype");
602 case DBI_TYPE_DECIMAL :
603 if ( !osrfHashGet(_f, "primitive") )
604 osrfHashSet(_f, "number", "primitive");
606 osrfHashSet(_f,"NUMERIC", "datatype");
609 case DBI_TYPE_STRING :
610 if ( !osrfHashGet(_f, "primitive") )
611 osrfHashSet(_f,"string", "primitive");
613 osrfHashSet(_f,"TEXT", "datatype");
616 case DBI_TYPE_DATETIME :
617 if ( !osrfHashGet(_f, "primitive") )
618 osrfHashSet(_f,"string", "primitive");
620 osrfHashSet(_f,"TIMESTAMP", "datatype");
623 case DBI_TYPE_BINARY :
624 if ( !osrfHashGet(_f, "primitive") )
625 osrfHashSet(_f,"string", "primitive");
627 osrfHashSet(_f,"BYTEA", "datatype");
632 "Setting [%s] to primitive [%s] and datatype [%s]...",
634 osrfHashGet(_f, "primitive"),
635 osrfHashGet(_f, "datatype")
639 } // end while loop for traversing columns of result
640 dbi_result_free(result);
642 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", classname);
644 } // end for each class in IDL
646 buffer_free( query_buf );
647 osrfHashIteratorFree( class_itr );
648 child_initialized = 1;
653 @brief Install a database driver.
654 @param conn Pointer to a database driver.
656 The driver is used to process quoted strings correctly.
658 This function is a sleazy hack, intended @em only for testing and debugging without
659 actually connecting to a database. Any real server process should initialize the
660 database connection by calling osrfAppChildInit().
662 void set_cstore_dbi_conn( dbi_conn conn ) {
663 dbhandle = writehandle = conn;
667 @brief Free an osrfHash that stores a transaction ID.
668 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
670 This function is a callback, to be called by the application session when it ends.
671 The application session stores the osrfHash via an opaque pointer.
673 If the osrfHash contains an entry for the key "xact_id", it means that an
674 uncommitted transaction is pending. Roll it back.
676 void userDataFree( void* blob ) {
677 osrfHash* hash = (osrfHash*) blob;
678 if( osrfHashGet( hash, "xact_id" ) && writehandle )
679 dbi_conn_query( writehandle, "ROLLBACK;" );
681 osrfHashFree( hash );
685 @name Managing session data
686 @brief Maintain data stored via the userData pointer of the application session.
688 Currently, session-level data is stored in an osrfHash. Other arrangements are
689 possible, and some would be more efficient. The application session calls a
690 callback function to free userData before terminating.
692 Currently, the only data we store at the session level is the transaction id. By this
693 means we can ensure that any pending transactions are rolled back before the application
699 @brief Free an item in the application session's userData.
700 @param key The name of a key for an osrfHash.
701 @param item An opaque pointer to the item associated with the key.
703 We store an osrfHash as userData with the application session, and arrange (by
704 installing userDataFree() as a different callback) for the session to free that
705 osrfHash before terminating.
707 This function is a callback for freeing items in the osrfHash. Currently we store
709 - Transaction id of a pending transaction; a character string. Key: "xact_id".
710 - Authkey; a character string. Key: "authkey".
711 - User object from the authentication server; a jsonObject. Key: "user_login".
713 If we ever store anything else in userData, we will need to revisit this function so
714 that it will free whatever else needs freeing.
716 static void sessionDataFree( char* key, void* item ) {
717 if ( !strcmp( key, "xact_id" )
718 || !strcmp( key, "authkey" ) ) {
720 } else if( !strcmp( key, "user_login" ) )
721 jsonObjectFree( (jsonObject*) item );
725 @brief Save a transaction id.
726 @param ctx Pointer to the method context.
728 Save the session_id of the current application session as a transaction id.
730 static void setXactId( osrfMethodContext* ctx ) {
731 if( ctx && ctx->session ) {
732 osrfAppSession* session = ctx->session;
734 osrfHash* cache = session->userData;
736 // If the session doesn't already have a hash, create one. Make sure
737 // that the application session frees the hash when it terminates.
738 if( NULL == cache ) {
739 session->userData = cache = osrfNewHash();
740 osrfHashSetCallback( cache, &sessionDataFree );
741 ctx->session->userDataFree = &userDataFree;
744 // Save the transaction id in the hash, with the key "xact_id"
745 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
750 @brief Get the transaction ID for the current transaction, if any.
751 @param ctx Pointer to the method context.
752 @return Pointer to the transaction ID.
754 The return value points to an internal buffer, and will become invalid upon issuing
755 a commit or rollback.
757 static inline const char* getXactId( osrfMethodContext* ctx ) {
758 if( ctx && ctx->session && ctx->session->userData )
759 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
765 @brief Clear the current transaction id.
766 @param ctx Pointer to the method context.
768 static inline void clearXactId( osrfMethodContext* ctx ) {
769 if( ctx && ctx->session && ctx->session->userData )
770 osrfHashRemove( ctx->session->userData, "xact_id" );
775 @brief Save the user's login in the userData for the current application session.
776 @param ctx Pointer to the method context.
777 @param user_login Pointer to the user login object to be cached (we cache the original,
780 If @a user_login is NULL, remove the user login if one is already cached.
782 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
783 if( ctx && ctx->session ) {
784 osrfAppSession* session = ctx->session;
786 osrfHash* cache = session->userData;
788 // If the session doesn't already have a hash, create one. Make sure
789 // that the application session frees the hash when it terminates.
790 if( NULL == cache ) {
791 session->userData = cache = osrfNewHash();
792 osrfHashSetCallback( cache, &sessionDataFree );
793 ctx->session->userDataFree = &userDataFree;
797 osrfHashSet( cache, user_login, "user_login" );
799 osrfHashRemove( cache, "user_login" );
804 @brief Get the user login object for the current application session, if any.
805 @param ctx Pointer to the method context.
806 @return Pointer to the user login object if found; otherwise NULL.
808 The user login object was returned from the authentication server, and then cached so
809 we don't have to call the authentication server again for the same user.
811 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
812 if( ctx && ctx->session && ctx->session->userData )
813 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
819 @brief Save a copy of an authkey in the userData of the current application session.
820 @param ctx Pointer to the method context.
821 @param authkey The authkey to be saved.
823 If @a authkey is NULL, remove the authkey if one is already cached.
825 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
826 if( ctx && ctx->session && authkey ) {
827 osrfAppSession* session = ctx->session;
828 osrfHash* cache = session->userData;
830 // If the session doesn't already have a hash, create one. Make sure
831 // that the application session frees the hash when it terminates.
832 if( NULL == cache ) {
833 session->userData = cache = osrfNewHash();
834 osrfHashSetCallback( cache, &sessionDataFree );
835 ctx->session->userDataFree = &userDataFree;
838 // Save the transaction id in the hash, with the key "xact_id"
839 if( authkey && *authkey )
840 osrfHashSet( cache, strdup( authkey ), "authkey" );
842 osrfHashRemove( cache, "authkey" );
847 @brief Reset the login timeout.
848 @param authkey The authentication key for the current login session.
849 @param now The current time.
850 @return Zero if successful, or 1 if not.
852 Tell the authentication server to reset the timeout so that the login session won't
853 expire for a while longer.
855 We could dispense with the @a now parameter by calling time(). But we just called
856 time() in order to decide whether to reset the timeout, so we might as well reuse
857 the result instead of calling time() again.
859 static int reset_timeout( const char* authkey, time_t now ) {
860 jsonObject* auth_object = jsonNewObject( authkey );
862 // Ask the authentication server to reset the timeout. It returns an event
863 // indicating success or failure.
864 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
865 "open-ils.auth.session.reset_timeout", auth_object );
866 jsonObjectFree(auth_object);
868 if( !result || result->type != JSON_HASH ) {
869 osrfLogError( OSRF_LOG_MARK,
870 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
871 jsonObjectFree( result );
872 return 1; // Not the right sort of object returned
875 const jsonObject* ilsevent = jsonObjectGetKey( result, "ilsevent" );
876 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
877 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
878 jsonObjectFree( result );
879 return 1; // Return code from method not available
882 if( jsonObjectGetNumber( ilsevent ) != 0.0 ){
883 const char* desc = jsonObjectGetString( jsonObjectGetKey( result, "desc" ) );
885 desc = "(No reason available)"; // failsafe; shouldn't happen
886 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
887 jsonObjectFree( result );
891 // Revise our local proxy for the timeout deadline
892 // by a smallish fraction of the timeout interval
893 const char* timeout = jsonObjectGetString( jsonObjectGetKey( result, "payload" ) );
895 timeout = "1"; // failsafe; shouldn't happen
896 time_next_reset = now + atoi( timeout ) / 15;
898 jsonObjectFree( result );
899 return 0; // Successfully reset timeout
903 @brief Get the authkey string for the current application session, if any.
904 @param ctx Pointer to the method context.
905 @return Pointer to the cached authkey if found; otherwise NULL.
907 If present, the authkey string was cached from a previous method call.
909 static const char* getAuthkey( osrfMethodContext* ctx ) {
910 if( ctx && ctx->session && ctx->session->userData ) {
911 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
913 // Possibly reset the authentication timeout to keep the login alive. We do so
914 // no more than once per method call, and not at all if it has been only a short
915 // time since the last reset.
917 // Here we reset explicitly, if at all. We also implicitly reset the timeout
918 // whenever we call the "open-ils.auth.session.retrieve" method.
919 if( timeout_needs_resetting ) {
920 time_t now = time( NULL );
921 if( now >= time_next_reset && reset_timeout( authkey, now ) )
922 authkey = NULL; // timeout has apparently expired already
925 timeout_needs_resetting = 0;
933 @brief Implement the transaction.begin method.
934 @param ctx Pointer to the method context.
935 @return Zero if successful, or -1 upon error.
937 Start a transaction. Save a transaction ID for future reference.
940 - authkey (PCRUD only)
942 Return to client: Transaction ID
944 int beginTransaction ( osrfMethodContext* ctx ) {
945 if(osrfMethodVerifyContext( ctx )) {
946 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
950 if( enforce_pcrud ) {
951 timeout_needs_resetting = 1;
952 const jsonObject* user = verifyUserPCRUD( ctx );
957 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
959 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
960 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
961 "osrfMethodException", ctx->request, "Error starting transaction" );
965 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
966 osrfAppRespondComplete( ctx, ret );
973 @brief Implement the savepoint.set method.
974 @param ctx Pointer to the method context.
975 @return Zero if successful, or -1 if not.
977 Issue a SAVEPOINT to the database server.
980 - authkey (PCRUD only)
983 Return to client: Savepoint name
985 int setSavepoint ( osrfMethodContext* ctx ) {
986 if(osrfMethodVerifyContext( ctx )) {
987 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
992 if( enforce_pcrud ) {
994 timeout_needs_resetting = 1;
995 const jsonObject* user = verifyUserPCRUD( ctx );
1000 // Verify that a transaction is pending
1001 const char* trans_id = getXactId( ctx );
1002 if( NULL == trans_id ) {
1003 osrfAppSessionStatus(
1005 OSRF_STATUS_INTERNALSERVERERROR,
1006 "osrfMethodException",
1008 "No active transaction -- required for savepoints"
1013 // Get the savepoint name from the method params
1014 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1016 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
1020 "%s: Error creating savepoint %s in transaction %s",
1025 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1026 "osrfMethodException", ctx->request, "Error creating savepoint" );
1029 jsonObject* ret = jsonNewObject(spName);
1030 osrfAppRespondComplete( ctx, ret );
1031 jsonObjectFree(ret);
1037 @brief Implement the savepoint.release method.
1038 @param ctx Pointer to the method context.
1039 @return Zero if successful, or -1 if not.
1041 Issue a RELEASE SAVEPOINT to the database server.
1044 - authkey (PCRUD only)
1047 Return to client: Savepoint name
1049 int releaseSavepoint ( osrfMethodContext* ctx ) {
1050 if(osrfMethodVerifyContext( ctx )) {
1051 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1056 if( enforce_pcrud ) {
1058 timeout_needs_resetting = 1;
1059 const jsonObject* user = verifyUserPCRUD( ctx );
1064 // Verify that a transaction is pending
1065 const char* trans_id = getXactId( ctx );
1066 if( NULL == trans_id ) {
1067 osrfAppSessionStatus(
1069 OSRF_STATUS_INTERNALSERVERERROR,
1070 "osrfMethodException",
1072 "No active transaction -- required for savepoints"
1077 // Get the savepoint name from the method params
1078 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1080 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
1084 "%s: Error releasing savepoint %s in transaction %s",
1089 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1090 "osrfMethodException", ctx->request, "Error releasing savepoint" );
1093 jsonObject* ret = jsonNewObject(spName);
1094 osrfAppRespondComplete( ctx, ret );
1095 jsonObjectFree(ret);
1101 @brief Implement the savepoint.rollback method.
1102 @param ctx Pointer to the method context.
1103 @return Zero if successful, or -1 if not.
1105 Issue a ROLLBACK TO SAVEPOINT to the database server.
1108 - authkey (PCRUD only)
1111 Return to client: Savepoint name
1113 int rollbackSavepoint ( osrfMethodContext* ctx ) {
1114 if(osrfMethodVerifyContext( ctx )) {
1115 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1120 if( enforce_pcrud ) {
1122 timeout_needs_resetting = 1;
1123 const jsonObject* user = verifyUserPCRUD( ctx );
1128 // Verify that a transaction is pending
1129 const char* trans_id = getXactId( ctx );
1130 if( NULL == trans_id ) {
1131 osrfAppSessionStatus(
1133 OSRF_STATUS_INTERNALSERVERERROR,
1134 "osrfMethodException",
1136 "No active transaction -- required for savepoints"
1141 // Get the savepoint name from the method params
1142 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1144 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
1148 "%s: Error rolling back savepoint %s in transaction %s",
1153 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1154 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1157 jsonObject* ret = jsonNewObject(spName);
1158 osrfAppRespondComplete( ctx, ret );
1159 jsonObjectFree(ret);
1165 @brief Implement the transaction.commit method.
1166 @param ctx Pointer to the method context.
1167 @return Zero if successful, or -1 if not.
1169 Issue a COMMIT to the database server.
1172 - authkey (PCRUD only)
1174 Return to client: Transaction ID.
1176 int commitTransaction ( osrfMethodContext* ctx ) {
1177 if(osrfMethodVerifyContext( ctx )) {
1178 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1182 if( enforce_pcrud ) {
1183 timeout_needs_resetting = 1;
1184 const jsonObject* user = verifyUserPCRUD( ctx );
1189 // Verify that a transaction is pending
1190 const char* trans_id = getXactId( ctx );
1191 if( NULL == trans_id ) {
1192 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1193 "osrfMethodException", ctx->request, "No active transaction to commit" );
1197 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
1199 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
1200 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1201 "osrfMethodException", ctx->request, "Error committing transaction" );
1204 jsonObject* ret = jsonNewObject( trans_id );
1205 osrfAppRespondComplete( ctx, ret );
1206 jsonObjectFree(ret);
1213 @brief Implement the transaction.rollback method.
1214 @param ctx Pointer to the method context.
1215 @return Zero if successful, or -1 if not.
1217 Issue a ROLLBACK to the database server.
1220 - authkey (PCRUD only)
1222 Return to client: Transaction ID
1224 int rollbackTransaction ( osrfMethodContext* ctx ) {
1225 if(osrfMethodVerifyContext( ctx )) {
1226 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1230 if( enforce_pcrud ) {
1231 timeout_needs_resetting = 1;
1232 const jsonObject* user = verifyUserPCRUD( ctx );
1237 // Verify that a transaction is pending
1238 const char* trans_id = getXactId( ctx );
1239 if( NULL == trans_id ) {
1240 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1241 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1245 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
1247 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
1248 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1249 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1252 jsonObject* ret = jsonNewObject( trans_id );
1253 osrfAppRespondComplete( ctx, ret );
1254 jsonObjectFree(ret);
1261 @brief Implement the class-specific methods.
1262 @param ctx Pointer to the method context.
1263 @return Zero if successful, or -1 if not.
1265 Branch on the method type: create, retrieve, update, delete, search, or id_list.
1267 The method parameters and the type of value returned to the client depend on the method
1268 type. However, for PCRUD methods, the first method parameter should always be an
1271 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
1272 if(osrfMethodVerifyContext( ctx )) {
1273 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1277 int err = 0; // to be returned to caller
1278 jsonObject * obj = NULL; // to be returned to client
1281 timeout_needs_resetting = 1;
1283 // Get the method type so that we can branch on it
1284 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1285 const char* methodtype = osrfHashGet( method_meta, "methodtype" );
1287 if (!strcmp(methodtype, "create")) {
1288 obj = doCreate(ctx, &err);
1289 osrfAppRespondComplete( ctx, obj );
1291 else if (!strcmp(methodtype, "retrieve")) {
1292 obj = doRetrieve(ctx, &err);
1293 osrfAppRespondComplete( ctx, obj );
1295 else if (!strcmp(methodtype, "update")) {
1296 obj = doUpdate(ctx, &err);
1297 osrfAppRespondComplete( ctx, obj );
1299 else if (!strcmp(methodtype, "delete")) {
1300 obj = doDelete(ctx, &err);
1301 osrfAppRespondComplete( ctx, obj );
1303 else if (!strcmp(methodtype, "search")) {
1304 err = doSearch( ctx );
1305 osrfAppRespondComplete( ctx, NULL );
1306 } else if (!strcmp(methodtype, "id_list")) {
1307 err = doIdList( ctx );
1308 osrfAppRespondComplete( ctx, NULL );
1310 osrfAppRespondComplete( ctx, NULL ); // should be unreachable...
1313 jsonObjectFree(obj);
1319 @brief Implement the "search" method.
1320 @param ctx Pointer to the method context.
1321 @return Zero if successful, or -1 if not.
1324 - authkey (PCRUD only)
1325 - WHERE clause, as jsonObject
1326 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1328 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1329 Optionally flesh linked fields.
1331 static int doSearch( osrfMethodContext* ctx ) {
1333 jsonObject* where_clause;
1334 jsonObject* rest_of_query;
1336 if( enforce_pcrud ) {
1337 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1338 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1340 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1341 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1344 // Get the class metadata
1345 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1346 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1350 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1354 // Return each row to the client (except that some may be suppressed by PCRUD)
1355 jsonObject* cur = 0;
1356 unsigned long res_idx = 0;
1357 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1358 if( enforce_pcrud && !verifyObjectPCRUD(ctx, cur))
1360 osrfAppRespond( ctx, cur );
1362 jsonObjectFree( obj );
1368 @brief Implement the "id_list" method.
1369 @param ctx Pointer to the method context.
1370 @param err Pointer through which to return an error code.
1371 @return Zero if successful, or -1 if not.
1374 - authkey (PCRUD only)
1375 - WHERE clause, as jsonObject
1376 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1378 Return to client: The primary key values for all rows of the relevant class that
1379 satisfy a specified WHERE clause.
1381 This method relies on the assumption that every class has a primary key consisting of
1384 static int doIdList( osrfMethodContext* ctx ) {
1385 jsonObject* where_clause;
1386 jsonObject* rest_of_query;
1388 // We use the where clause without change. But we need to massage the rest of the
1389 // query, so we work with a copy of it instead of modifying the original.
1391 if( enforce_pcrud ) {
1392 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1393 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1395 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1396 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1399 // Eliminate certain SQL clauses, if present.
1400 if ( rest_of_query ) {
1401 jsonObjectRemoveKey( rest_of_query, "select" );
1402 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1403 jsonObjectRemoveKey( rest_of_query, "flesh" );
1404 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
1406 rest_of_query = jsonNewObjectType( JSON_HASH );
1409 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1411 // Get the class metadata
1412 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1413 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1415 // Build a SELECT list containing just the primary key,
1416 // i.e. like { "classname":["keyname"] }
1417 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1419 // Load array with name of primary key
1420 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1421 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1422 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1424 jsonObjectSetKey( rest_of_query, "select", select_clause );
1429 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1431 jsonObjectFree( rest_of_query );
1435 // Return each primary key value to the client
1437 unsigned long res_idx = 0;
1438 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1439 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1440 continue; // Suppress due to lack of permission
1442 osrfAppRespond( ctx,
1443 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1445 jsonObjectFree( obj );
1451 @brief Verify that we have a valid class reference.
1452 @param ctx Pointer to the method context.
1453 @param param Pointer to the method parameters.
1454 @return 1 if the class reference is valid, or zero if it isn't.
1456 The class of the method params must match the class to which the method id devoted.
1457 For PCRUD there are additional restrictions.
1459 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1461 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1462 osrfHash* class = osrfHashGet( method_meta, "class" );
1464 // Compare the method's class to the parameters' class
1465 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1467 // Oops -- they don't match. Complain.
1468 growing_buffer* msg = buffer_init(128);
1471 "%s: %s method for type %s was passed a %s",
1473 osrfHashGet(method_meta, "methodtype"),
1474 osrfHashGet(class, "classname"),
1475 param->classname ? param->classname : "(null)"
1478 char* m = buffer_release(msg);
1479 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1487 return verifyObjectPCRUD( ctx, param );
1493 @brief (PCRUD only) Verify that the user is properly logged in.
1494 @param ctx Pointer to the method context.
1495 @return If the user is logged in, a pointer to the user object from the authentication
1496 server; otherwise NULL.
1498 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1500 // Get the authkey (the first method parameter)
1501 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1503 // See if we have the same authkey, and a user object,
1504 // locally cached from a previous call
1505 const char* cached_authkey = getAuthkey( ctx );
1506 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1507 const jsonObject* cached_user = getUserLogin( ctx );
1512 // We have no matching authentication data in the cache. Authenticate from scratch.
1513 jsonObject* auth_object = jsonNewObject(auth);
1515 // Fetch the user object from the authentication server
1516 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1518 jsonObjectFree(auth_object);
1520 if (!user->classname || strcmp(user->classname, "au")) {
1522 growing_buffer* msg = buffer_init(128);
1525 "%s: permacrud received a bad auth token: %s",
1530 char* m = buffer_release(msg);
1531 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1535 jsonObjectFree(user);
1539 setUserLogin( ctx, user );
1540 setAuthkey( ctx, auth );
1542 // Allow ourselves up to a second before we have to reset the login timeout.
1543 // It would be nice to use some fraction of the timeout interval enforced by the
1544 // authentication server, but that value is not readily available at this point.
1545 // Instead, we use a conservative default interval.
1546 time_next_reset = time( NULL ) + 1;
1551 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1553 dbhandle = writehandle;
1555 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1556 osrfHash* class = osrfHashGet( method_metadata, "class" );
1557 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1560 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
1561 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1562 } else if ( *method_type == 'u' || *method_type == 'd' ) {
1563 fetch = 1; // MUST go to the db for the object for update and delete
1566 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
1569 // No permacrud for this method type on this class
1571 growing_buffer* msg = buffer_init(128);
1574 "%s: %s on class %s has no permacrud IDL entry",
1576 osrfHashGet(method_metadata, "methodtype"),
1577 osrfHashGet(class, "classname")
1580 char* m = buffer_release(msg);
1581 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1582 "osrfMethodException", ctx->request, m );
1589 const jsonObject* user = verifyUserPCRUD( ctx );
1593 int userid = atoi( oilsFMGetString( user, "id" ) );
1595 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1596 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1597 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1599 osrfStringArray* context_org_array = osrfNewStringArray(1);
1602 char* pkey_value = NULL;
1603 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1604 osrfLogDebug( OSRF_LOG_MARK,
1605 "global-level permissions required, fetching top of the org tree" );
1607 // check for perm at top of org tree
1608 char* org_tree_root_id = org_tree_root( ctx );
1609 if( org_tree_root_id ) {
1610 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1611 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1613 osrfStringArrayFree( context_org_array );
1618 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1619 "fetching context org ids" );
1620 const char* pkey = osrfHashGet(class, "primarykey");
1621 jsonObject *param = NULL;
1623 if (obj->classname) {
1624 pkey_value = oilsFMGetString( obj, pkey );
1626 param = jsonObjectClone(obj);
1627 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1630 pkey_value = jsonObjectToSimpleString( obj );
1632 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1633 "of %s and retrieving from the database", pkey_value );
1637 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1638 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1639 jsonObjectFree(_tmp_params);
1641 param = jsonObjectExtractIndex(_list, 0);
1642 jsonObjectFree(_list);
1646 osrfLogDebug( OSRF_LOG_MARK,
1647 "Object not found in the database with primary key %s of %s",
1650 growing_buffer* msg = buffer_init(128);
1653 "%s: no object found with primary key %s of %s",
1659 char* m = buffer_release(msg);
1660 osrfAppSessionStatus(
1662 OSRF_STATUS_INTERNALSERVERERROR,
1663 "osrfMethodException",
1669 if (pkey_value) free(pkey_value);
1674 if (local_context->size > 0) {
1675 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1676 local_context->size);
1678 const char* lcontext = NULL;
1679 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1680 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1683 "adding class-local field %s (value: %s) to the context org list",
1685 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1690 if (foreign_context) {
1691 unsigned long class_count = osrfHashGetCount( foreign_context );
1692 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1694 if (class_count > 0) {
1696 osrfHash* fcontext = NULL;
1697 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1698 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1699 const char* class_name = osrfHashIteratorKey( class_itr );
1700 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1704 "%d foreign context fields(s) specified for class %s",
1705 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1709 char* foreign_pkey = osrfHashGet(fcontext, "field");
1710 char* foreign_pkey_value =
1711 oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1713 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1715 jsonObject* _list = doFieldmapperSearch(
1716 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1718 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1719 jsonObjectFree(_tmp_params);
1720 jsonObjectFree(_list);
1722 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1724 if (_fparam && jump_list) {
1725 const char* flink = NULL;
1727 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1728 free(foreign_pkey_value);
1730 osrfHash* foreign_link_hash =
1731 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1733 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1734 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1736 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1738 _list = doFieldmapperSearch(
1740 osrfHashGet( oilsIDL(),
1741 osrfHashGet( foreign_link_hash, "class" ) ),
1747 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1748 jsonObjectFree(_tmp_params);
1749 jsonObjectFree(_list);
1755 growing_buffer* msg = buffer_init(128);
1758 "%s: no object found with primary key %s of %s",
1764 char* m = buffer_release(msg);
1765 osrfAppSessionStatus(
1767 OSRF_STATUS_INTERNALSERVERERROR,
1768 "osrfMethodException",
1774 osrfHashIteratorFree(class_itr);
1775 free(foreign_pkey_value);
1776 jsonObjectFree(param);
1781 free(foreign_pkey_value);
1784 const char* foreign_field = NULL;
1785 while ( (foreign_field = osrfStringArrayGetString(
1786 osrfHashGet(fcontext,"context" ), j++)) ) {
1787 osrfStringArrayAdd( context_org_array,
1788 oilsFMGetString( _fparam, foreign_field ) );
1791 "adding foreign class %s field %s (value: %s) to the context org list",
1794 osrfStringArrayGetString(
1795 context_org_array, context_org_array->size - 1)
1799 jsonObjectFree(_fparam);
1802 osrfHashIteratorFree( class_itr );
1806 jsonObjectFree(param);
1809 const char* context_org = NULL;
1810 const char* perm = NULL;
1813 if (permission->size == 0) {
1814 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1819 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1821 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1827 "Checking object permission [%s] for user %d "
1828 "on object %s (class %s) at org %d",
1832 osrfHashGet(class, "classname"),
1836 result = dbi_conn_queryf(
1838 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1841 osrfHashGet(class, "classname"),
1849 "Received a result for object permission [%s] "
1850 "for user %d on object %s (class %s) at org %d",
1854 osrfHashGet(class, "classname"),
1858 if (dbi_result_first_row(result)) {
1859 jsonObject* return_val = oilsMakeJSONFromResult( result );
1860 const char* has_perm = jsonObjectGetString(
1861 jsonObjectGetKeyConst(return_val, "has_perm") );
1865 "Status of object permission [%s] for user %d "
1866 "on object %s (class %s) at org %d is %s",
1870 osrfHashGet(class, "classname"),
1875 if ( *has_perm == 't' ) OK = 1;
1876 jsonObjectFree(return_val);
1879 dbi_result_free(result);
1885 osrfLogDebug( OSRF_LOG_MARK,
1886 "Checking non-object permission [%s] for user %d at org %d",
1887 perm, userid, atoi(context_org) );
1888 result = dbi_conn_queryf(
1890 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1897 osrfLogDebug( OSRF_LOG_MARK,
1898 "Received a result for permission [%s] for user %d at org %d",
1899 perm, userid, atoi(context_org) );
1900 if ( dbi_result_first_row(result) ) {
1901 jsonObject* return_val = oilsMakeJSONFromResult( result );
1902 const char* has_perm = jsonObjectGetString(
1903 jsonObjectGetKeyConst(return_val, "has_perm") );
1904 osrfLogDebug( OSRF_LOG_MARK,
1905 "Status of permission [%s] for user %d at org %d is [%s]",
1906 perm, userid, atoi(context_org), has_perm );
1907 if ( *has_perm == 't' )
1909 jsonObjectFree(return_val);
1912 dbi_result_free(result);
1921 if (pkey_value) free(pkey_value);
1922 osrfStringArrayFree(context_org_array);
1928 @brief Look up the root of the org_unit tree.
1929 @param ctx Pointer to the method context.
1930 @return The id of the root org unit, as a character string.
1932 Query actor.org_unit where parent_ou is null, and return the id as a string.
1934 This function assumes that there is only one root org unit, i.e. that we
1935 have a single tree, not a forest.
1937 The calling code is responsible for freeing the returned string.
1939 static char* org_tree_root( osrfMethodContext* ctx ) {
1941 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1942 static time_t last_lookup_time = 0;
1943 time_t current_time = time( NULL );
1945 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1946 // We successfully looked this up less than an hour ago.
1947 // It's not likely to have changed since then.
1948 return strdup( cached_root_id );
1950 last_lookup_time = current_time;
1953 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1954 jsonObject* result = doFieldmapperSearch(
1955 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1956 jsonObjectFree( where_clause );
1958 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1961 jsonObjectFree( result );
1963 growing_buffer* msg = buffer_init(128);
1964 OSRF_BUFFER_ADD( msg, MODULENAME );
1965 OSRF_BUFFER_ADD( msg,
1966 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1968 char* m = buffer_release(msg);
1969 osrfAppSessionStatus( ctx->session,
1970 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1973 cached_root_id[ 0 ] = '\0';
1977 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1978 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1980 jsonObjectFree( result );
1982 strcpy( cached_root_id, root_org_unit_id );
1983 return root_org_unit_id;
1987 @brief Create a JSON_HASH with a single key/value pair.
1988 @param key The key of the key/value pair.
1989 @param value the value of the key/value pair.
1990 @return Pointer to a newly created jsonObject of type JSON_HASH.
1992 The value of the key/value is either a string or (if @a value is NULL) a null.
1994 static jsonObject* single_hash( const char* key, const char* value ) {
1996 if( ! key ) key = "";
1998 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1999 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2004 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
2006 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2007 jsonObject* target = NULL;
2008 jsonObject* options = NULL;
2010 if( enforce_pcrud ) {
2011 target = jsonObjectGetIndex( ctx->params, 1 );
2012 options = jsonObjectGetIndex( ctx->params, 2 );
2014 target = jsonObjectGetIndex( ctx->params, 0 );
2015 options = jsonObjectGetIndex( ctx->params, 1 );
2018 if (!verifyObjectClass(ctx, target)) {
2023 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2025 const char* trans_id = getXactId( ctx );
2027 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2029 osrfAppSessionStatus(
2031 OSRF_STATUS_BADREQUEST,
2032 "osrfMethodException",
2034 "No active transaction -- required for CREATE"
2040 // The following test is harmless but redundant. If a class is
2041 // readonly, we don't register a create method for it.
2042 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2043 osrfAppSessionStatus(
2045 OSRF_STATUS_BADREQUEST,
2046 "osrfMethodException",
2048 "Cannot INSERT readonly class"
2054 // Set the last_xact_id
2055 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2057 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2058 trans_id, target->classname, index);
2059 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
2062 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2064 dbhandle = writehandle;
2066 osrfHash* fields = osrfHashGet(meta, "fields");
2067 char* pkey = osrfHashGet(meta, "primarykey");
2068 char* seq = osrfHashGet(meta, "sequence");
2070 growing_buffer* table_buf = buffer_init(128);
2071 growing_buffer* col_buf = buffer_init(128);
2072 growing_buffer* val_buf = buffer_init(128);
2074 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
2075 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
2076 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2077 buffer_add(val_buf,"VALUES (");
2081 osrfHash* field = NULL;
2082 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2083 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2085 const char* field_name = osrfHashIteratorKey( field_itr );
2087 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2090 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2093 if (field_object && field_object->classname) {
2094 value = oilsFMGetString(
2096 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
2098 } else if( field_object && JSON_BOOL == field_object->type ) {
2099 if( jsonBoolIsTrue( field_object ) )
2100 value = strdup( "t" );
2102 value = strdup( "f" );
2104 value = jsonObjectToSimpleString( field_object );
2110 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2111 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2114 buffer_add(col_buf, field_name);
2116 if (!field_object || field_object->type == JSON_NULL) {
2117 buffer_add( val_buf, "DEFAULT" );
2119 } else if ( !strcmp(get_primitive( field ), "number") ) {
2120 const char* numtype = get_datatype( field );
2121 if ( !strcmp( numtype, "INT8") ) {
2122 buffer_fadd( val_buf, "%lld", atoll(value) );
2124 } else if ( !strcmp( numtype, "INT") ) {
2125 buffer_fadd( val_buf, "%d", atoi(value) );
2127 } else if ( !strcmp( numtype, "NUMERIC") ) {
2128 buffer_fadd( val_buf, "%f", atof(value) );
2131 if ( dbi_conn_quote_string(writehandle, &value) ) {
2132 OSRF_BUFFER_ADD( val_buf, value );
2135 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
2136 osrfAppSessionStatus(
2138 OSRF_STATUS_INTERNALSERVERERROR,
2139 "osrfMethodException",
2141 "Error quoting string -- please see the error log for more details"
2144 buffer_free(table_buf);
2145 buffer_free(col_buf);
2146 buffer_free(val_buf);
2156 osrfHashIteratorFree( field_itr );
2158 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2159 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2161 char* table_str = buffer_release(table_buf);
2162 char* col_str = buffer_release(col_buf);
2163 char* val_str = buffer_release(val_buf);
2164 growing_buffer* sql = buffer_init(128);
2165 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2170 char* query = buffer_release(sql);
2172 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
2175 dbi_result result = dbi_conn_query(writehandle, query);
2177 jsonObject* obj = NULL;
2180 obj = jsonNewObject(NULL);
2183 "%s ERROR inserting %s object using query [%s]",
2185 osrfHashGet(meta, "fieldmapper"),
2188 osrfAppSessionStatus(
2190 OSRF_STATUS_INTERNALSERVERERROR,
2191 "osrfMethodException",
2193 "INSERT error -- please see the error log for more details"
2198 char* id = oilsFMGetString(target, pkey);
2200 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
2201 growing_buffer* _id = buffer_init(10);
2202 buffer_fadd(_id, "%lld", new_id);
2203 id = buffer_release(_id);
2206 // Find quietness specification, if present
2207 const char* quiet_str = NULL;
2209 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2211 quiet_str = jsonObjectGetString( quiet_obj );
2214 if( str_is_true( quiet_str ) ) { // if quietness is specified
2215 obj = jsonNewObject(id);
2219 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2220 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
2222 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
2224 jsonObjectFree( where_clause );
2229 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
2232 jsonObjectFree( list );
2245 @brief Implement the retrieve method.
2246 @param ctx Pointer to the method context.
2247 @param err Pointer through which to return an error code.
2248 @return If successful, a pointer to the result to be returned to the client;
2251 From the method's class, fetch a row with a specified value in the primary key. This
2252 method relies on the database design convention that a primary key consists of a single
2256 - authkey (PCRUD only)
2257 - value of the primary key for the desired row, for building the WHERE clause
2258 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2260 Return to client: One row from the query.
2262 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
2267 if( enforce_pcrud ) {
2272 // Get the class metadata
2273 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2275 // Get the value of the primary key, from a method parameter
2276 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos);
2280 "%s retrieving %s object with primary key value of %s",
2282 osrfHashGet( class_def, "fieldmapper" ),
2283 jsonObjectGetString( id_obj )
2286 // Build a WHERE clause based on the key value
2287 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2290 osrfHashGet( class_def, "primarykey" ), // name of key column
2291 jsonObjectClone( id_obj ) // value of key column
2294 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
2297 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
2299 jsonObjectFree( where_clause );
2303 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2304 jsonObjectFree( list );
2306 if( enforce_pcrud ) {
2307 if(!verifyObjectPCRUD(ctx, obj)) {
2308 jsonObjectFree(obj);
2311 growing_buffer* msg = buffer_init(128);
2312 OSRF_BUFFER_ADD( msg, MODULENAME );
2313 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2315 char* m = buffer_release(msg);
2316 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2328 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
2329 growing_buffer* val_buf = buffer_init(32);
2330 const char* numtype = get_datatype( field );
2332 if ( !strncmp( numtype, "INT", 3 ) ) {
2333 if (value->type == JSON_NUMBER)
2334 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2335 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2337 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2340 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
2341 if (value->type == JSON_NUMBER)
2342 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2344 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2348 // Presumably this was really intended ot be a string, so quote it
2349 char* str = jsonObjectToSimpleString( value );
2350 if ( dbi_conn_quote_string(dbhandle, &str) ) {
2351 OSRF_BUFFER_ADD( val_buf, str );
2354 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
2356 buffer_free(val_buf);
2361 return buffer_release(val_buf);
2364 static char* searchINPredicate (const char* class_alias, osrfHash* field,
2365 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2366 growing_buffer* sql_buf = buffer_init(32);
2372 osrfHashGet(field, "name")
2376 buffer_add(sql_buf, "IN (");
2377 } else if (!(strcasecmp(op,"not in"))) {
2378 buffer_add(sql_buf, "NOT IN (");
2380 buffer_add(sql_buf, "IN (");
2383 if (node->type == JSON_HASH) {
2384 // subquery predicate
2385 char* subpred = buildQuery( ctx, node, SUBSELECT );
2387 buffer_free( sql_buf );
2391 buffer_add(sql_buf, subpred);
2394 } else if (node->type == JSON_ARRAY) {
2395 // literal value list
2396 int in_item_index = 0;
2397 int in_item_first = 1;
2398 const jsonObject* in_item;
2399 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
2404 buffer_add(sql_buf, ", ");
2407 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2408 osrfLogError( OSRF_LOG_MARK,
2409 "%s: Expected string or number within IN list; found %s",
2410 MODULENAME, json_type( in_item->type ) );
2411 buffer_free(sql_buf);
2415 // Append the literal value -- quoted if not a number
2416 if ( JSON_NUMBER == in_item->type ) {
2417 char* val = jsonNumberToDBString( field, in_item );
2418 OSRF_BUFFER_ADD( sql_buf, val );
2421 } else if ( !strcmp( get_primitive( field ), "number") ) {
2422 char* val = jsonNumberToDBString( field, in_item );
2423 OSRF_BUFFER_ADD( sql_buf, val );
2427 char* key_string = jsonObjectToSimpleString(in_item);
2428 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
2429 OSRF_BUFFER_ADD( sql_buf, key_string );
2432 osrfLogError(OSRF_LOG_MARK,
2433 "%s: Error quoting key string [%s]", MODULENAME, key_string);
2435 buffer_free(sql_buf);
2441 if( in_item_first ) {
2442 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
2443 buffer_free( sql_buf );
2447 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2448 MODULENAME, json_type( node->type ) );
2449 buffer_free(sql_buf);
2453 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2455 return buffer_release(sql_buf);
2458 // Receive a JSON_ARRAY representing a function call. The first
2459 // entry in the array is the function name. The rest are parameters.
2460 static char* searchValueTransform( const jsonObject* array ) {
2462 if( array->size < 1 ) {
2463 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
2467 // Get the function name
2468 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2469 if( func_item->type != JSON_STRING ) {
2470 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2471 MODULENAME, json_type( func_item->type ) );
2475 growing_buffer* sql_buf = buffer_init(32);
2477 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2478 OSRF_BUFFER_ADD( sql_buf, "( " );
2480 // Get the parameters
2481 int func_item_index = 1; // We already grabbed the zeroth entry
2482 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2484 // Add a separator comma, if we need one
2485 if( func_item_index > 2 )
2486 buffer_add( sql_buf, ", " );
2488 // Add the current parameter
2489 if (func_item->type == JSON_NULL) {
2490 buffer_add( sql_buf, "NULL" );
2492 char* val = jsonObjectToSimpleString(func_item);
2493 if ( dbi_conn_quote_string(dbhandle, &val) ) {
2494 OSRF_BUFFER_ADD( sql_buf, val );
2497 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2498 buffer_free(sql_buf);
2505 buffer_add( sql_buf, " )" );
2507 return buffer_release(sql_buf);
2510 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
2511 const jsonObject* node, const char* op) {
2513 if( ! is_good_operator( op ) ) {
2514 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2518 char* val = searchValueTransform(node);
2522 growing_buffer* sql_buf = buffer_init(32);
2527 osrfHashGet(field, "name"),
2534 return buffer_release(sql_buf);
2537 // class_alias is a class name or other table alias
2538 // field is a field definition as stored in the IDL
2539 // node comes from the method parameter, and may represent an entry in the SELECT list
2540 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
2541 growing_buffer* sql_buf = buffer_init(32);
2543 const char* field_transform = jsonObjectGetString(
2544 jsonObjectGetKeyConst( node, "transform" ) );
2545 const char* transform_subcolumn = jsonObjectGetString(
2546 jsonObjectGetKeyConst( node, "result_field" ) );
2548 if(transform_subcolumn) {
2549 if( ! is_identifier( transform_subcolumn ) ) {
2550 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2551 MODULENAME, transform_subcolumn );
2552 buffer_free( sql_buf );
2555 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2558 if (field_transform) {
2560 if( ! is_identifier( field_transform ) ) {
2561 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2562 MODULENAME, field_transform );
2563 buffer_free( sql_buf );
2567 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2568 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2569 field_transform, class_alias, osrfHashGet(field, "name"));
2571 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2572 field_transform, class_alias, osrfHashGet(field, "name"));
2575 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2578 if( array->type != JSON_ARRAY ) {
2579 osrfLogError( OSRF_LOG_MARK,
2580 "%s: Expected JSON_ARRAY for function params; found %s",
2581 MODULENAME, json_type( array->type ) );
2582 buffer_free( sql_buf );
2585 int func_item_index = 0;
2586 jsonObject* func_item;
2587 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2589 char* val = jsonObjectToSimpleString(func_item);
2592 buffer_add( sql_buf, ",NULL" );
2593 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
2594 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2595 OSRF_BUFFER_ADD( sql_buf, val );
2597 osrfLogError( OSRF_LOG_MARK,
2598 "%s: Error quoting key string [%s]", MODULENAME, val);
2600 buffer_free(sql_buf);
2607 buffer_add( sql_buf, " )" );
2610 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2613 if (transform_subcolumn)
2614 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2616 return buffer_release(sql_buf);
2619 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2620 const jsonObject* node, const char* op ) {
2622 if( ! is_good_operator( op ) ) {
2623 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2627 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2628 if( ! field_transform )
2631 int extra_parens = 0; // boolean
2633 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2634 if ( ! value_obj ) {
2635 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2637 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2639 free(field_transform);
2643 } else if ( value_obj->type == JSON_ARRAY ) {
2644 value = searchValueTransform( value_obj );
2646 osrfLogError(OSRF_LOG_MARK,
2647 "%s: Error building value transform for field transform", MODULENAME);
2648 free( field_transform );
2651 } else if ( value_obj->type == JSON_HASH ) {
2652 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2654 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2656 free(field_transform);
2660 } else if ( value_obj->type == JSON_NUMBER ) {
2661 value = jsonNumberToDBString( field, value_obj );
2662 } else if ( value_obj->type == JSON_NULL ) {
2663 osrfLogError( OSRF_LOG_MARK,
2664 "%s: Error building predicate for field transform: null value", MODULENAME );
2665 free(field_transform);
2667 } else if ( value_obj->type == JSON_BOOL ) {
2668 osrfLogError( OSRF_LOG_MARK,
2669 "%s: Error building predicate for field transform: boolean value", MODULENAME );
2670 free(field_transform);
2673 if ( !strcmp( get_primitive( field ), "number") ) {
2674 value = jsonNumberToDBString( field, value_obj );
2676 value = jsonObjectToSimpleString( value_obj );
2677 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2678 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2681 free(field_transform);
2687 const char* left_parens = "";
2688 const char* right_parens = "";
2690 if( extra_parens ) {
2695 growing_buffer* sql_buf = buffer_init(32);
2699 "%s%s %s %s %s %s%s",
2710 free(field_transform);
2712 return buffer_release(sql_buf);
2715 static char* searchSimplePredicate (const char* op, const char* class_alias,
2716 osrfHash* field, const jsonObject* node) {
2718 if( ! is_good_operator( op ) ) {
2719 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2725 // Get the value to which we are comparing the specified column
2726 if (node->type != JSON_NULL) {
2727 if ( node->type == JSON_NUMBER ) {
2728 val = jsonNumberToDBString( field, node );
2729 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2730 val = jsonNumberToDBString( field, node );
2732 val = jsonObjectToSimpleString(node);
2737 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2738 // Value is not numeric; enclose it in quotes
2739 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2740 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2747 // Compare to a null value
2748 val = strdup( "NULL" );
2749 if (strcmp( op, "=" ))
2755 growing_buffer* sql_buf = buffer_init(32);
2756 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2757 char* pred = buffer_release( sql_buf );
2764 static char* searchBETWEENPredicate (const char* class_alias,
2765 osrfHash* field, const jsonObject* node) {
2767 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2768 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2770 if( NULL == y_node ) {
2771 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2774 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2775 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2782 if ( !strcmp( get_primitive( field ), "number") ) {
2783 x_string = jsonNumberToDBString(field, x_node);
2784 y_string = jsonNumberToDBString(field, y_node);
2787 x_string = jsonObjectToSimpleString(x_node);
2788 y_string = jsonObjectToSimpleString(y_node);
2789 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2790 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2791 MODULENAME, x_string, y_string);
2798 growing_buffer* sql_buf = buffer_init(32);
2799 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2800 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2804 return buffer_release(sql_buf);
2807 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2808 jsonObject* node, osrfMethodContext* ctx ) {
2811 if (node->type == JSON_ARRAY) { // equality IN search
2812 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2813 } else if (node->type == JSON_HASH) { // other search
2814 jsonIterator* pred_itr = jsonNewIterator( node );
2815 if( !jsonIteratorHasNext( pred_itr ) ) {
2816 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2817 MODULENAME, osrfHashGet(field, "name") );
2819 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2821 // Verify that there are no additional predicates
2822 if( jsonIteratorHasNext( pred_itr ) ) {
2823 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2824 MODULENAME, osrfHashGet(field, "name") );
2825 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2826 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2827 else if ( !(strcasecmp( pred_itr->key,"in" ))
2828 || !(strcasecmp( pred_itr->key,"not in" )) )
2829 pred = searchINPredicate(
2830 class_info->alias, field, pred_node, pred_itr->key, ctx );
2831 else if ( pred_node->type == JSON_ARRAY )
2832 pred = searchFunctionPredicate(
2833 class_info->alias, field, pred_node, pred_itr->key );
2834 else if ( pred_node->type == JSON_HASH )
2835 pred = searchFieldTransformPredicate(
2836 class_info, field, pred_node, pred_itr->key );
2838 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2840 jsonIteratorFree(pred_itr);
2842 } else if (node->type == JSON_NULL) { // IS NULL search
2843 growing_buffer* _p = buffer_init(64);
2846 "\"%s\".%s IS NULL",
2847 class_info->class_name,
2848 osrfHashGet(field, "name")
2850 pred = buffer_release(_p);
2851 } else { // equality search
2852 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2871 field : call_number,
2887 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2889 const jsonObject* working_hash;
2890 jsonObject* freeable_hash = NULL;
2892 if (join_hash->type == JSON_HASH) {
2893 working_hash = join_hash;
2894 } else if (join_hash->type == JSON_STRING) {
2895 // turn it into a JSON_HASH by creating a wrapper
2896 // around a copy of the original
2897 const char* _tmp = jsonObjectGetString( join_hash );
2898 freeable_hash = jsonNewObjectType(JSON_HASH);
2899 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2900 working_hash = freeable_hash;
2904 "%s: JOIN failed; expected JSON object type not found",
2910 growing_buffer* join_buf = buffer_init(128);
2911 const char* leftclass = left_info->class_name;
2913 jsonObject* snode = NULL;
2914 jsonIterator* search_itr = jsonNewIterator( working_hash );
2916 while ( (snode = jsonIteratorNext( search_itr )) ) {
2917 const char* right_alias = search_itr->key;
2919 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2921 class = right_alias;
2923 const ClassInfo* right_info = add_joined_class( right_alias, class );
2927 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2931 jsonIteratorFree( search_itr );
2932 buffer_free( join_buf );
2934 jsonObjectFree( freeable_hash );
2937 osrfHash* links = right_info->links;
2938 const char* table = right_info->source_def;
2940 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2941 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2943 if (field && !fkey) {
2944 // Look up the corresponding join column in the IDL.
2945 // The link must be defined in the child table,
2946 // and point to the right parent table.
2947 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2948 const char* reltype = NULL;
2949 const char* other_class = NULL;
2950 reltype = osrfHashGet( idl_link, "reltype" );
2951 if( reltype && strcmp( reltype, "has_many" ) )
2952 other_class = osrfHashGet( idl_link, "class" );
2953 if( other_class && !strcmp( other_class, leftclass ) )
2954 fkey = osrfHashGet( idl_link, "key" );
2958 "%s: JOIN failed. No link defined from %s.%s to %s",
2964 buffer_free(join_buf);
2966 jsonObjectFree(freeable_hash);
2967 jsonIteratorFree(search_itr);
2971 } else if (!field && fkey) {
2972 // Look up the corresponding join column in the IDL.
2973 // The link must be defined in the child table,
2974 // and point to the right parent table.
2975 osrfHash* left_links = left_info->links;
2976 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2977 const char* reltype = NULL;
2978 const char* other_class = NULL;
2979 reltype = osrfHashGet( idl_link, "reltype" );
2980 if( reltype && strcmp( reltype, "has_many" ) )
2981 other_class = osrfHashGet( idl_link, "class" );
2982 if( other_class && !strcmp( other_class, class ) )
2983 field = osrfHashGet( idl_link, "key" );
2987 "%s: JOIN failed. No link defined from %s.%s to %s",
2993 buffer_free(join_buf);
2995 jsonObjectFree(freeable_hash);
2996 jsonIteratorFree(search_itr);
3000 } else if (!field && !fkey) {
3001 osrfHash* left_links = left_info->links;
3003 // For each link defined for the left class:
3004 // see if the link references the joined class
3005 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3006 osrfHash* curr_link = NULL;
3007 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3008 const char* other_class = osrfHashGet( curr_link, "class" );
3009 if( other_class && !strcmp( other_class, class ) ) {
3011 // In the IDL, the parent class doesn't always know then names of the child
3012 // columns that are pointing to it, so don't use that end of the link
3013 const char* reltype = osrfHashGet( curr_link, "reltype" );
3014 if( reltype && strcmp( reltype, "has_many" ) ) {
3015 // Found a link between the classes
3016 fkey = osrfHashIteratorKey( itr );
3017 field = osrfHashGet( curr_link, "key" );
3022 osrfHashIteratorFree( itr );
3024 if (!field || !fkey) {
3025 // Do another such search, with the classes reversed
3027 // For each link defined for the joined class:
3028 // see if the link references the left class
3029 osrfHashIterator* itr = osrfNewHashIterator( links );
3030 osrfHash* curr_link = NULL;
3031 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3032 const char* other_class = osrfHashGet( curr_link, "class" );
3033 if( other_class && !strcmp( other_class, leftclass ) ) {
3035 // In the IDL, the parent class doesn't know then names of the child
3036 // columns that are pointing to it, so don't use that end of the link
3037 const char* reltype = osrfHashGet( curr_link, "reltype" );
3038 if( reltype && strcmp( reltype, "has_many" ) ) {
3039 // Found a link between the classes
3040 field = osrfHashIteratorKey( itr );
3041 fkey = osrfHashGet( curr_link, "key" );
3046 osrfHashIteratorFree( itr );
3049 if (!field || !fkey) {
3052 "%s: JOIN failed. No link defined between %s and %s",
3057 buffer_free(join_buf);
3059 jsonObjectFree(freeable_hash);
3060 jsonIteratorFree(search_itr);
3065 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3067 if ( !strcasecmp(type,"left") ) {
3068 buffer_add(join_buf, " LEFT JOIN");
3069 } else if ( !strcasecmp(type,"right") ) {
3070 buffer_add(join_buf, " RIGHT JOIN");
3071 } else if ( !strcasecmp(type,"full") ) {
3072 buffer_add(join_buf, " FULL JOIN");
3074 buffer_add(join_buf, " INNER JOIN");
3077 buffer_add(join_buf, " INNER JOIN");
3080 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3081 table, right_alias, right_alias, field, left_info->alias, fkey);
3083 // Add any other join conditions as specified by "filter"
3084 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3086 const char* filter_op = jsonObjectGetString(
3087 jsonObjectGetKeyConst( snode, "filter_op" ) );
3088 if ( filter_op && !strcasecmp("or",filter_op) ) {
3089 buffer_add( join_buf, " OR " );
3091 buffer_add( join_buf, " AND " );
3094 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3096 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3097 OSRF_BUFFER_ADD( join_buf, jpred );
3102 "%s: JOIN failed. Invalid conditional expression.",
3105 jsonIteratorFree( search_itr );
3106 buffer_free( join_buf );
3108 jsonObjectFree( freeable_hash );
3113 buffer_add(join_buf, " ) ");
3115 // Recursively add a nested join, if one is present
3116 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3118 char* jpred = searchJOIN( join_filter, right_info );
3120 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3121 OSRF_BUFFER_ADD( join_buf, jpred );
3124 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
3125 jsonIteratorFree( search_itr );
3126 buffer_free( join_buf );
3128 jsonObjectFree( freeable_hash );
3135 jsonObjectFree(freeable_hash);
3136 jsonIteratorFree(search_itr);
3138 return buffer_release(join_buf);
3143 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3144 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3145 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3147 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3149 search_hash is the JSON expression of the conditions.
3150 meta is the class definition from the IDL, for the relevant table.
3151 opjoin_type indicates whether multiple conditions, if present, should be
3152 connected by AND or OR.
3153 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3154 to pass it to other functions -- and all they do with it is to use the session
3155 and request members to send error messages back to the client.
3159 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
3160 int opjoin_type, osrfMethodContext* ctx ) {
3164 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3165 "opjoin_type = %d, ctx addr = %p",
3168 class_info->class_def,
3173 growing_buffer* sql_buf = buffer_init(128);
3175 jsonObject* node = NULL;
3178 if ( search_hash->type == JSON_ARRAY ) {
3179 osrfLogDebug( OSRF_LOG_MARK,
3180 "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME );
3181 if( 0 == search_hash->size ) {
3184 "%s: Invalid predicate structure: empty JSON array",
3187 buffer_free( sql_buf );
3191 unsigned long i = 0;
3192 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
3196 if (opjoin_type == OR_OP_JOIN)
3197 buffer_add(sql_buf, " OR ");
3199 buffer_add(sql_buf, " AND ");
3202 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3204 buffer_free( sql_buf );
3208 buffer_fadd(sql_buf, "( %s )", subpred);
3212 } else if ( search_hash->type == JSON_HASH ) {
3213 osrfLogDebug( OSRF_LOG_MARK,
3214 "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME );
3215 jsonIterator* search_itr = jsonNewIterator( search_hash );
3216 if( !jsonIteratorHasNext( search_itr ) ) {
3219 "%s: Invalid predicate structure: empty JSON object",
3222 jsonIteratorFree( search_itr );
3223 buffer_free( sql_buf );
3227 while ( (node = jsonIteratorNext( search_itr )) ) {
3232 if (opjoin_type == OR_OP_JOIN)
3233 buffer_add(sql_buf, " OR ");
3235 buffer_add(sql_buf, " AND ");
3238 if ( '+' == search_itr->key[ 0 ] ) {
3240 // This plus sign prefixes a class name or other table alias;
3241 // make sure the table alias is in scope
3242 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3243 if( ! alias_info ) {
3246 "%s: Invalid table alias \"%s\" in WHERE clause",
3250 jsonIteratorFree( search_itr );
3251 buffer_free( sql_buf );
3255 if ( node->type == JSON_STRING ) {
3256 // It's the name of a column; make sure it belongs to the class
3257 const char* fieldname = jsonObjectGetString( node );
3258 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3261 "%s: Invalid column name \"%s\" in WHERE clause "
3262 "for table alias \"%s\"",
3267 jsonIteratorFree( search_itr );
3268 buffer_free( sql_buf );
3272 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3274 // It's something more complicated
3275 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3277 jsonIteratorFree( search_itr );
3278 buffer_free( sql_buf );
3282 buffer_fadd(sql_buf, "( %s )", subpred);
3285 } else if ( '-' == search_itr->key[ 0 ] ) {
3286 if ( !strcasecmp("-or",search_itr->key) ) {
3287 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3289 jsonIteratorFree( search_itr );
3290 buffer_free( sql_buf );
3294 buffer_fadd(sql_buf, "( %s )", subpred);
3296 } else if ( !strcasecmp("-and",search_itr->key) ) {
3297 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3299 jsonIteratorFree( search_itr );
3300 buffer_free( sql_buf );
3304 buffer_fadd(sql_buf, "( %s )", subpred);
3306 } else if ( !strcasecmp("-not",search_itr->key) ) {
3307 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3309 jsonIteratorFree( search_itr );
3310 buffer_free( sql_buf );
3314 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
3316 } else if ( !strcasecmp("-exists",search_itr->key) ) {
3317 char* subpred = buildQuery( ctx, node, SUBSELECT );
3319 jsonIteratorFree( search_itr );
3320 buffer_free( sql_buf );
3324 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
3326 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
3327 char* subpred = buildQuery( ctx, node, SUBSELECT );
3329 jsonIteratorFree( search_itr );
3330 buffer_free( sql_buf );
3334 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
3336 } else { // Invalid "minus" operator
3339 "%s: Invalid operator \"%s\" in WHERE clause",
3343 jsonIteratorFree( search_itr );
3344 buffer_free( sql_buf );
3350 const char* class = class_info->class_name;
3351 osrfHash* fields = class_info->fields;
3352 osrfHash* field = osrfHashGet( fields, search_itr->key );
3355 const char* table = class_info->source_def;
3358 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3361 table ? table : "?",
3364 jsonIteratorFree(search_itr);
3365 buffer_free(sql_buf);
3369 char* subpred = searchPredicate( class_info, field, node, ctx );
3371 buffer_free(sql_buf);
3372 jsonIteratorFree(search_itr);
3376 buffer_add( sql_buf, subpred );
3380 jsonIteratorFree(search_itr);
3383 // ERROR ... only hash and array allowed at this level
3384 char* predicate_string = jsonObjectToJSON( search_hash );
3387 "%s: Invalid predicate structure: %s",
3391 buffer_free(sql_buf);
3392 free(predicate_string);
3396 return buffer_release(sql_buf);
3399 /* Build a JSON_ARRAY of field names for a given table alias
3401 static jsonObject* defaultSelectList( const char* table_alias ) {
3406 ClassInfo* class_info = search_all_alias( table_alias );
3407 if( ! class_info ) {
3410 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3417 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3418 osrfHash* field_def = NULL;
3419 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3420 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3421 const char* field_name = osrfHashIteratorKey( field_itr );
3422 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3423 jsonObjectPush( array, jsonNewObject( field_name ) );
3426 osrfHashIteratorFree( field_itr );
3431 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3432 // The jsonObject must be a JSON_HASH with an single entry for "union",
3433 // "intersect", or "except". The data associated with this key must be an
3434 // array of hashes, each hash being a query.
3435 // Also allowed but currently ignored: entries for "order_by" and "alias".
3436 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3438 if( ! combo || combo->type != JSON_HASH )
3439 return NULL; // should be impossible; validated by caller
3441 const jsonObject* query_array = NULL; // array of subordinate queries
3442 const char* op = NULL; // name of operator, e.g. UNION
3443 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3444 int op_count = 0; // for detecting conflicting operators
3445 int excepting = 0; // boolean
3446 int all = 0; // boolean
3447 jsonObject* order_obj = NULL;
3449 // Identify the elements in the hash
3450 jsonIterator* query_itr = jsonNewIterator( combo );
3451 jsonObject* curr_obj = NULL;
3452 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3453 if( ! strcmp( "union", query_itr->key ) ) {
3456 query_array = curr_obj;
3457 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3460 query_array = curr_obj;
3461 } else if( ! strcmp( "except", query_itr->key ) ) {
3465 query_array = curr_obj;
3466 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3469 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3472 order_obj = curr_obj;
3473 } else if( ! strcmp( "alias", query_itr->key ) ) {
3474 if( curr_obj->type != JSON_STRING ) {
3475 jsonIteratorFree( query_itr );
3478 alias = jsonObjectGetString( curr_obj );
3479 } else if( ! strcmp( "all", query_itr->key ) ) {
3480 if( obj_is_true( curr_obj ) )
3484 osrfAppSessionStatus(
3486 OSRF_STATUS_INTERNALSERVERERROR,
3487 "osrfMethodException",
3489 "Malformed query; unexpected entry in query object"
3493 "%s: Unexpected entry for \"%s\" in%squery",
3498 jsonIteratorFree( query_itr );
3502 jsonIteratorFree( query_itr );
3504 // More sanity checks
3505 if( ! query_array ) {
3507 osrfAppSessionStatus(
3509 OSRF_STATUS_INTERNALSERVERERROR,
3510 "osrfMethodException",
3512 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3516 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3519 return NULL; // should be impossible...
3520 } else if( op_count > 1 ) {
3522 osrfAppSessionStatus(
3524 OSRF_STATUS_INTERNALSERVERERROR,
3525 "osrfMethodException",
3527 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3531 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3535 } if( query_array->type != JSON_ARRAY ) {
3537 osrfAppSessionStatus(
3539 OSRF_STATUS_INTERNALSERVERERROR,
3540 "osrfMethodException",
3542 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3546 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3549 json_type( query_array->type )
3552 } if( query_array->size < 2 ) {
3554 osrfAppSessionStatus(
3556 OSRF_STATUS_INTERNALSERVERERROR,
3557 "osrfMethodException",
3559 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3563 "%s:%srequires multiple queries as operands",
3568 } else if( excepting && query_array->size > 2 ) {
3570 osrfAppSessionStatus(
3572 OSRF_STATUS_INTERNALSERVERERROR,
3573 "osrfMethodException",
3575 "EXCEPT operator has too many queries as operands"
3579 "%s:EXCEPT operator has too many queries as operands",
3583 } else if( order_obj && ! alias ) {
3585 osrfAppSessionStatus(
3587 OSRF_STATUS_INTERNALSERVERERROR,
3588 "osrfMethodException",
3590 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3594 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3600 // So far so good. Now build the SQL.
3601 growing_buffer* sql = buffer_init( 256 );
3603 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3604 // Add a layer of parentheses
3605 if( flags & SUBCOMBO )
3606 OSRF_BUFFER_ADD( sql, "( " );
3608 // Traverse the query array. Each entry should be a hash.
3609 int first = 1; // boolean
3611 jsonObject* query = NULL;
3612 while((query = jsonObjectGetIndex( query_array, i++ ) )) {
3613 if( query->type != JSON_HASH ) {
3615 osrfAppSessionStatus(
3617 OSRF_STATUS_INTERNALSERVERERROR,
3618 "osrfMethodException",
3620 "Malformed query under UNION, INTERSECT or EXCEPT"
3624 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3627 json_type( query->type )
3636 OSRF_BUFFER_ADD( sql, op );
3638 OSRF_BUFFER_ADD( sql, "ALL " );
3641 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3645 "%s: Error building query under%s",
3653 OSRF_BUFFER_ADD( sql, query_str );
3656 if( flags & SUBCOMBO )
3657 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3659 if ( !(flags & SUBSELECT) )
3660 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3662 return buffer_release( sql );
3665 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3666 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3667 // or "except" to indicate the type of query.
3668 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3672 osrfAppSessionStatus(
3674 OSRF_STATUS_INTERNALSERVERERROR,
3675 "osrfMethodException",
3677 "Malformed query; no query object"
3679 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", MODULENAME );
3681 } else if( query->type != JSON_HASH ) {
3683 osrfAppSessionStatus(
3685 OSRF_STATUS_INTERNALSERVERERROR,
3686 "osrfMethodException",
3688 "Malformed query object"
3692 "%s: Query object is %s instead of JSON_HASH",
3694 json_type( query->type )
3699 // Determine what kind of query it purports to be, and dispatch accordingly.
3700 if( jsonObjectGetKey( query, "union" ) ||
3701 jsonObjectGetKey( query, "intersect" ) ||
3702 jsonObjectGetKey( query, "except" ) ) {
3703 return doCombo( ctx, query, flags );
3705 // It is presumably a SELECT query
3707 // Push a node onto the stack for the current query. Every level of
3708 // subquery gets its own QueryFrame on the Stack.
3711 // Build an SQL SELECT statement
3714 jsonObjectGetKey( query, "select" ),
3715 jsonObjectGetKey( query, "from" ),
3716 jsonObjectGetKey( query, "where" ),
3717 jsonObjectGetKey( query, "having" ),
3718 jsonObjectGetKey( query, "order_by" ),
3719 jsonObjectGetKey( query, "limit" ),
3720 jsonObjectGetKey( query, "offset" ),
3729 /* method context */ osrfMethodContext* ctx,
3731 /* SELECT */ jsonObject* selhash,
3732 /* FROM */ jsonObject* join_hash,
3733 /* WHERE */ jsonObject* search_hash,
3734 /* HAVING */ jsonObject* having_hash,
3735 /* ORDER BY */ jsonObject* order_hash,
3736 /* LIMIT */ jsonObject* limit,
3737 /* OFFSET */ jsonObject* offset,
3738 /* flags */ int flags
3740 const char* locale = osrf_message_get_last_locale();
3742 // general tmp objects
3743 const jsonObject* tmp_const;
3744 jsonObject* selclass = NULL;
3745 jsonObject* snode = NULL;
3746 jsonObject* onode = NULL;
3748 char* string = NULL;
3749 int from_function = 0;
3754 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3756 // punt if there's no FROM clause
3757 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3760 "%s: FROM clause is missing or empty",
3764 osrfAppSessionStatus(
3766 OSRF_STATUS_INTERNALSERVERERROR,
3767 "osrfMethodException",
3769 "FROM clause is missing or empty in JSON query"
3774 // the core search class
3775 const char* core_class = NULL;
3777 // get the core class -- the only key of the top level FROM clause, or a string
3778 if (join_hash->type == JSON_HASH) {
3779 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3780 snode = jsonIteratorNext( tmp_itr );
3782 // Populate the current QueryFrame with information
3783 // about the core class
3784 if( add_query_core( NULL, tmp_itr->key ) ) {
3786 osrfAppSessionStatus(
3788 OSRF_STATUS_INTERNALSERVERERROR,
3789 "osrfMethodException",
3791 "Unable to look up core class"
3795 core_class = curr_query->core.class_name;
3798 jsonObject* extra = jsonIteratorNext( tmp_itr );
3800 jsonIteratorFree( tmp_itr );
3803 // There shouldn't be more than one entry in join_hash
3807 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3811 osrfAppSessionStatus(
3813 OSRF_STATUS_INTERNALSERVERERROR,
3814 "osrfMethodException",
3816 "Malformed FROM clause in JSON query"
3818 return NULL; // Malformed join_hash; extra entry
3820 } else if (join_hash->type == JSON_ARRAY) {
3821 // We're selecting from a function, not from a table
3823 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
3826 } else if (join_hash->type == JSON_STRING) {
3827 // Populate the current QueryFrame with information
3828 // about the core class
3829 core_class = jsonObjectGetString( join_hash );
3831 if( add_query_core( NULL, core_class ) ) {
3833 osrfAppSessionStatus(
3835 OSRF_STATUS_INTERNALSERVERERROR,
3836 "osrfMethodException",
3838 "Unable to look up core class"
3846 "%s: FROM clause is unexpected JSON type: %s",
3848 json_type( join_hash->type )
3851 osrfAppSessionStatus(
3853 OSRF_STATUS_INTERNALSERVERERROR,
3854 "osrfMethodException",
3856 "Ill-formed FROM clause in JSON query"
3861 // Build the join clause, if any, while filling out the list
3862 // of joined classes in the current QueryFrame.
3863 char* join_clause = NULL;
3864 if( join_hash && ! from_function ) {
3866 join_clause = searchJOIN( join_hash, &curr_query->core );
3867 if( ! join_clause ) {
3869 osrfAppSessionStatus(
3871 OSRF_STATUS_INTERNALSERVERERROR,
3872 "osrfMethodException",
3874 "Unable to construct JOIN clause(s)"
3880 // For in case we don't get a select list
3881 jsonObject* defaultselhash = NULL;
3883 // if there is no select list, build a default select list ...
3884 if (!selhash && !from_function) {
3885 jsonObject* default_list = defaultSelectList( core_class );
3886 if( ! default_list ) {
3888 osrfAppSessionStatus(
3890 OSRF_STATUS_INTERNALSERVERERROR,
3891 "osrfMethodException",
3893 "Unable to build default SELECT clause in JSON query"
3895 free( join_clause );
3900 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3901 jsonObjectSetKey( selhash, core_class, default_list );
3904 // The SELECT clause can be encoded only by a hash
3905 if( !from_function && selhash->type != JSON_HASH ) {
3908 "%s: Expected JSON_HASH for SELECT clause; found %s",
3910 json_type( selhash->type )
3914 osrfAppSessionStatus(
3916 OSRF_STATUS_INTERNALSERVERERROR,
3917 "osrfMethodException",
3919 "Malformed SELECT clause in JSON query"
3921 free( join_clause );
3925 // If you see a null or wild card specifier for the core class, or an
3926 // empty array, replace it with a default SELECT list
3927 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3929 int default_needed = 0; // boolean
3930 if( JSON_STRING == tmp_const->type
3931 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3933 else if( JSON_NULL == tmp_const->type )
3936 if( default_needed ) {
3937 // Build a default SELECT list
3938 jsonObject* default_list = defaultSelectList( core_class );
3939 if( ! default_list ) {
3941 osrfAppSessionStatus(
3943 OSRF_STATUS_INTERNALSERVERERROR,
3944 "osrfMethodException",
3946 "Can't build default SELECT clause in JSON query"
3948 free( join_clause );
3953 jsonObjectSetKey( selhash, core_class, default_list );
3957 // temp buffers for the SELECT list and GROUP BY clause
3958 growing_buffer* select_buf = buffer_init(128);
3959 growing_buffer* group_buf = buffer_init(128);
3961 int aggregate_found = 0; // boolean
3963 // Build a select list
3964 if(from_function) // From a function we select everything
3965 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3968 // Build the SELECT list as SQL
3972 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3973 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3975 const char* cname = selclass_itr->key;
3977 // Make sure the target relation is in the FROM clause.
3979 // At this point join_hash is a step down from the join_hash we
3980 // received as a parameter. If the original was a JSON_STRING,
3981 // then json_hash is now NULL. If the original was a JSON_HASH,
3982 // then json_hash is now the first (and only) entry in it,
3983 // denoting the core class. We've already excluded the
3984 // possibility that the original was a JSON_ARRAY, because in
3985 // that case from_function would be non-NULL, and we wouldn't
3988 // If the current table alias isn't in scope, bail out
3989 ClassInfo* class_info = search_alias( cname );
3990 if( ! class_info ) {
3993 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3998 osrfAppSessionStatus(
4000 OSRF_STATUS_INTERNALSERVERERROR,
4001 "osrfMethodException",
4003 "Selected class not in FROM clause in JSON query"
4005 jsonIteratorFree( selclass_itr );
4006 buffer_free( select_buf );
4007 buffer_free( group_buf );
4008 if( defaultselhash )
4009 jsonObjectFree( defaultselhash );
4010 free( join_clause );
4014 if( selclass->type != JSON_ARRAY ) {
4017 "%s: Malformed SELECT list for class \"%s\"; not an array",
4022 osrfAppSessionStatus(
4024 OSRF_STATUS_INTERNALSERVERERROR,
4025 "osrfMethodException",
4027 "Selected class not in FROM clause in JSON query"
4030 jsonIteratorFree( selclass_itr );
4031 buffer_free( select_buf );
4032 buffer_free( group_buf );
4033 if( defaultselhash )
4034 jsonObjectFree( defaultselhash );
4035 free( join_clause );
4039 // Look up some attributes of the current class
4040 osrfHash* idlClass = class_info->class_def;
4041 osrfHash* class_field_set = class_info->fields;
4042 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4043 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4045 if( 0 == selclass->size ) {
4048 "%s: No columns selected from \"%s\"",
4054 // stitch together the column list for the current table alias...
4055 unsigned long field_idx = 0;
4056 jsonObject* selfield = NULL;
4057 while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4059 // If we need a separator comma, add one
4063 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4066 // if the field specification is a string, add it to the list
4067 if (selfield->type == JSON_STRING) {
4069 // Look up the field in the IDL
4070 const char* col_name = jsonObjectGetString( selfield );
4071 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4073 // No such field in current class
4076 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4082 osrfAppSessionStatus(
4084 OSRF_STATUS_INTERNALSERVERERROR,
4085 "osrfMethodException",
4087 "Selected column not defined in JSON query"
4089 jsonIteratorFree( selclass_itr );
4090 buffer_free( select_buf );
4091 buffer_free( group_buf );
4092 if( defaultselhash )
4093 jsonObjectFree( defaultselhash );
4094 free( join_clause );
4096 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4097 // Virtual field not allowed
4100 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4106 osrfAppSessionStatus(
4108 OSRF_STATUS_INTERNALSERVERERROR,
4109 "osrfMethodException",
4111 "Selected column may not be virtual in JSON query"
4113 jsonIteratorFree( selclass_itr );
4114 buffer_free( select_buf );
4115 buffer_free( group_buf );
4116 if( defaultselhash )
4117 jsonObjectFree( defaultselhash );
4118 free( join_clause );
4124 if (flags & DISABLE_I18N)
4127 i18n = osrfHashGet(field_def, "i18n");
4129 if( str_is_true( i18n ) ) {
4130 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4131 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4132 class_tname, cname, col_name, class_pkey,
4133 cname, class_pkey, locale, col_name );
4135 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"",
4136 cname, col_name, col_name );
4139 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"",
4140 cname, col_name, col_name );
4143 // ... but it could be an object, in which case we check for a Field Transform
4144 } else if (selfield->type == JSON_HASH) {
4146 const char* col_name = jsonObjectGetString(
4147 jsonObjectGetKeyConst( selfield, "column" ) );
4149 // Get the field definition from the IDL
4150 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4152 // No such field in current class
4155 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4161 osrfAppSessionStatus(
4163 OSRF_STATUS_INTERNALSERVERERROR,
4164 "osrfMethodException",
4166 "Selected column is not defined in JSON query"
4168 jsonIteratorFree( selclass_itr );
4169 buffer_free( select_buf );
4170 buffer_free( group_buf );
4171 if( defaultselhash )
4172 jsonObjectFree( defaultselhash );
4173 free( join_clause );
4175 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4176 // No such field in current class
4179 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4185 osrfAppSessionStatus(
4187 OSRF_STATUS_INTERNALSERVERERROR,
4188 "osrfMethodException",
4190 "Selected column is virtual in JSON query"
4192 jsonIteratorFree( selclass_itr );
4193 buffer_free( select_buf );
4194 buffer_free( group_buf );
4195 if( defaultselhash )
4196 jsonObjectFree( defaultselhash );
4197 free( join_clause );
4201 // Decide what to use as a column alias
4203 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4204 _alias = jsonObjectGetString( tmp_const );
4205 } else { // Use field name as the alias
4209 if (jsonObjectGetKeyConst( selfield, "transform" )) {
4210 char* transform_str = searchFieldTransform(
4211 class_info->alias, field_def, selfield );
4212 if( transform_str ) {
4213 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
4214 free(transform_str);
4217 osrfAppSessionStatus(
4219 OSRF_STATUS_INTERNALSERVERERROR,
4220 "osrfMethodException",
4222 "Unable to generate transform function in JSON query"
4224 jsonIteratorFree( selclass_itr );
4225 buffer_free( select_buf );
4226 buffer_free( group_buf );
4227 if( defaultselhash )
4228 jsonObjectFree( defaultselhash );
4229 free( join_clause );
4236 if (flags & DISABLE_I18N)
4239 i18n = osrfHashGet(field_def, "i18n");
4241 if( str_is_true( i18n ) ) {
4242 buffer_fadd( select_buf,
4243 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4244 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4245 class_tname, cname, col_name, class_pkey, cname,
4246 class_pkey, locale, _alias);
4248 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4249 cname, col_name, _alias );
4252 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4253 cname, col_name, _alias);
4260 "%s: Selected item is unexpected JSON type: %s",
4262 json_type( selfield->type )
4265 osrfAppSessionStatus(
4267 OSRF_STATUS_INTERNALSERVERERROR,
4268 "osrfMethodException",
4270 "Ill-formed SELECT item in JSON query"
4272 jsonIteratorFree( selclass_itr );
4273 buffer_free( select_buf );
4274 buffer_free( group_buf );
4275 if( defaultselhash )
4276 jsonObjectFree( defaultselhash );
4277 free( join_clause );
4281 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
4282 if( obj_is_true( agg_obj ) )
4283 aggregate_found = 1;
4285 // Append a comma (except for the first one)
4286 // and add the column to a GROUP BY clause
4290 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4292 buffer_fadd(group_buf, " %d", sel_pos);
4296 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4298 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4299 if ( ! obj_is_true( aggregate_obj ) ) {
4303 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4306 buffer_fadd(group_buf, " %d", sel_pos);
4309 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4313 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4316 _column = searchFieldTransform(class_info->alias, field, selfield);
4317 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4318 OSRF_BUFFER_ADD(group_buf, _column);
4319 _column = searchFieldTransform(class_info->alias, field, selfield);
4326 } // end while -- iterating across SELECT columns
4328 } // end while -- iterating across classes
4330 jsonIteratorFree(selclass_itr);
4334 char* col_list = buffer_release(select_buf);
4336 // Make sure the SELECT list isn't empty. This can happen, for example,
4337 // if we try to build a default SELECT clause from a non-core table.
4340 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
4342 osrfAppSessionStatus(
4344 OSRF_STATUS_INTERNALSERVERERROR,
4345 "osrfMethodException",
4347 "SELECT list is empty"
4350 buffer_free( group_buf );
4351 if( defaultselhash )
4352 jsonObjectFree( defaultselhash );
4353 free( join_clause );
4358 if (from_function) table = searchValueTransform(join_hash);
4359 else table = strdup( curr_query->core.source_def );
4363 osrfAppSessionStatus(
4365 OSRF_STATUS_INTERNALSERVERERROR,
4366 "osrfMethodException",
4368 "Unable to identify table for core class"
4371 buffer_free( group_buf );
4372 if( defaultselhash )
4373 jsonObjectFree( defaultselhash );
4374 free( join_clause );
4378 // Put it all together
4379 growing_buffer* sql_buf = buffer_init(128);
4380 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4384 // Append the join clause, if any
4386 buffer_add(sql_buf, join_clause);
4390 char* order_by_list = NULL;
4391 char* having_buf = NULL;
4393 if (!from_function) {
4395 // Build a WHERE clause, if there is one
4396 if ( search_hash ) {
4397 buffer_add(sql_buf, " WHERE ");
4399 // and it's on the WHERE clause
4400 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4403 osrfAppSessionStatus(
4405 OSRF_STATUS_INTERNALSERVERERROR,
4406 "osrfMethodException",
4408 "Severe query error in WHERE predicate -- see error log for more details"
4411 buffer_free(group_buf);
4412 buffer_free(sql_buf);
4414 jsonObjectFree(defaultselhash);
4418 buffer_add(sql_buf, pred);
4422 // Build a HAVING clause, if there is one
4423 if ( having_hash ) {
4425 // and it's on the the WHERE clause
4426 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4428 if( ! having_buf ) {
4430 osrfAppSessionStatus(
4432 OSRF_STATUS_INTERNALSERVERERROR,
4433 "osrfMethodException",
4435 "Severe query error in HAVING predicate -- see error log for more details"
4438 buffer_free(group_buf);
4439 buffer_free(sql_buf);
4441 jsonObjectFree(defaultselhash);
4446 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4448 // Build an ORDER BY clause, if there is one
4449 if( NULL == order_hash )
4450 ; // No ORDER BY? do nothing
4451 else if( JSON_ARRAY == order_hash->type ) {
4452 // Array of field specifications, each specification being a
4453 // hash to define the class, field, and other details
4455 jsonObject* order_spec;
4456 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4458 if( JSON_HASH != order_spec->type ) {
4459 osrfLogError(OSRF_LOG_MARK,
4460 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4461 MODULENAME, json_type( order_spec->type ) );
4463 osrfAppSessionStatus(
4465 OSRF_STATUS_INTERNALSERVERERROR,
4466 "osrfMethodException",
4468 "Malformed ORDER BY clause -- see error log for more details"
4470 buffer_free( order_buf );
4472 buffer_free(group_buf);
4473 buffer_free(sql_buf);
4475 jsonObjectFree(defaultselhash);
4479 const char* class_alias =
4480 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4482 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4485 OSRF_BUFFER_ADD(order_buf, ", ");
4487 order_buf = buffer_init(128);
4489 if( !field || !class_alias ) {
4490 osrfLogError( OSRF_LOG_MARK,
4491 "%s: Missing class or field name in field specification "
4492 "of ORDER BY clause",
4495 osrfAppSessionStatus(
4497 OSRF_STATUS_INTERNALSERVERERROR,
4498 "osrfMethodException",
4500 "Malformed ORDER BY clause -- see error log for more details"
4502 buffer_free( order_buf );
4504 buffer_free(group_buf);
4505 buffer_free(sql_buf);
4507 jsonObjectFree(defaultselhash);
4511 ClassInfo* order_class_info = search_alias( class_alias );
4512 if( ! order_class_info ) {
4513 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4514 "not in FROM clause", MODULENAME, class_alias );
4516 osrfAppSessionStatus(
4518 OSRF_STATUS_INTERNALSERVERERROR,
4519 "osrfMethodException",
4521 "Invalid class referenced in ORDER BY clause -- "
4522 "see error log for more details"
4525 buffer_free(group_buf);
4526 buffer_free(sql_buf);
4528 jsonObjectFree(defaultselhash);
4532 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4534 osrfLogError( OSRF_LOG_MARK,
4535 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4536 MODULENAME, class_alias, field );
4538 osrfAppSessionStatus(
4540 OSRF_STATUS_INTERNALSERVERERROR,
4541 "osrfMethodException",
4543 "Invalid field referenced in ORDER BY clause -- "
4544 "see error log for more details"
4547 buffer_free(group_buf);
4548 buffer_free(sql_buf);
4550 jsonObjectFree(defaultselhash);
4552 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4553 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4554 MODULENAME, field );
4556 osrfAppSessionStatus(
4558 OSRF_STATUS_INTERNALSERVERERROR,
4559 "osrfMethodException",
4561 "Virtual field in ORDER BY clause -- see error log for more details"
4563 buffer_free( order_buf );
4565 buffer_free(group_buf);
4566 buffer_free(sql_buf);
4568 jsonObjectFree(defaultselhash);
4572 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4573 char* transform_str = searchFieldTransform(
4574 class_alias, field_def, order_spec );
4575 if( ! transform_str ) {
4577 osrfAppSessionStatus(
4579 OSRF_STATUS_INTERNALSERVERERROR,
4580 "osrfMethodException",
4582 "Severe query error in ORDER BY clause -- "
4583 "see error log for more details"
4585 buffer_free( order_buf );
4587 buffer_free(group_buf);
4588 buffer_free(sql_buf);
4590 jsonObjectFree(defaultselhash);
4594 OSRF_BUFFER_ADD( order_buf, transform_str );
4595 free( transform_str );
4598 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4600 const char* direction =
4601 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4603 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4604 OSRF_BUFFER_ADD( order_buf, " DESC" );
4606 OSRF_BUFFER_ADD( order_buf, " ASC" );
4609 } else if( JSON_HASH == order_hash->type ) {
4610 // This hash is keyed on class alias. Each class has either
4611 // an array of field names or a hash keyed on field name.
4612 jsonIterator* class_itr = jsonNewIterator( order_hash );
4613 while ( (snode = jsonIteratorNext( class_itr )) ) {
4615 ClassInfo* order_class_info = search_alias( class_itr->key );
4616 if( ! order_class_info ) {
4617 osrfLogError(OSRF_LOG_MARK,
4618 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4619 MODULENAME, class_itr->key );
4621 osrfAppSessionStatus(
4623 OSRF_STATUS_INTERNALSERVERERROR,
4624 "osrfMethodException",
4626 "Invalid class referenced in ORDER BY clause -- "
4627 "see error log for more details"
4629 jsonIteratorFree( class_itr );
4630 buffer_free( order_buf );
4632 buffer_free(group_buf);
4633 buffer_free(sql_buf);
4635 jsonObjectFree(defaultselhash);
4639 osrfHash* field_list_def = order_class_info->fields;
4641 if ( snode->type == JSON_HASH ) {
4643 // Hash is keyed on field names from the current class. For each field
4644 // there is another layer of hash to define the sorting details, if any,
4645 // or a string to indicate direction of sorting.
4646 jsonIterator* order_itr = jsonNewIterator( snode );
4647 while ( (onode = jsonIteratorNext( order_itr )) ) {
4649 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4651 osrfLogError( OSRF_LOG_MARK,
4652 "%s: Invalid field \"%s\" in ORDER BY clause",
4653 MODULENAME, order_itr->key );
4655 osrfAppSessionStatus(
4657 OSRF_STATUS_INTERNALSERVERERROR,
4658 "osrfMethodException",
4660 "Invalid field in ORDER BY clause -- see error log for more details"
4662 jsonIteratorFree( order_itr );
4663 jsonIteratorFree( class_itr );
4664 buffer_free( order_buf );
4666 buffer_free(group_buf);
4667 buffer_free(sql_buf);
4669 jsonObjectFree(defaultselhash);
4671 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4672 osrfLogError( OSRF_LOG_MARK,
4673 "%s: Virtual field \"%s\" in ORDER BY clause",
4674 MODULENAME, order_itr->key );
4676 osrfAppSessionStatus(
4678 OSRF_STATUS_INTERNALSERVERERROR,
4679 "osrfMethodException",
4681 "Virtual field in ORDER BY clause -- "
4682 "see error log for more details"
4684 jsonIteratorFree( order_itr );
4685 jsonIteratorFree( class_itr );
4686 buffer_free( order_buf );
4688 buffer_free(group_buf);
4689 buffer_free(sql_buf);
4691 jsonObjectFree(defaultselhash);
4695 const char* direction = NULL;
4696 if ( onode->type == JSON_HASH ) {
4697 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4698 string = searchFieldTransform(
4700 osrfHashGet( field_list_def, order_itr->key ),
4704 if( ctx ) osrfAppSessionStatus(
4706 OSRF_STATUS_INTERNALSERVERERROR,
4707 "osrfMethodException",
4709 "Severe query error in ORDER BY clause -- "
4710 "see error log for more details"
4712 jsonIteratorFree( order_itr );
4713 jsonIteratorFree( class_itr );
4715 buffer_free(group_buf);
4716 buffer_free(order_buf);
4717 buffer_free(sql_buf);
4719 jsonObjectFree(defaultselhash);
4723 growing_buffer* field_buf = buffer_init(16);
4724 buffer_fadd( field_buf, "\"%s\".%s",
4725 class_itr->key, order_itr->key );
4726 string = buffer_release(field_buf);
4729 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4730 const char* dir = jsonObjectGetString(tmp_const);
4731 if (!strncasecmp(dir, "d", 1)) {
4732 direction = " DESC";
4738 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4739 osrfLogError( OSRF_LOG_MARK,
4740 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4741 MODULENAME, json_type( onode->type ) );
4743 osrfAppSessionStatus(
4745 OSRF_STATUS_INTERNALSERVERERROR,
4746 "osrfMethodException",
4748 "Malformed ORDER BY clause -- see error log for more details"
4750 jsonIteratorFree( order_itr );
4751 jsonIteratorFree( class_itr );
4753 buffer_free(group_buf);
4754 buffer_free(order_buf);
4755 buffer_free(sql_buf);
4757 jsonObjectFree(defaultselhash);
4761 string = strdup(order_itr->key);
4762 const char* dir = jsonObjectGetString(onode);
4763 if (!strncasecmp(dir, "d", 1)) {
4764 direction = " DESC";
4771 OSRF_BUFFER_ADD(order_buf, ", ");
4773 order_buf = buffer_init(128);
4775 OSRF_BUFFER_ADD(order_buf, string);
4779 OSRF_BUFFER_ADD(order_buf, direction);
4783 jsonIteratorFree(order_itr);
4785 } else if ( snode->type == JSON_ARRAY ) {
4787 // Array is a list of fields from the current class
4788 unsigned long order_idx = 0;
4789 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4791 const char* _f = jsonObjectGetString( onode );
4793 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4795 osrfLogError( OSRF_LOG_MARK,
4796 "%s: Invalid field \"%s\" in ORDER BY clause",
4799 osrfAppSessionStatus(
4801 OSRF_STATUS_INTERNALSERVERERROR,
4802 "osrfMethodException",
4804 "Invalid field in ORDER BY clause -- "
4805 "see error log for more details"
4807 jsonIteratorFree( class_itr );
4808 buffer_free( order_buf );
4810 buffer_free(group_buf);
4811 buffer_free(sql_buf);
4813 jsonObjectFree(defaultselhash);
4815 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4816 osrfLogError( OSRF_LOG_MARK,
4817 "%s: Virtual field \"%s\" in ORDER BY clause",
4820 osrfAppSessionStatus(
4822 OSRF_STATUS_INTERNALSERVERERROR,
4823 "osrfMethodException",
4825 "Virtual field in ORDER BY clause -- "
4826 "see error log for more details"
4828 jsonIteratorFree( class_itr );
4829 buffer_free( order_buf );
4831 buffer_free(group_buf);
4832 buffer_free(sql_buf);
4834 jsonObjectFree(defaultselhash);
4839 OSRF_BUFFER_ADD(order_buf, ", ");
4841 order_buf = buffer_init(128);
4843 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
4847 // IT'S THE OOOOOOOOOOOLD STYLE!
4849 osrfLogError(OSRF_LOG_MARK,
4850 "%s: Possible SQL injection attempt; direct order by is not allowed",
4853 osrfAppSessionStatus(
4855 OSRF_STATUS_INTERNALSERVERERROR,
4856 "osrfMethodException",
4858 "Severe query error -- see error log for more details"
4863 buffer_free(group_buf);
4864 buffer_free(order_buf);
4865 buffer_free(sql_buf);
4867 jsonObjectFree(defaultselhash);
4868 jsonIteratorFree(class_itr);
4872 jsonIteratorFree( class_itr );
4874 osrfLogError(OSRF_LOG_MARK,
4875 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4876 MODULENAME, json_type( order_hash->type ) );
4878 osrfAppSessionStatus(
4880 OSRF_STATUS_INTERNALSERVERERROR,
4881 "osrfMethodException",
4883 "Malformed ORDER BY clause -- see error log for more details"
4885 buffer_free( order_buf );
4887 buffer_free(group_buf);
4888 buffer_free(sql_buf);
4890 jsonObjectFree(defaultselhash);
4895 order_by_list = buffer_release( order_buf );
4899 string = buffer_release(group_buf);
4901 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4902 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4903 OSRF_BUFFER_ADD( sql_buf, string );
4908 if( having_buf && *having_buf ) {
4909 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4910 OSRF_BUFFER_ADD( sql_buf, having_buf );
4914 if( order_by_list ) {
4916 if ( *order_by_list ) {
4917 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4918 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4921 free( order_by_list );
4925 const char* str = jsonObjectGetString(limit);
4926 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
4930 const char* str = jsonObjectGetString(offset);
4931 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
4934 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4937 jsonObjectFree(defaultselhash);
4939 return buffer_release(sql_buf);
4941 } // end of SELECT()
4943 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4945 const char* locale = osrf_message_get_last_locale();
4947 osrfHash* fields = osrfHashGet(meta, "fields");
4948 char* core_class = osrfHashGet(meta, "classname");
4950 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4952 jsonObject* node = NULL;
4953 jsonObject* snode = NULL;
4954 jsonObject* onode = NULL;
4955 const jsonObject* _tmp = NULL;
4956 jsonObject* selhash = NULL;
4957 jsonObject* defaultselhash = NULL;
4959 growing_buffer* sql_buf = buffer_init(128);
4960 growing_buffer* select_buf = buffer_init(128);
4962 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4963 defaultselhash = jsonNewObjectType(JSON_HASH);
4964 selhash = defaultselhash;
4967 // If there's no SELECT list for the core class, build one
4968 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4969 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4971 // Add every non-virtual field to the field list
4972 osrfHash* field_def = NULL;
4973 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4974 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4975 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4976 const char* field = osrfHashIteratorKey( field_itr );
4977 jsonObjectPush( field_list, jsonNewObject( field ) );
4980 osrfHashIteratorFree( field_itr );
4981 jsonObjectSetKey( selhash, core_class, field_list );
4985 jsonIterator* class_itr = jsonNewIterator( selhash );
4986 while ( (snode = jsonIteratorNext( class_itr )) ) {
4988 const char* cname = class_itr->key;
4989 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4993 if (strcmp(core_class,class_itr->key)) {
4997 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4999 jsonObjectFree(found);
5003 jsonObjectFree(found);
5006 jsonIterator* select_itr = jsonNewIterator( snode );
5007 while ( (node = jsonIteratorNext( select_itr )) ) {
5008 const char* item_str = jsonObjectGetString( node );
5009 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5010 char* fname = osrfHashGet(field, "name");
5018 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
5023 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
5024 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5027 i18n = osrfHashGet(field, "i18n");
5029 if( str_is_true( i18n ) ) {
5030 char* pkey = osrfHashGet(idlClass, "primarykey");
5031 char* tname = osrfHashGet(idlClass, "tablename");
5033 buffer_fadd(select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5034 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5035 tname, cname, fname, pkey, cname, pkey, locale, fname);
5037 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
5040 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
5044 jsonIteratorFree(select_itr);
5047 jsonIteratorFree(class_itr);
5049 char* col_list = buffer_release(select_buf);
5050 char* table = getRelation(meta);
5052 table = strdup( "(null)" );
5054 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5058 // Clear the query stack (as a fail-safe precaution against possible
5059 // leftover garbage); then push the first query frame onto the stack.
5060 clear_query_stack();
5062 if( add_query_core( NULL, core_class ) ) {
5064 osrfAppSessionStatus(
5066 OSRF_STATUS_INTERNALSERVERERROR,
5067 "osrfMethodException",
5069 "Unable to build query frame for core class"
5075 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5076 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
5077 OSRF_BUFFER_ADD(sql_buf, join_clause);
5081 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5082 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
5084 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
5086 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5088 osrfAppSessionStatus(
5090 OSRF_STATUS_INTERNALSERVERERROR,
5091 "osrfMethodException",
5093 "Severe query error -- see error log for more details"
5095 buffer_free(sql_buf);
5097 jsonObjectFree(defaultselhash);
5098 clear_query_stack();
5101 buffer_add(sql_buf, pred);
5106 char* string = NULL;
5107 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
5109 growing_buffer* order_buf = buffer_init(128);
5112 jsonIterator* class_itr = jsonNewIterator( _tmp );
5113 while ( (snode = jsonIteratorNext( class_itr )) ) {
5115 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
5118 if ( snode->type == JSON_HASH ) {
5120 jsonIterator* order_itr = jsonNewIterator( snode );
5121 while ( (onode = jsonIteratorNext( order_itr )) ) {
5123 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
5124 class_itr->key, order_itr->key );
5128 char* direction = NULL;
5129 if ( onode->type == JSON_HASH ) {
5130 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
5131 string = searchFieldTransform( class_itr->key, field_def, onode );
5133 osrfAppSessionStatus(
5135 OSRF_STATUS_INTERNALSERVERERROR,
5136 "osrfMethodException",
5138 "Severe query error in ORDER BY clause -- "
5139 "see error log for more details"
5141 jsonIteratorFree( order_itr );
5142 jsonIteratorFree( class_itr );
5143 buffer_free( order_buf );
5144 buffer_free( sql_buf );
5145 if( defaultselhash )
5146 jsonObjectFree( defaultselhash );
5147 clear_query_stack();
5151 growing_buffer* field_buf = buffer_init(16);
5152 buffer_fadd( field_buf, "\"%s\".%s",
5153 class_itr->key, order_itr->key );
5154 string = buffer_release(field_buf);
5157 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
5158 const char* dir = jsonObjectGetString(_tmp);
5159 if (!strncasecmp(dir, "d", 1)) {
5160 direction = " DESC";
5166 string = strdup(order_itr->key);
5167 const char* dir = jsonObjectGetString(onode);
5168 if (!strncasecmp(dir, "d", 1)) {
5169 direction = " DESC";
5178 buffer_add(order_buf, ", ");
5181 buffer_add(order_buf, string);
5185 buffer_add(order_buf, direction);
5189 jsonIteratorFree(order_itr);
5192 const char* str = jsonObjectGetString(snode);
5193 buffer_add(order_buf, str);
5199 jsonIteratorFree(class_itr);
5201 string = buffer_release(order_buf);
5204 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5205 OSRF_BUFFER_ADD( sql_buf, string );
5211 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
5212 const char* str = jsonObjectGetString(_tmp);
5220 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
5222 const char* str = jsonObjectGetString(_tmp);
5232 jsonObjectFree(defaultselhash);
5233 clear_query_stack();
5235 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
5236 return buffer_release(sql_buf);
5239 int doJSONSearch ( osrfMethodContext* ctx ) {
5240 if(osrfMethodVerifyContext( ctx )) {
5241 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5245 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
5250 dbhandle = writehandle;
5252 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
5256 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
5257 flags |= SELECT_DISTINCT;
5259 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
5260 flags |= DISABLE_I18N;
5262 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
5263 clear_query_stack(); // a possibly needless precaution
5264 char* sql = buildQuery( ctx, hash, flags );
5265 clear_query_stack();
5272 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
5273 dbi_result result = dbi_conn_query(dbhandle, sql);
5276 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
5278 if (dbi_result_first_row(result)) {
5279 /* JSONify the result */
5280 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
5283 jsonObject* return_val = oilsMakeJSONFromResult( result );
5284 osrfAppRespond( ctx, return_val );
5285 jsonObjectFree( return_val );
5286 } while (dbi_result_next_row(result));
5289 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
5292 osrfAppRespondComplete( ctx, NULL );
5294 /* clean up the query */
5295 dbi_result_free(result);
5299 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
5300 osrfAppSessionStatus(
5302 OSRF_STATUS_INTERNALSERVERERROR,
5303 "osrfMethodException",
5305 "Severe query error -- see error log for more details"
5313 // The last parameter, err, is used to report an error condition by updating an int owned by
5314 // the calling code.
5316 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5317 // It is the responsibility of the calling code to initialize *err before the
5318 // call, so that it will be able to make sense of the result.
5320 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5321 // redundant anyway.
5322 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
5323 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5326 dbhandle = writehandle;
5328 char* core_class = osrfHashGet( class_meta, "classname" );
5329 char* pkey = osrfHashGet( class_meta, "primarykey" );
5331 const jsonObject* _tmp;
5333 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5335 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
5340 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
5342 dbi_result result = dbi_conn_query(dbhandle, sql);
5343 if( NULL == result ) {
5344 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
5345 MODULENAME, osrfHashGet( class_meta, "fieldmapper" ), sql);
5346 osrfAppSessionStatus(
5348 OSRF_STATUS_INTERNALSERVERERROR,
5349 "osrfMethodException",
5351 "Severe query error -- see error log for more details"
5358 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
5361 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
5362 jsonObject* row_obj = NULL;
5364 if (dbi_result_first_row(result)) {
5366 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5367 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5368 // eliminate the duplicates.
5369 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
5370 osrfHash* dedup = osrfNewHash();
5372 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5373 char* pkey_val = oilsFMGetString( row_obj, pkey );
5374 if ( osrfHashGet( dedup, pkey_val ) ) {
5375 jsonObjectFree( row_obj );
5378 osrfHashSet( dedup, pkey_val, pkey_val );
5379 jsonObjectPush( res_list, row_obj );
5381 } while (dbi_result_next_row(result));
5382 osrfHashFree(dedup);
5385 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
5389 /* clean up the query */
5390 dbi_result_free(result);
5393 // If we're asked to flesh, and there's anything to flesh, then flesh.
5394 if (res_list->size && query_hash) {
5395 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5397 // Get the flesh depth
5398 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5399 if ( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5400 flesh_depth = max_flesh_depth;
5402 // We need a non-zero flesh depth, and a list of fields to flesh
5403 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5404 if ( temp_blob && flesh_depth > 0 ) {
5406 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5407 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5409 osrfStringArray* link_fields = NULL;
5410 osrfHash* links = osrfHashGet( class_meta, "links" );
5412 // Make an osrfStringArray of the names of fields to be fleshed
5414 if (flesh_fields->size == 1) {
5415 const char* _t = jsonObjectGetString(
5416 jsonObjectGetIndex( flesh_fields, 0 ) );
5417 if (!strcmp(_t,"*"))
5418 link_fields = osrfHashKeys( links );
5423 link_fields = osrfNewStringArray(1);
5424 jsonIterator* _i = jsonNewIterator( flesh_fields );
5425 while ((_f = jsonIteratorNext( _i ))) {
5426 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5428 jsonIteratorFree(_i);
5432 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5434 // Iterate over the JSON_ARRAY of rows
5436 unsigned long res_idx = 0;
5437 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5440 const char* link_field;
5442 // Iterate over the list of fleshable fields
5443 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5445 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
5447 osrfHash* kid_link = osrfHashGet(links, link_field);
5449 continue; // Not a link field; skip it
5451 osrfHash* field = osrfHashGet(fields, link_field);
5453 continue; // Not a field at all; skip it (IDL is ill-formed)
5455 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
5457 continue; // The class it links to doesn't exist; skip it
5459 const char* reltype = osrfHashGet( kid_link, "reltype" );
5461 continue; // No reltype; skip it (IDL is ill-formed)
5463 osrfHash* value_field = field;
5465 if ( !strcmp( reltype, "has_many" )
5466 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5467 value_field = osrfHashGet(
5468 fields, osrfHashGet( class_meta, "primarykey" ) );
5471 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5473 if (link_map->size > 0) {
5474 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
5477 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5482 osrfHashGet(kid_link, "class"),
5489 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5490 osrfHashGet(kid_link, "field"),
5491 osrfHashGet(kid_link, "class"),
5492 osrfHashGet(kid_link, "key"),
5493 osrfHashGet(kid_link, "reltype")
5496 const char* search_key = jsonObjectGetString(
5497 jsonObjectGetIndex( cur,
5498 atoi( osrfHashGet(value_field, "array_position") )
5503 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
5507 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
5509 // construct WHERE clause
5510 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
5513 osrfHashGet(kid_link, "key"),
5514 jsonNewObject( search_key )
5517 // construct the rest of the query, mostly
5518 // by copying pieces of the previous level of query
5519 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
5520 jsonObjectSetKey( rest_of_query, "flesh",
5521 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5525 jsonObjectSetKey( rest_of_query, "flesh_fields",
5526 jsonObjectClone(flesh_blob) );
5528 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
5529 jsonObjectSetKey( rest_of_query, "order_by",
5530 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
5534 if (jsonObjectGetKeyConst(query_hash, "select")) {
5535 jsonObjectSetKey( rest_of_query, "select",
5536 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
5540 // do the query, recursively, to expand the fleshable field
5541 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5542 where_clause, rest_of_query, err);
5544 jsonObjectFree( where_clause );
5545 jsonObjectFree( rest_of_query );
5548 osrfStringArrayFree(link_fields);
5549 jsonObjectFree(res_list);
5550 jsonObjectFree(flesh_blob);
5554 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects",
5555 osrfHashGet(kid_link, "class"), kids->size);
5557 // Traverse the result set
5558 jsonObject* X = NULL;
5559 if ( link_map->size > 0 && kids->size > 0 ) {
5561 kids = jsonNewObjectType(JSON_ARRAY);
5563 jsonObject* _k_node;
5564 unsigned long res_idx = 0;
5565 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5571 (unsigned long)atoi(
5577 osrfHashGet(kid_link, "class")
5581 osrfStringArrayGetString( link_map, 0 )
5589 } // end while loop traversing X
5592 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" ))
5593 || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
5594 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5595 osrfHashGet(kid_link, "field"));
5598 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5599 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
5603 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) {
5605 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5606 osrfHashGet( kid_link, "field" ) );
5609 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5610 jsonObjectClone( kids )
5615 jsonObjectFree(kids);
5619 jsonObjectFree( kids );
5621 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5622 osrfHashGet( kid_link, "field" ) );
5623 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
5625 } // end while loop traversing list of fleshable fields
5626 } // end while loop traversing res_list
5627 jsonObjectFree( flesh_blob );
5628 osrfStringArrayFree(link_fields);
5637 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
5639 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5641 jsonObject* target = NULL;
5643 target = jsonObjectGetIndex( ctx->params, 1 );
5645 target = jsonObjectGetIndex( ctx->params, 0 );
5647 if (!verifyObjectClass(ctx, target)) {
5652 if( getXactId( ctx ) == NULL ) {
5653 osrfAppSessionStatus(
5655 OSRF_STATUS_BADREQUEST,
5656 "osrfMethodException",
5658 "No active transaction -- required for UPDATE"
5664 // The following test is harmless but redundant. If a class is
5665 // readonly, we don't register an update method for it.
5666 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5667 osrfAppSessionStatus(
5669 OSRF_STATUS_BADREQUEST,
5670 "osrfMethodException",
5672 "Cannot UPDATE readonly class"
5678 dbhandle = writehandle;
5679 const char* trans_id = getXactId( ctx );
5681 // Set the last_xact_id
5682 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5684 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5685 trans_id, target->classname, index);
5686 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
5689 char* pkey = osrfHashGet(meta, "primarykey");
5690 osrfHash* fields = osrfHashGet(meta, "fields");
5692 char* id = oilsFMGetString( target, pkey );
5696 "%s updating %s object with %s = %s",
5698 osrfHashGet(meta, "fieldmapper"),
5703 growing_buffer* sql = buffer_init(128);
5704 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
5707 osrfHash* field_def = NULL;
5708 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5709 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
5711 // Skip virtual fields, and the primary key
5712 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5715 const char* field_name = osrfHashIteratorKey( field_itr );
5716 if( ! strcmp( field_name, pkey ) )
5719 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5721 int value_is_numeric = 0; // boolean
5723 if (field_object && field_object->classname) {
5724 value = oilsFMGetString(
5726 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
5728 } else if( field_object && JSON_BOOL == field_object->type ) {
5729 if( jsonBoolIsTrue( field_object ) )
5730 value = strdup( "t" );
5732 value = strdup( "f" );
5734 value = jsonObjectToSimpleString( field_object );
5735 if( field_object && JSON_NUMBER == field_object->type )
5736 value_is_numeric = 1;
5739 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5740 osrfHashGet(meta, "fieldmapper"), field_name, value);
5742 if (!field_object || field_object->type == JSON_NULL) {
5743 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
5744 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5745 if (first) first = 0;
5746 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5747 buffer_fadd( sql, " %s = NULL", field_name );
5750 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5751 if (first) first = 0;
5752 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5754 const char* numtype = get_datatype( field_def );
5755 if ( !strncmp( numtype, "INT", 3 ) ) {
5756 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
5757 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
5758 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
5760 // Must really be intended as a string, so quote it
5761 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5762 buffer_fadd( sql, " %s = %s", field_name, value );
5764 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5766 osrfAppSessionStatus(
5768 OSRF_STATUS_INTERNALSERVERERROR,
5769 "osrfMethodException",
5771 "Error quoting string -- please see the error log for more details"
5775 osrfHashIteratorFree( field_itr );
5782 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5785 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5786 if (first) first = 0;
5787 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5788 buffer_fadd( sql, " %s = %s", field_name, value );
5791 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5792 osrfAppSessionStatus(
5794 OSRF_STATUS_INTERNALSERVERERROR,
5795 "osrfMethodException",
5797 "Error quoting string -- please see the error log for more details"
5801 osrfHashIteratorFree( field_itr );
5812 osrfHashIteratorFree( field_itr );
5814 jsonObject* obj = jsonNewObject(id);
5816 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5817 dbi_conn_quote_string(dbhandle, &id);
5819 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5821 char* query = buffer_release(sql);
5822 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
5824 dbi_result result = dbi_conn_query(dbhandle, query);
5828 jsonObjectFree(obj);
5829 obj = jsonNewObject(NULL);
5832 "%s ERROR updating %s object with %s = %s",
5834 osrfHashGet(meta, "fieldmapper"),
5845 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
5847 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5849 if( getXactId( ctx ) == NULL ) {
5850 osrfAppSessionStatus(
5852 OSRF_STATUS_BADREQUEST,
5853 "osrfMethodException",
5855 "No active transaction -- required for DELETE"
5861 // The following test is harmless but redundant. If a class is
5862 // readonly, we don't register a delete method for it.
5863 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5864 osrfAppSessionStatus(
5866 OSRF_STATUS_BADREQUEST,
5867 "osrfMethodException",
5869 "Cannot DELETE readonly class"
5875 dbhandle = writehandle;
5879 char* pkey = osrfHashGet(meta, "primarykey");
5886 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
5887 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5892 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5894 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5898 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
5903 "%s deleting %s object with %s = %s",
5905 osrfHashGet(meta, "fieldmapper"),
5910 obj = jsonNewObject(id);
5912 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5913 dbi_conn_quote_string(writehandle, &id);
5915 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;",
5916 osrfHashGet(meta, "tablename"), pkey, id);
5919 jsonObjectFree(obj);
5920 obj = jsonNewObject(NULL);
5923 "%s ERROR deleting %s object with %s = %s",
5925 osrfHashGet(meta, "fieldmapper"),
5937 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5938 @param result An iterator for a result set; we only look at the current row.
5939 @param @meta Pointer to the class metadata for the core class.
5940 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5942 If a column is not defined in the IDL, or if it has no array_position defined for it in
5943 the IDL, or if it is defined as virtual, ignore it.
5945 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5946 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5947 array_position in the IDL.
5949 A field defined in the IDL but not represented in the returned row will leave a hole
5950 in the JSON_ARRAY. In effect it will be treated as a null value.
5952 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5953 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5954 classname corresponding to the @a meta argument.
5956 The calling code is responsible for freeing the the resulting jsonObject by calling
5959 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5960 if(!(result && meta)) return jsonNULL;
5962 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5963 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
5964 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
5966 osrfHash* fields = osrfHashGet(meta, "fields");
5968 int columnIndex = 1;
5969 const char* columnName;
5971 /* cycle through the columns in the row returned from the database */
5972 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5974 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5976 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5978 /* determine the field type and storage attributes */
5979 unsigned short type = dbi_result_get_field_type_idx(result, columnIndex);
5980 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5982 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5983 // or if it has no sequence number there, or if it's virtual, skip it.
5984 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
5987 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
5988 continue; // skip this column: IDL says it's virtual
5990 const char* pos = (char*)osrfHashGet(_f, "array_position");
5991 if ( !pos ) // IDL has no sequence number for it. This shouldn't happen,
5992 continue; // since we assign sequence numbers dynamically as we load the IDL.
5994 fmIndex = atoi( pos );
5995 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
5997 continue; // This field is not defined in the IDL
6000 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6001 // sequence number from the IDL (which is likely to be different from the sequence
6002 // of columns in the SELECT clause).
6003 if (dbi_result_field_is_null_idx(result, columnIndex)) {
6004 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
6009 case DBI_TYPE_INTEGER :
6011 if( attr & DBI_INTEGER_SIZE8 )
6012 jsonObjectSetIndex( object, fmIndex,
6013 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
6015 jsonObjectSetIndex( object, fmIndex,
6016 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
6020 case DBI_TYPE_DECIMAL :
6021 jsonObjectSetIndex( object, fmIndex,
6022 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
6025 case DBI_TYPE_STRING :
6030 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
6035 case DBI_TYPE_DATETIME : {
6037 char dt_string[256] = "";
6040 // Fetch the date column as a time_t
6041 time_t _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
6043 // Translate the time_t to a human-readable string
6044 if (!(attr & DBI_DATETIME_DATE)) {
6045 gmtime_r( &_tmp_dt, &gmdt );
6046 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
6047 } else if (!(attr & DBI_DATETIME_TIME)) {
6048 localtime_r( &_tmp_dt, &gmdt );
6049 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
6051 localtime_r( &_tmp_dt, &gmdt );
6052 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
6055 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
6059 case DBI_TYPE_BINARY :
6060 osrfLogError( OSRF_LOG_MARK,
6061 "Can't do binary at column %s : index %d", columnName, columnIndex);
6070 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6071 if(!result) return jsonNULL;
6073 jsonObject* object = jsonNewObject(NULL);
6076 char dt_string[256];
6080 int columnIndex = 1;
6082 unsigned short type;
6083 const char* columnName;
6085 /* cycle through the column list */
6086 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
6088 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
6090 fmIndex = -1; // reset the position
6092 /* determine the field type and storage attributes */
6093 type = dbi_result_get_field_type_idx(result, columnIndex);
6094 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
6096 if (dbi_result_field_is_null_idx(result, columnIndex)) {
6097 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
6102 case DBI_TYPE_INTEGER :
6104 if( attr & DBI_INTEGER_SIZE8 )
6105 jsonObjectSetKey( object, columnName,
6106 jsonNewNumberObject(dbi_result_get_longlong_idx(
6107 result, columnIndex)) );
6109 jsonObjectSetKey( object, columnName,
6110 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
6113 case DBI_TYPE_DECIMAL :
6114 jsonObjectSetKey( object, columnName,
6115 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
6118 case DBI_TYPE_STRING :
6119 jsonObjectSetKey( object, columnName,
6120 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
6123 case DBI_TYPE_DATETIME :
6125 memset(dt_string, '\0', sizeof(dt_string));
6126 memset(&gmdt, '\0', sizeof(gmdt));
6128 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
6131 if (!(attr & DBI_DATETIME_DATE)) {
6132 gmtime_r( &_tmp_dt, &gmdt );
6133 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
6134 } else if (!(attr & DBI_DATETIME_TIME)) {
6135 localtime_r( &_tmp_dt, &gmdt );
6136 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
6138 localtime_r( &_tmp_dt, &gmdt );
6139 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
6142 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
6145 case DBI_TYPE_BINARY :
6146 osrfLogError( OSRF_LOG_MARK,
6147 "Can't do binary at column %s : index %d", columnName, columnIndex );
6151 } // end while loop traversing result
6156 // Interpret a string as true or false
6157 static int str_is_true( const char* str ) {
6158 if( NULL == str || strcasecmp( str, "true" ) )
6164 // Interpret a jsonObject as true or false
6165 static int obj_is_true( const jsonObject* obj ) {
6168 else switch( obj->type )
6176 if( strcasecmp( obj->value.s, "true" ) )
6180 case JSON_NUMBER : // Support 1/0 for perl's sake
6181 if( jsonObjectGetNumber( obj ) == 1.0 )
6190 // Translate a numeric code into a text string identifying a type of
6191 // jsonObject. To be used for building error messages.
6192 static const char* json_type( int code ) {
6198 return "JSON_ARRAY";
6200 return "JSON_STRING";
6202 return "JSON_NUMBER";
6208 return "(unrecognized)";
6212 // Extract the "primitive" attribute from an IDL field definition.
6213 // If we haven't initialized the app, then we must be running in
6214 // some kind of testbed. In that case, default to "string".
6215 static const char* get_primitive( osrfHash* field ) {
6216 const char* s = osrfHashGet( field, "primitive" );
6218 if( child_initialized )
6221 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6223 osrfHashGet( field, "name" )
6231 // Extract the "datatype" attribute from an IDL field definition.
6232 // If we haven't initialized the app, then we must be running in
6233 // some kind of testbed. In that case, default to to NUMERIC,
6234 // since we look at the datatype only for numbers.
6235 static const char* get_datatype( osrfHash* field ) {
6236 const char* s = osrfHashGet( field, "datatype" );
6238 if( child_initialized )
6241 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6243 osrfHashGet( field, "name" )
6252 @brief Determine whether a string is potentially a valid SQL identifier.
6253 @param s The identifier to be tested.
6254 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6256 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6257 need to follow all the rules exactly, such as requiring that the first character not
6260 We allow leading and trailing white space. In between, we do not allow punctuation
6261 (except for underscores and dollar signs), control characters, or embedded white space.
6263 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6264 for the foreseeable future such quoted identifiers are not likely to be an issue.
6266 static int is_identifier( const char* s) {
6270 // Skip leading white space
6271 while( isspace( (unsigned char) *s ) )
6275 return 0; // Nothing but white space? Not okay.
6277 // Check each character until we reach white space or
6278 // end-of-string. Letters, digits, underscores, and
6279 // dollar signs are okay. With the exception of periods
6280 // (as in schema.identifier), control characters and other
6281 // punctuation characters are not okay. Anything else
6282 // is okay -- it could for example be part of a multibyte
6283 // UTF8 character such as a letter with diacritical marks,
6284 // and those are allowed.
6286 if( isalnum( (unsigned char) *s )
6290 ; // Fine; keep going
6291 else if( ispunct( (unsigned char) *s )
6292 || iscntrl( (unsigned char) *s ) )
6295 } while( *s && ! isspace( (unsigned char) *s ) );
6297 // If we found any white space in the above loop,
6298 // the rest had better be all white space.
6300 while( isspace( (unsigned char) *s ) )
6304 return 0; // White space was embedded within non-white space
6310 @brief Determine whether to accept a character string as a comparison operator.
6311 @param op The candidate comparison operator.
6312 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6314 We don't validate the operator for real. We just make sure that it doesn't contain
6315 any semicolons or white space (with special exceptions for a few specific operators).
6316 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6317 space but it's still not a valid operator, then the database will complain.
6319 Another approach would be to compare the string against a short list of approved operators.
6320 We don't do that because we want to allow custom operators like ">100*", which at this
6321 writing would be difficult or impossible to express otherwise in a JSON query.
6323 static int is_good_operator( const char* op ) {
6324 if( !op ) return 0; // Sanity check
6328 if( isspace( (unsigned char) *s ) ) {
6329 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6330 // and IS NOT DISTINCT FROM.
6331 if( !strcasecmp( op, "similar to" ) )
6333 else if( !strcasecmp( op, "is distinct from" ) )
6335 else if( !strcasecmp( op, "is not distinct from" ) )
6340 else if( ';' == *s )
6348 @name Query Frame Management
6350 The following machinery supports a stack of query frames for use by SELECT().
6352 A query frame caches information about one level of a SELECT query. When we enter
6353 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6355 The query frame stores information about the core class, and about any joined classes
6358 The main purpose is to map table aliases to classes and tables, so that a query can
6359 join to the same table more than once. A secondary goal is to reduce the number of
6360 lookups in the IDL by caching the results.
6364 #define STATIC_CLASS_INFO_COUNT 3
6366 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6369 @brief Allocate a ClassInfo as raw memory.
6370 @return Pointer to the newly allocated ClassInfo.
6372 Except for the in_use flag, which is used only by the allocation and deallocation
6373 logic, we don't initialize the ClassInfo here.
6375 static ClassInfo* allocate_class_info( void ) {
6376 // In order to reduce the number of mallocs and frees, we return a static
6377 // instance of ClassInfo, if we can find one that we're not already using.
6378 // We rely on the fact that the compiler will implicitly initialize the
6379 // static instances so that in_use == 0.
6382 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6383 if( ! static_class_info[ i ].in_use ) {
6384 static_class_info[ i ].in_use = 1;
6385 return static_class_info + i;
6389 // The static ones are all in use. Malloc one.
6391 return safe_malloc( sizeof( ClassInfo ) );
6395 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6396 @param info Pointer to the ClassInfo to be cleared.
6398 static void clear_class_info( ClassInfo* info ) {
6403 // Free any malloc'd strings
6405 if( info->alias != info->alias_store )
6406 free( info->alias );
6408 if( info->class_name != info->class_name_store )
6409 free( info->class_name );
6411 free( info->source_def );
6413 info->alias = info->class_name = info->source_def = NULL;
6418 @brief Free a ClassInfo and everything it owns.
6419 @param info Pointer to the ClassInfo to be freed.
6421 static void free_class_info( ClassInfo* info ) {
6426 clear_class_info( info );
6428 // If it's one of the static instances, just mark it as not in use
6431 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6432 if( info == static_class_info + i ) {
6433 static_class_info[ i ].in_use = 0;
6438 // Otherwise it must have been malloc'd, so free it
6444 @brief Populate an already-allocated ClassInfo.
6445 @param info Pointer to the ClassInfo to be populated.
6446 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6448 @param class Name of the class.
6449 @return Zero if successful, or 1 if not.
6451 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6452 the relevant portions of the IDL for the specified class.
6454 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6457 osrfLogError( OSRF_LOG_MARK,
6458 "%s ERROR: No ClassInfo available to populate", MODULENAME );
6459 info->alias = info->class_name = info->source_def = NULL;
6460 info->class_def = info->fields = info->links = NULL;
6465 osrfLogError( OSRF_LOG_MARK,
6466 "%s ERROR: No class name provided for lookup", MODULENAME );
6467 info->alias = info->class_name = info->source_def = NULL;
6468 info->class_def = info->fields = info->links = NULL;
6472 // Alias defaults to class name if not supplied
6473 if( ! alias || ! alias[ 0 ] )
6476 // Look up class info in the IDL
6477 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6479 osrfLogError( OSRF_LOG_MARK,
6480 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
6481 info->alias = info->class_name = info->source_def = NULL;
6482 info->class_def = info->fields = info->links = NULL;
6484 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6485 osrfLogError( OSRF_LOG_MARK,
6486 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
6487 info->alias = info->class_name = info->source_def = NULL;
6488 info->class_def = info->fields = info->links = NULL;
6492 osrfHash* links = osrfHashGet( class_def, "links" );
6494 osrfLogError( OSRF_LOG_MARK,
6495 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
6496 info->alias = info->class_name = info->source_def = NULL;
6497 info->class_def = info->fields = info->links = NULL;
6501 osrfHash* fields = osrfHashGet( class_def, "fields" );
6503 osrfLogError( OSRF_LOG_MARK,
6504 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
6505 info->alias = info->class_name = info->source_def = NULL;
6506 info->class_def = info->fields = info->links = NULL;
6510 char* source_def = getRelation( class_def );
6514 // We got everything we need, so populate the ClassInfo
6515 if( strlen( alias ) > ALIAS_STORE_SIZE )
6516 info->alias = strdup( alias );
6518 strcpy( info->alias_store, alias );
6519 info->alias = info->alias_store;
6522 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6523 info->class_name = strdup( class );
6525 strcpy( info->class_name_store, class );
6526 info->class_name = info->class_name_store;
6529 info->source_def = source_def;
6531 info->class_def = class_def;
6532 info->links = links;
6533 info->fields = fields;
6538 #define STATIC_FRAME_COUNT 3
6540 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6543 @brief Allocate a QueryFrame as raw memory.
6544 @return Pointer to the newly allocated QueryFrame.
6546 Except for the in_use flag, which is used only by the allocation and deallocation
6547 logic, we don't initialize the QueryFrame here.
6549 static QueryFrame* allocate_frame( void ) {
6550 // In order to reduce the number of mallocs and frees, we return a static
6551 // instance of QueryFrame, if we can find one that we're not already using.
6552 // We rely on the fact that the compiler will implicitly initialize the
6553 // static instances so that in_use == 0.
6556 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6557 if( ! static_frame[ i ].in_use ) {
6558 static_frame[ i ].in_use = 1;
6559 return static_frame + i;
6563 // The static ones are all in use. Malloc one.
6565 return safe_malloc( sizeof( QueryFrame ) );
6569 @brief Free a QueryFrame, and all the memory it owns.
6570 @param frame Pointer to the QueryFrame to be freed.
6572 static void free_query_frame( QueryFrame* frame ) {
6577 clear_class_info( &frame->core );
6579 // Free the join list
6581 ClassInfo* info = frame->join_list;
6584 free_class_info( info );
6588 frame->join_list = NULL;
6591 // If the frame is a static instance, just mark it as unused
6593 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6594 if( frame == static_frame + i ) {
6595 static_frame[ i ].in_use = 0;
6600 // Otherwise it must have been malloc'd, so free it
6606 @brief Search a given QueryFrame for a specified alias.
6607 @param frame Pointer to the QueryFrame to be searched.
6608 @param target The alias for which to search.
6609 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6611 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6612 if( ! frame || ! target ) {
6616 ClassInfo* found_class = NULL;
6618 if( !strcmp( target, frame->core.alias ) )
6619 return &(frame->core);
6621 ClassInfo* curr_class = frame->join_list;
6622 while( curr_class ) {
6623 if( strcmp( target, curr_class->alias ) )
6624 curr_class = curr_class->next;
6626 found_class = curr_class;
6636 @brief Push a new (blank) QueryFrame onto the stack.
6638 static void push_query_frame( void ) {
6639 QueryFrame* frame = allocate_frame();
6640 frame->join_list = NULL;
6641 frame->next = curr_query;
6643 // Initialize the ClassInfo for the core class
6644 ClassInfo* core = &frame->core;
6645 core->alias = core->class_name = core->source_def = NULL;
6646 core->class_def = core->fields = core->links = NULL;
6652 @brief Pop a QueryFrame off the stack and destroy it.
6654 static void pop_query_frame( void ) {
6659 QueryFrame* popped = curr_query;
6660 curr_query = popped->next;
6662 free_query_frame( popped );
6666 @brief Populate the ClassInfo for the core class.
6667 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6668 class name as an alias.
6669 @param class_name Name of the core class.
6670 @return Zero if successful, or 1 if not.
6672 Populate the ClassInfo of the core class with copies of the alias and class name, and
6673 with pointers to the relevant portions of the IDL for the core class.
6675 static int add_query_core( const char* alias, const char* class_name ) {
6678 if( ! curr_query ) {
6679 osrfLogError( OSRF_LOG_MARK,
6680 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
6682 } else if( curr_query->core.alias ) {
6683 osrfLogError( OSRF_LOG_MARK,
6684 "%s ERROR: Core class %s already populated as %s",
6685 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
6689 build_class_info( &curr_query->core, alias, class_name );
6690 if( curr_query->core.alias )
6693 osrfLogError( OSRF_LOG_MARK,
6694 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
6700 @brief Search the current QueryFrame for a specified alias.
6701 @param target The alias for which to search.
6702 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6704 static inline ClassInfo* search_alias( const char* target ) {
6705 return search_alias_in_frame( curr_query, target );
6709 @brief Search all levels of query for a specified alias, starting with the current query.
6710 @param target The alias for which to search.
6711 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6713 static ClassInfo* search_all_alias( const char* target ) {
6714 ClassInfo* found_class = NULL;
6715 QueryFrame* curr_frame = curr_query;
6717 while( curr_frame ) {
6718 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6721 curr_frame = curr_frame->next;
6728 @brief Add a class to the list of classes joined to the current query.
6729 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6730 the class name as an alias.
6731 @param classname The name of the class to be added.
6732 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6734 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6736 if( ! classname || ! *classname ) { // sanity check
6737 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6744 const ClassInfo* conflict = search_alias( alias );
6746 osrfLogError( OSRF_LOG_MARK,
6747 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6748 MODULENAME, alias, conflict->class_name );
6752 ClassInfo* info = allocate_class_info();
6754 if( build_class_info( info, alias, classname ) ) {
6755 free_class_info( info );
6759 // Add the new ClassInfo to the join list of the current QueryFrame
6760 info->next = curr_query->join_list;
6761 curr_query->join_list = info;
6767 @brief Destroy all nodes on the query stack.
6769 static void clear_query_stack( void ) {