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 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2568 field_transform, class_alias, osrfHashGet(field, "name"));
2569 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2572 if( array->type != JSON_ARRAY ) {
2573 osrfLogError( OSRF_LOG_MARK,
2574 "%s: Expected JSON_ARRAY for function params; found %s",
2575 MODULENAME, json_type( array->type ) );
2576 buffer_free( sql_buf );
2579 int func_item_index = 0;
2580 jsonObject* func_item;
2581 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2583 char* val = jsonObjectToSimpleString(func_item);
2586 buffer_add( sql_buf, ",NULL" );
2587 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
2588 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2589 OSRF_BUFFER_ADD( sql_buf, val );
2591 osrfLogError( OSRF_LOG_MARK,
2592 "%s: Error quoting key string [%s]", MODULENAME, val);
2594 buffer_free(sql_buf);
2601 buffer_add( sql_buf, " )" );
2604 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2607 if (transform_subcolumn)
2608 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2610 return buffer_release(sql_buf);
2613 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2614 const jsonObject* node, const char* op ) {
2616 if( ! is_good_operator( op ) ) {
2617 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2621 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2622 if( ! field_transform )
2625 int extra_parens = 0; // boolean
2627 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2628 if ( ! value_obj ) {
2629 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2631 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2633 free(field_transform);
2637 } else if ( value_obj->type == JSON_ARRAY ) {
2638 value = searchValueTransform( value_obj );
2640 osrfLogError(OSRF_LOG_MARK,
2641 "%s: Error building value transform for field transform", MODULENAME);
2642 free( field_transform );
2645 } else if ( value_obj->type == JSON_HASH ) {
2646 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2648 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2650 free(field_transform);
2654 } else if ( value_obj->type == JSON_NUMBER ) {
2655 value = jsonNumberToDBString( field, value_obj );
2656 } else if ( value_obj->type == JSON_NULL ) {
2657 osrfLogError( OSRF_LOG_MARK,
2658 "%s: Error building predicate for field transform: null value", MODULENAME );
2659 free(field_transform);
2661 } else if ( value_obj->type == JSON_BOOL ) {
2662 osrfLogError( OSRF_LOG_MARK,
2663 "%s: Error building predicate for field transform: boolean value", MODULENAME );
2664 free(field_transform);
2667 if ( !strcmp( get_primitive( field ), "number") ) {
2668 value = jsonNumberToDBString( field, value_obj );
2670 value = jsonObjectToSimpleString( value_obj );
2671 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2672 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2675 free(field_transform);
2681 const char* left_parens = "";
2682 const char* right_parens = "";
2684 if( extra_parens ) {
2689 growing_buffer* sql_buf = buffer_init(32);
2693 "%s%s %s %s %s %s%s",
2704 free(field_transform);
2706 return buffer_release(sql_buf);
2709 static char* searchSimplePredicate (const char* op, const char* class_alias,
2710 osrfHash* field, const jsonObject* node) {
2712 if( ! is_good_operator( op ) ) {
2713 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2719 // Get the value to which we are comparing the specified column
2720 if (node->type != JSON_NULL) {
2721 if ( node->type == JSON_NUMBER ) {
2722 val = jsonNumberToDBString( field, node );
2723 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2724 val = jsonNumberToDBString( field, node );
2726 val = jsonObjectToSimpleString(node);
2731 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2732 // Value is not numeric; enclose it in quotes
2733 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2734 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2741 // Compare to a null value
2742 val = strdup( "NULL" );
2743 if (strcmp( op, "=" ))
2749 growing_buffer* sql_buf = buffer_init(32);
2750 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2751 char* pred = buffer_release( sql_buf );
2758 static char* searchBETWEENPredicate (const char* class_alias,
2759 osrfHash* field, const jsonObject* node) {
2761 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2762 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2764 if( NULL == y_node ) {
2765 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2768 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2769 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2776 if ( !strcmp( get_primitive( field ), "number") ) {
2777 x_string = jsonNumberToDBString(field, x_node);
2778 y_string = jsonNumberToDBString(field, y_node);
2781 x_string = jsonObjectToSimpleString(x_node);
2782 y_string = jsonObjectToSimpleString(y_node);
2783 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2784 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2785 MODULENAME, x_string, y_string);
2792 growing_buffer* sql_buf = buffer_init(32);
2793 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2794 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2798 return buffer_release(sql_buf);
2801 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2802 jsonObject* node, osrfMethodContext* ctx ) {
2805 if (node->type == JSON_ARRAY) { // equality IN search
2806 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2807 } else if (node->type == JSON_HASH) { // other search
2808 jsonIterator* pred_itr = jsonNewIterator( node );
2809 if( !jsonIteratorHasNext( pred_itr ) ) {
2810 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2811 MODULENAME, osrfHashGet(field, "name") );
2813 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2815 // Verify that there are no additional predicates
2816 if( jsonIteratorHasNext( pred_itr ) ) {
2817 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2818 MODULENAME, osrfHashGet(field, "name") );
2819 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2820 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2821 else if ( !(strcasecmp( pred_itr->key,"in" ))
2822 || !(strcasecmp( pred_itr->key,"not in" )) )
2823 pred = searchINPredicate(
2824 class_info->alias, field, pred_node, pred_itr->key, ctx );
2825 else if ( pred_node->type == JSON_ARRAY )
2826 pred = searchFunctionPredicate(
2827 class_info->alias, field, pred_node, pred_itr->key );
2828 else if ( pred_node->type == JSON_HASH )
2829 pred = searchFieldTransformPredicate(
2830 class_info, field, pred_node, pred_itr->key );
2832 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2834 jsonIteratorFree(pred_itr);
2836 } else if (node->type == JSON_NULL) { // IS NULL search
2837 growing_buffer* _p = buffer_init(64);
2840 "\"%s\".%s IS NULL",
2841 class_info->class_name,
2842 osrfHashGet(field, "name")
2844 pred = buffer_release(_p);
2845 } else { // equality search
2846 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2865 field : call_number,
2881 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2883 const jsonObject* working_hash;
2884 jsonObject* freeable_hash = NULL;
2886 if (join_hash->type == JSON_HASH) {
2887 working_hash = join_hash;
2888 } else if (join_hash->type == JSON_STRING) {
2889 // turn it into a JSON_HASH by creating a wrapper
2890 // around a copy of the original
2891 const char* _tmp = jsonObjectGetString( join_hash );
2892 freeable_hash = jsonNewObjectType(JSON_HASH);
2893 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2894 working_hash = freeable_hash;
2898 "%s: JOIN failed; expected JSON object type not found",
2904 growing_buffer* join_buf = buffer_init(128);
2905 const char* leftclass = left_info->class_name;
2907 jsonObject* snode = NULL;
2908 jsonIterator* search_itr = jsonNewIterator( working_hash );
2910 while ( (snode = jsonIteratorNext( search_itr )) ) {
2911 const char* right_alias = search_itr->key;
2913 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2915 class = right_alias;
2917 const ClassInfo* right_info = add_joined_class( right_alias, class );
2921 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2925 jsonIteratorFree( search_itr );
2926 buffer_free( join_buf );
2928 jsonObjectFree( freeable_hash );
2931 osrfHash* links = right_info->links;
2932 const char* table = right_info->source_def;
2934 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2935 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2937 if (field && !fkey) {
2938 // Look up the corresponding join column in the IDL.
2939 // The link must be defined in the child table,
2940 // and point to the right parent table.
2941 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2942 const char* reltype = NULL;
2943 const char* other_class = NULL;
2944 reltype = osrfHashGet( idl_link, "reltype" );
2945 if( reltype && strcmp( reltype, "has_many" ) )
2946 other_class = osrfHashGet( idl_link, "class" );
2947 if( other_class && !strcmp( other_class, leftclass ) )
2948 fkey = osrfHashGet( idl_link, "key" );
2952 "%s: JOIN failed. No link defined from %s.%s to %s",
2958 buffer_free(join_buf);
2960 jsonObjectFree(freeable_hash);
2961 jsonIteratorFree(search_itr);
2965 } else if (!field && fkey) {
2966 // Look up the corresponding join column in the IDL.
2967 // The link must be defined in the child table,
2968 // and point to the right parent table.
2969 osrfHash* left_links = left_info->links;
2970 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2971 const char* reltype = NULL;
2972 const char* other_class = NULL;
2973 reltype = osrfHashGet( idl_link, "reltype" );
2974 if( reltype && strcmp( reltype, "has_many" ) )
2975 other_class = osrfHashGet( idl_link, "class" );
2976 if( other_class && !strcmp( other_class, class ) )
2977 field = osrfHashGet( idl_link, "key" );
2981 "%s: JOIN failed. No link defined from %s.%s to %s",
2987 buffer_free(join_buf);
2989 jsonObjectFree(freeable_hash);
2990 jsonIteratorFree(search_itr);
2994 } else if (!field && !fkey) {
2995 osrfHash* left_links = left_info->links;
2997 // For each link defined for the left class:
2998 // see if the link references the joined class
2999 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3000 osrfHash* curr_link = NULL;
3001 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3002 const char* other_class = osrfHashGet( curr_link, "class" );
3003 if( other_class && !strcmp( other_class, class ) ) {
3005 // In the IDL, the parent class doesn't always know then names of the child
3006 // columns that are pointing to it, so don't use that end of the link
3007 const char* reltype = osrfHashGet( curr_link, "reltype" );
3008 if( reltype && strcmp( reltype, "has_many" ) ) {
3009 // Found a link between the classes
3010 fkey = osrfHashIteratorKey( itr );
3011 field = osrfHashGet( curr_link, "key" );
3016 osrfHashIteratorFree( itr );
3018 if (!field || !fkey) {
3019 // Do another such search, with the classes reversed
3021 // For each link defined for the joined class:
3022 // see if the link references the left class
3023 osrfHashIterator* itr = osrfNewHashIterator( links );
3024 osrfHash* curr_link = NULL;
3025 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3026 const char* other_class = osrfHashGet( curr_link, "class" );
3027 if( other_class && !strcmp( other_class, leftclass ) ) {
3029 // In the IDL, the parent class doesn't know then names of the child
3030 // columns that are pointing to it, so don't use that end of the link
3031 const char* reltype = osrfHashGet( curr_link, "reltype" );
3032 if( reltype && strcmp( reltype, "has_many" ) ) {
3033 // Found a link between the classes
3034 field = osrfHashIteratorKey( itr );
3035 fkey = osrfHashGet( curr_link, "key" );
3040 osrfHashIteratorFree( itr );
3043 if (!field || !fkey) {
3046 "%s: JOIN failed. No link defined between %s and %s",
3051 buffer_free(join_buf);
3053 jsonObjectFree(freeable_hash);
3054 jsonIteratorFree(search_itr);
3059 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3061 if ( !strcasecmp(type,"left") ) {
3062 buffer_add(join_buf, " LEFT JOIN");
3063 } else if ( !strcasecmp(type,"right") ) {
3064 buffer_add(join_buf, " RIGHT JOIN");
3065 } else if ( !strcasecmp(type,"full") ) {
3066 buffer_add(join_buf, " FULL JOIN");
3068 buffer_add(join_buf, " INNER JOIN");
3071 buffer_add(join_buf, " INNER JOIN");
3074 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3075 table, right_alias, right_alias, field, left_info->alias, fkey);
3077 // Add any other join conditions as specified by "filter"
3078 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3080 const char* filter_op = jsonObjectGetString(
3081 jsonObjectGetKeyConst( snode, "filter_op" ) );
3082 if ( filter_op && !strcasecmp("or",filter_op) ) {
3083 buffer_add( join_buf, " OR " );
3085 buffer_add( join_buf, " AND " );
3088 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3090 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3091 OSRF_BUFFER_ADD( join_buf, jpred );
3096 "%s: JOIN failed. Invalid conditional expression.",
3099 jsonIteratorFree( search_itr );
3100 buffer_free( join_buf );
3102 jsonObjectFree( freeable_hash );
3107 buffer_add(join_buf, " ) ");
3109 // Recursively add a nested join, if one is present
3110 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3112 char* jpred = searchJOIN( join_filter, right_info );
3114 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3115 OSRF_BUFFER_ADD( join_buf, jpred );
3118 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
3119 jsonIteratorFree( search_itr );
3120 buffer_free( join_buf );
3122 jsonObjectFree( freeable_hash );
3129 jsonObjectFree(freeable_hash);
3130 jsonIteratorFree(search_itr);
3132 return buffer_release(join_buf);
3137 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3138 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3139 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3141 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3143 search_hash is the JSON expression of the conditions.
3144 meta is the class definition from the IDL, for the relevant table.
3145 opjoin_type indicates whether multiple conditions, if present, should be
3146 connected by AND or OR.
3147 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3148 to pass it to other functions -- and all they do with it is to use the session
3149 and request members to send error messages back to the client.
3153 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
3154 int opjoin_type, osrfMethodContext* ctx ) {
3158 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3159 "opjoin_type = %d, ctx addr = %p",
3162 class_info->class_def,
3167 growing_buffer* sql_buf = buffer_init(128);
3169 jsonObject* node = NULL;
3172 if ( search_hash->type == JSON_ARRAY ) {
3173 osrfLogDebug( OSRF_LOG_MARK,
3174 "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME );
3175 if( 0 == search_hash->size ) {
3178 "%s: Invalid predicate structure: empty JSON array",
3181 buffer_free( sql_buf );
3185 unsigned long i = 0;
3186 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
3190 if (opjoin_type == OR_OP_JOIN)
3191 buffer_add(sql_buf, " OR ");
3193 buffer_add(sql_buf, " AND ");
3196 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3198 buffer_free( sql_buf );
3202 buffer_fadd(sql_buf, "( %s )", subpred);
3206 } else if ( search_hash->type == JSON_HASH ) {
3207 osrfLogDebug( OSRF_LOG_MARK,
3208 "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME );
3209 jsonIterator* search_itr = jsonNewIterator( search_hash );
3210 if( !jsonIteratorHasNext( search_itr ) ) {
3213 "%s: Invalid predicate structure: empty JSON object",
3216 jsonIteratorFree( search_itr );
3217 buffer_free( sql_buf );
3221 while ( (node = jsonIteratorNext( search_itr )) ) {
3226 if (opjoin_type == OR_OP_JOIN)
3227 buffer_add(sql_buf, " OR ");
3229 buffer_add(sql_buf, " AND ");
3232 if ( '+' == search_itr->key[ 0 ] ) {
3234 // This plus sign prefixes a class name or other table alias;
3235 // make sure the table alias is in scope
3236 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3237 if( ! alias_info ) {
3240 "%s: Invalid table alias \"%s\" in WHERE clause",
3244 jsonIteratorFree( search_itr );
3245 buffer_free( sql_buf );
3249 if ( node->type == JSON_STRING ) {
3250 // It's the name of a column; make sure it belongs to the class
3251 const char* fieldname = jsonObjectGetString( node );
3252 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3255 "%s: Invalid column name \"%s\" in WHERE clause "
3256 "for table alias \"%s\"",
3261 jsonIteratorFree( search_itr );
3262 buffer_free( sql_buf );
3266 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3268 // It's something more complicated
3269 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3271 jsonIteratorFree( search_itr );
3272 buffer_free( sql_buf );
3276 buffer_fadd(sql_buf, "( %s )", subpred);
3279 } else if ( '-' == search_itr->key[ 0 ] ) {
3280 if ( !strcasecmp("-or",search_itr->key) ) {
3281 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3283 jsonIteratorFree( search_itr );
3284 buffer_free( sql_buf );
3288 buffer_fadd(sql_buf, "( %s )", subpred);
3290 } else if ( !strcasecmp("-and",search_itr->key) ) {
3291 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3293 jsonIteratorFree( search_itr );
3294 buffer_free( sql_buf );
3298 buffer_fadd(sql_buf, "( %s )", subpred);
3300 } else if ( !strcasecmp("-not",search_itr->key) ) {
3301 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3303 jsonIteratorFree( search_itr );
3304 buffer_free( sql_buf );
3308 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
3310 } else if ( !strcasecmp("-exists",search_itr->key) ) {
3311 char* subpred = buildQuery( ctx, node, SUBSELECT );
3313 jsonIteratorFree( search_itr );
3314 buffer_free( sql_buf );
3318 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
3320 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
3321 char* subpred = buildQuery( ctx, node, SUBSELECT );
3323 jsonIteratorFree( search_itr );
3324 buffer_free( sql_buf );
3328 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
3330 } else { // Invalid "minus" operator
3333 "%s: Invalid operator \"%s\" in WHERE clause",
3337 jsonIteratorFree( search_itr );
3338 buffer_free( sql_buf );
3344 const char* class = class_info->class_name;
3345 osrfHash* fields = class_info->fields;
3346 osrfHash* field = osrfHashGet( fields, search_itr->key );
3349 const char* table = class_info->source_def;
3352 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3355 table ? table : "?",
3358 jsonIteratorFree(search_itr);
3359 buffer_free(sql_buf);
3363 char* subpred = searchPredicate( class_info, field, node, ctx );
3365 buffer_free(sql_buf);
3366 jsonIteratorFree(search_itr);
3370 buffer_add( sql_buf, subpred );
3374 jsonIteratorFree(search_itr);
3377 // ERROR ... only hash and array allowed at this level
3378 char* predicate_string = jsonObjectToJSON( search_hash );
3381 "%s: Invalid predicate structure: %s",
3385 buffer_free(sql_buf);
3386 free(predicate_string);
3390 return buffer_release(sql_buf);
3393 /* Build a JSON_ARRAY of field names for a given table alias
3395 static jsonObject* defaultSelectList( const char* table_alias ) {
3400 ClassInfo* class_info = search_all_alias( table_alias );
3401 if( ! class_info ) {
3404 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3411 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3412 osrfHash* field_def = NULL;
3413 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3414 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3415 const char* field_name = osrfHashIteratorKey( field_itr );
3416 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3417 jsonObjectPush( array, jsonNewObject( field_name ) );
3420 osrfHashIteratorFree( field_itr );
3425 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3426 // The jsonObject must be a JSON_HASH with an single entry for "union",
3427 // "intersect", or "except". The data associated with this key must be an
3428 // array of hashes, each hash being a query.
3429 // Also allowed but currently ignored: entries for "order_by" and "alias".
3430 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3432 if( ! combo || combo->type != JSON_HASH )
3433 return NULL; // should be impossible; validated by caller
3435 const jsonObject* query_array = NULL; // array of subordinate queries
3436 const char* op = NULL; // name of operator, e.g. UNION
3437 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3438 int op_count = 0; // for detecting conflicting operators
3439 int excepting = 0; // boolean
3440 int all = 0; // boolean
3441 jsonObject* order_obj = NULL;
3443 // Identify the elements in the hash
3444 jsonIterator* query_itr = jsonNewIterator( combo );
3445 jsonObject* curr_obj = NULL;
3446 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3447 if( ! strcmp( "union", query_itr->key ) ) {
3450 query_array = curr_obj;
3451 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3454 query_array = curr_obj;
3455 } else if( ! strcmp( "except", query_itr->key ) ) {
3459 query_array = curr_obj;
3460 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3463 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3466 order_obj = curr_obj;
3467 } else if( ! strcmp( "alias", query_itr->key ) ) {
3468 if( curr_obj->type != JSON_STRING ) {
3469 jsonIteratorFree( query_itr );
3472 alias = jsonObjectGetString( curr_obj );
3473 } else if( ! strcmp( "all", query_itr->key ) ) {
3474 if( obj_is_true( curr_obj ) )
3478 osrfAppSessionStatus(
3480 OSRF_STATUS_INTERNALSERVERERROR,
3481 "osrfMethodException",
3483 "Malformed query; unexpected entry in query object"
3487 "%s: Unexpected entry for \"%s\" in%squery",
3492 jsonIteratorFree( query_itr );
3496 jsonIteratorFree( query_itr );
3498 // More sanity checks
3499 if( ! query_array ) {
3501 osrfAppSessionStatus(
3503 OSRF_STATUS_INTERNALSERVERERROR,
3504 "osrfMethodException",
3506 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3510 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3513 return NULL; // should be impossible...
3514 } else if( op_count > 1 ) {
3516 osrfAppSessionStatus(
3518 OSRF_STATUS_INTERNALSERVERERROR,
3519 "osrfMethodException",
3521 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3525 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3529 } if( query_array->type != JSON_ARRAY ) {
3531 osrfAppSessionStatus(
3533 OSRF_STATUS_INTERNALSERVERERROR,
3534 "osrfMethodException",
3536 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3540 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3543 json_type( query_array->type )
3546 } if( query_array->size < 2 ) {
3548 osrfAppSessionStatus(
3550 OSRF_STATUS_INTERNALSERVERERROR,
3551 "osrfMethodException",
3553 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3557 "%s:%srequires multiple queries as operands",
3562 } else if( excepting && query_array->size > 2 ) {
3564 osrfAppSessionStatus(
3566 OSRF_STATUS_INTERNALSERVERERROR,
3567 "osrfMethodException",
3569 "EXCEPT operator has too many queries as operands"
3573 "%s:EXCEPT operator has too many queries as operands",
3577 } else if( order_obj && ! alias ) {
3579 osrfAppSessionStatus(
3581 OSRF_STATUS_INTERNALSERVERERROR,
3582 "osrfMethodException",
3584 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3588 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3594 // So far so good. Now build the SQL.
3595 growing_buffer* sql = buffer_init( 256 );
3597 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3598 // Add a layer of parentheses
3599 if( flags & SUBCOMBO )
3600 OSRF_BUFFER_ADD( sql, "( " );
3602 // Traverse the query array. Each entry should be a hash.
3603 int first = 1; // boolean
3605 jsonObject* query = NULL;
3606 while((query = jsonObjectGetIndex( query_array, i++ ) )) {
3607 if( query->type != JSON_HASH ) {
3609 osrfAppSessionStatus(
3611 OSRF_STATUS_INTERNALSERVERERROR,
3612 "osrfMethodException",
3614 "Malformed query under UNION, INTERSECT or EXCEPT"
3618 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3621 json_type( query->type )
3630 OSRF_BUFFER_ADD( sql, op );
3632 OSRF_BUFFER_ADD( sql, "ALL " );
3635 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3639 "%s: Error building query under%s",
3647 OSRF_BUFFER_ADD( sql, query_str );
3650 if( flags & SUBCOMBO )
3651 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3653 if ( !(flags & SUBSELECT) )
3654 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3656 return buffer_release( sql );
3659 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3660 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3661 // or "except" to indicate the type of query.
3662 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3666 osrfAppSessionStatus(
3668 OSRF_STATUS_INTERNALSERVERERROR,
3669 "osrfMethodException",
3671 "Malformed query; no query object"
3673 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", MODULENAME );
3675 } else if( query->type != JSON_HASH ) {
3677 osrfAppSessionStatus(
3679 OSRF_STATUS_INTERNALSERVERERROR,
3680 "osrfMethodException",
3682 "Malformed query object"
3686 "%s: Query object is %s instead of JSON_HASH",
3688 json_type( query->type )
3693 // Determine what kind of query it purports to be, and dispatch accordingly.
3694 if( jsonObjectGetKey( query, "union" ) ||
3695 jsonObjectGetKey( query, "intersect" ) ||
3696 jsonObjectGetKey( query, "except" ) ) {
3697 return doCombo( ctx, query, flags );
3699 // It is presumably a SELECT query
3701 // Push a node onto the stack for the current query. Every level of
3702 // subquery gets its own QueryFrame on the Stack.
3705 // Build an SQL SELECT statement
3708 jsonObjectGetKey( query, "select" ),
3709 jsonObjectGetKey( query, "from" ),
3710 jsonObjectGetKey( query, "where" ),
3711 jsonObjectGetKey( query, "having" ),
3712 jsonObjectGetKey( query, "order_by" ),
3713 jsonObjectGetKey( query, "limit" ),
3714 jsonObjectGetKey( query, "offset" ),
3723 /* method context */ osrfMethodContext* ctx,
3725 /* SELECT */ jsonObject* selhash,
3726 /* FROM */ jsonObject* join_hash,
3727 /* WHERE */ jsonObject* search_hash,
3728 /* HAVING */ jsonObject* having_hash,
3729 /* ORDER BY */ jsonObject* order_hash,
3730 /* LIMIT */ jsonObject* limit,
3731 /* OFFSET */ jsonObject* offset,
3732 /* flags */ int flags
3734 const char* locale = osrf_message_get_last_locale();
3736 // general tmp objects
3737 const jsonObject* tmp_const;
3738 jsonObject* selclass = NULL;
3739 jsonObject* snode = NULL;
3740 jsonObject* onode = NULL;
3742 char* string = NULL;
3743 int from_function = 0;
3748 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3750 // punt if there's no FROM clause
3751 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3754 "%s: FROM clause is missing or empty",
3758 osrfAppSessionStatus(
3760 OSRF_STATUS_INTERNALSERVERERROR,
3761 "osrfMethodException",
3763 "FROM clause is missing or empty in JSON query"
3768 // the core search class
3769 const char* core_class = NULL;
3771 // get the core class -- the only key of the top level FROM clause, or a string
3772 if (join_hash->type == JSON_HASH) {
3773 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3774 snode = jsonIteratorNext( tmp_itr );
3776 // Populate the current QueryFrame with information
3777 // about the core class
3778 if( add_query_core( NULL, tmp_itr->key ) ) {
3780 osrfAppSessionStatus(
3782 OSRF_STATUS_INTERNALSERVERERROR,
3783 "osrfMethodException",
3785 "Unable to look up core class"
3789 core_class = curr_query->core.class_name;
3792 jsonObject* extra = jsonIteratorNext( tmp_itr );
3794 jsonIteratorFree( tmp_itr );
3797 // There shouldn't be more than one entry in join_hash
3801 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3805 osrfAppSessionStatus(
3807 OSRF_STATUS_INTERNALSERVERERROR,
3808 "osrfMethodException",
3810 "Malformed FROM clause in JSON query"
3812 return NULL; // Malformed join_hash; extra entry
3814 } else if (join_hash->type == JSON_ARRAY) {
3815 // We're selecting from a function, not from a table
3817 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
3820 } else if (join_hash->type == JSON_STRING) {
3821 // Populate the current QueryFrame with information
3822 // about the core class
3823 core_class = jsonObjectGetString( join_hash );
3825 if( add_query_core( NULL, core_class ) ) {
3827 osrfAppSessionStatus(
3829 OSRF_STATUS_INTERNALSERVERERROR,
3830 "osrfMethodException",
3832 "Unable to look up core class"
3840 "%s: FROM clause is unexpected JSON type: %s",
3842 json_type( join_hash->type )
3845 osrfAppSessionStatus(
3847 OSRF_STATUS_INTERNALSERVERERROR,
3848 "osrfMethodException",
3850 "Ill-formed FROM clause in JSON query"
3855 // Build the join clause, if any, while filling out the list
3856 // of joined classes in the current QueryFrame.
3857 char* join_clause = NULL;
3858 if( join_hash && ! from_function ) {
3860 join_clause = searchJOIN( join_hash, &curr_query->core );
3861 if( ! join_clause ) {
3863 osrfAppSessionStatus(
3865 OSRF_STATUS_INTERNALSERVERERROR,
3866 "osrfMethodException",
3868 "Unable to construct JOIN clause(s)"
3874 // For in case we don't get a select list
3875 jsonObject* defaultselhash = NULL;
3877 // if there is no select list, build a default select list ...
3878 if (!selhash && !from_function) {
3879 jsonObject* default_list = defaultSelectList( core_class );
3880 if( ! default_list ) {
3882 osrfAppSessionStatus(
3884 OSRF_STATUS_INTERNALSERVERERROR,
3885 "osrfMethodException",
3887 "Unable to build default SELECT clause in JSON query"
3889 free( join_clause );
3894 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3895 jsonObjectSetKey( selhash, core_class, default_list );
3898 // The SELECT clause can be encoded only by a hash
3899 if( !from_function && selhash->type != JSON_HASH ) {
3902 "%s: Expected JSON_HASH for SELECT clause; found %s",
3904 json_type( selhash->type )
3908 osrfAppSessionStatus(
3910 OSRF_STATUS_INTERNALSERVERERROR,
3911 "osrfMethodException",
3913 "Malformed SELECT clause in JSON query"
3915 free( join_clause );
3919 // If you see a null or wild card specifier for the core class, or an
3920 // empty array, replace it with a default SELECT list
3921 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3923 int default_needed = 0; // boolean
3924 if( JSON_STRING == tmp_const->type
3925 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3927 else if( JSON_NULL == tmp_const->type )
3930 if( default_needed ) {
3931 // Build a default SELECT list
3932 jsonObject* default_list = defaultSelectList( core_class );
3933 if( ! default_list ) {
3935 osrfAppSessionStatus(
3937 OSRF_STATUS_INTERNALSERVERERROR,
3938 "osrfMethodException",
3940 "Can't build default SELECT clause in JSON query"
3942 free( join_clause );
3947 jsonObjectSetKey( selhash, core_class, default_list );
3951 // temp buffers for the SELECT list and GROUP BY clause
3952 growing_buffer* select_buf = buffer_init(128);
3953 growing_buffer* group_buf = buffer_init(128);
3955 int aggregate_found = 0; // boolean
3957 // Build a select list
3958 if(from_function) // From a function we select everything
3959 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3962 // Build the SELECT list as SQL
3966 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3967 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3969 const char* cname = selclass_itr->key;
3971 // Make sure the target relation is in the FROM clause.
3973 // At this point join_hash is a step down from the join_hash we
3974 // received as a parameter. If the original was a JSON_STRING,
3975 // then json_hash is now NULL. If the original was a JSON_HASH,
3976 // then json_hash is now the first (and only) entry in it,
3977 // denoting the core class. We've already excluded the
3978 // possibility that the original was a JSON_ARRAY, because in
3979 // that case from_function would be non-NULL, and we wouldn't
3982 // If the current table alias isn't in scope, bail out
3983 ClassInfo* class_info = search_alias( cname );
3984 if( ! class_info ) {
3987 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3992 osrfAppSessionStatus(
3994 OSRF_STATUS_INTERNALSERVERERROR,
3995 "osrfMethodException",
3997 "Selected class not in FROM clause in JSON query"
3999 jsonIteratorFree( selclass_itr );
4000 buffer_free( select_buf );
4001 buffer_free( group_buf );
4002 if( defaultselhash )
4003 jsonObjectFree( defaultselhash );
4004 free( join_clause );
4008 if( selclass->type != JSON_ARRAY ) {
4011 "%s: Malformed SELECT list for class \"%s\"; not an array",
4016 osrfAppSessionStatus(
4018 OSRF_STATUS_INTERNALSERVERERROR,
4019 "osrfMethodException",
4021 "Selected class not in FROM clause in JSON query"
4024 jsonIteratorFree( selclass_itr );
4025 buffer_free( select_buf );
4026 buffer_free( group_buf );
4027 if( defaultselhash )
4028 jsonObjectFree( defaultselhash );
4029 free( join_clause );
4033 // Look up some attributes of the current class
4034 osrfHash* idlClass = class_info->class_def;
4035 osrfHash* class_field_set = class_info->fields;
4036 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4037 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4039 if( 0 == selclass->size ) {
4042 "%s: No columns selected from \"%s\"",
4048 // stitch together the column list for the current table alias...
4049 unsigned long field_idx = 0;
4050 jsonObject* selfield = NULL;
4051 while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4053 // If we need a separator comma, add one
4057 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4060 // if the field specification is a string, add it to the list
4061 if (selfield->type == JSON_STRING) {
4063 // Look up the field in the IDL
4064 const char* col_name = jsonObjectGetString( selfield );
4065 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4067 // No such field in current class
4070 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4076 osrfAppSessionStatus(
4078 OSRF_STATUS_INTERNALSERVERERROR,
4079 "osrfMethodException",
4081 "Selected column not defined in JSON query"
4083 jsonIteratorFree( selclass_itr );
4084 buffer_free( select_buf );
4085 buffer_free( group_buf );
4086 if( defaultselhash )
4087 jsonObjectFree( defaultselhash );
4088 free( join_clause );
4090 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4091 // Virtual field not allowed
4094 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4100 osrfAppSessionStatus(
4102 OSRF_STATUS_INTERNALSERVERERROR,
4103 "osrfMethodException",
4105 "Selected column may not be virtual in JSON query"
4107 jsonIteratorFree( selclass_itr );
4108 buffer_free( select_buf );
4109 buffer_free( group_buf );
4110 if( defaultselhash )
4111 jsonObjectFree( defaultselhash );
4112 free( join_clause );
4118 if (flags & DISABLE_I18N)
4121 i18n = osrfHashGet(field_def, "i18n");
4123 if( str_is_true( i18n ) ) {
4124 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4125 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4126 class_tname, cname, col_name, class_pkey,
4127 cname, class_pkey, locale, col_name );
4129 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"",
4130 cname, col_name, col_name );
4133 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"",
4134 cname, col_name, col_name );
4137 // ... but it could be an object, in which case we check for a Field Transform
4138 } else if (selfield->type == JSON_HASH) {
4140 const char* col_name = jsonObjectGetString(
4141 jsonObjectGetKeyConst( selfield, "column" ) );
4143 // Get the field definition from the IDL
4144 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4146 // No such field in current class
4149 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4155 osrfAppSessionStatus(
4157 OSRF_STATUS_INTERNALSERVERERROR,
4158 "osrfMethodException",
4160 "Selected column is not defined in JSON query"
4162 jsonIteratorFree( selclass_itr );
4163 buffer_free( select_buf );
4164 buffer_free( group_buf );
4165 if( defaultselhash )
4166 jsonObjectFree( defaultselhash );
4167 free( join_clause );
4169 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4170 // No such field in current class
4173 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4179 osrfAppSessionStatus(
4181 OSRF_STATUS_INTERNALSERVERERROR,
4182 "osrfMethodException",
4184 "Selected column is virtual in JSON query"
4186 jsonIteratorFree( selclass_itr );
4187 buffer_free( select_buf );
4188 buffer_free( group_buf );
4189 if( defaultselhash )
4190 jsonObjectFree( defaultselhash );
4191 free( join_clause );
4195 // Decide what to use as a column alias
4197 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4198 _alias = jsonObjectGetString( tmp_const );
4199 } else { // Use field name as the alias
4203 if (jsonObjectGetKeyConst( selfield, "transform" )) {
4204 char* transform_str = searchFieldTransform(
4205 class_info->alias, field_def, selfield );
4206 if( transform_str ) {
4207 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
4208 free(transform_str);
4211 osrfAppSessionStatus(
4213 OSRF_STATUS_INTERNALSERVERERROR,
4214 "osrfMethodException",
4216 "Unable to generate transform function in JSON query"
4218 jsonIteratorFree( selclass_itr );
4219 buffer_free( select_buf );
4220 buffer_free( group_buf );
4221 if( defaultselhash )
4222 jsonObjectFree( defaultselhash );
4223 free( join_clause );
4230 if (flags & DISABLE_I18N)
4233 i18n = osrfHashGet(field_def, "i18n");
4235 if( str_is_true( i18n ) ) {
4236 buffer_fadd( select_buf,
4237 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4238 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4239 class_tname, cname, col_name, class_pkey, cname,
4240 class_pkey, locale, _alias);
4242 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4243 cname, col_name, _alias );
4246 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4247 cname, col_name, _alias);
4254 "%s: Selected item is unexpected JSON type: %s",
4256 json_type( selfield->type )
4259 osrfAppSessionStatus(
4261 OSRF_STATUS_INTERNALSERVERERROR,
4262 "osrfMethodException",
4264 "Ill-formed SELECT item in JSON query"
4266 jsonIteratorFree( selclass_itr );
4267 buffer_free( select_buf );
4268 buffer_free( group_buf );
4269 if( defaultselhash )
4270 jsonObjectFree( defaultselhash );
4271 free( join_clause );
4275 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
4276 if( obj_is_true( agg_obj ) )
4277 aggregate_found = 1;
4279 // Append a comma (except for the first one)
4280 // and add the column to a GROUP BY clause
4284 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4286 buffer_fadd(group_buf, " %d", sel_pos);
4290 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4292 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4293 if ( ! obj_is_true( aggregate_obj ) ) {
4297 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4300 buffer_fadd(group_buf, " %d", sel_pos);
4303 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4307 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4310 _column = searchFieldTransform(class_info->alias, field, selfield);
4311 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4312 OSRF_BUFFER_ADD(group_buf, _column);
4313 _column = searchFieldTransform(class_info->alias, field, selfield);
4320 } // end while -- iterating across SELECT columns
4322 } // end while -- iterating across classes
4324 jsonIteratorFree(selclass_itr);
4328 char* col_list = buffer_release(select_buf);
4330 // Make sure the SELECT list isn't empty. This can happen, for example,
4331 // if we try to build a default SELECT clause from a non-core table.
4334 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
4336 osrfAppSessionStatus(
4338 OSRF_STATUS_INTERNALSERVERERROR,
4339 "osrfMethodException",
4341 "SELECT list is empty"
4344 buffer_free( group_buf );
4345 if( defaultselhash )
4346 jsonObjectFree( defaultselhash );
4347 free( join_clause );
4352 if (from_function) table = searchValueTransform(join_hash);
4353 else table = strdup( curr_query->core.source_def );
4357 osrfAppSessionStatus(
4359 OSRF_STATUS_INTERNALSERVERERROR,
4360 "osrfMethodException",
4362 "Unable to identify table for core class"
4365 buffer_free( group_buf );
4366 if( defaultselhash )
4367 jsonObjectFree( defaultselhash );
4368 free( join_clause );
4372 // Put it all together
4373 growing_buffer* sql_buf = buffer_init(128);
4374 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4378 // Append the join clause, if any
4380 buffer_add(sql_buf, join_clause);
4384 char* order_by_list = NULL;
4385 char* having_buf = NULL;
4387 if (!from_function) {
4389 // Build a WHERE clause, if there is one
4390 if ( search_hash ) {
4391 buffer_add(sql_buf, " WHERE ");
4393 // and it's on the WHERE clause
4394 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4397 osrfAppSessionStatus(
4399 OSRF_STATUS_INTERNALSERVERERROR,
4400 "osrfMethodException",
4402 "Severe query error in WHERE predicate -- see error log for more details"
4405 buffer_free(group_buf);
4406 buffer_free(sql_buf);
4408 jsonObjectFree(defaultselhash);
4412 buffer_add(sql_buf, pred);
4416 // Build a HAVING clause, if there is one
4417 if ( having_hash ) {
4419 // and it's on the the WHERE clause
4420 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4422 if( ! having_buf ) {
4424 osrfAppSessionStatus(
4426 OSRF_STATUS_INTERNALSERVERERROR,
4427 "osrfMethodException",
4429 "Severe query error in HAVING predicate -- see error log for more details"
4432 buffer_free(group_buf);
4433 buffer_free(sql_buf);
4435 jsonObjectFree(defaultselhash);
4440 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4442 // Build an ORDER BY clause, if there is one
4443 if( NULL == order_hash )
4444 ; // No ORDER BY? do nothing
4445 else if( JSON_ARRAY == order_hash->type ) {
4446 // Array of field specifications, each specification being a
4447 // hash to define the class, field, and other details
4449 jsonObject* order_spec;
4450 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4452 if( JSON_HASH != order_spec->type ) {
4453 osrfLogError(OSRF_LOG_MARK,
4454 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4455 MODULENAME, json_type( order_spec->type ) );
4457 osrfAppSessionStatus(
4459 OSRF_STATUS_INTERNALSERVERERROR,
4460 "osrfMethodException",
4462 "Malformed ORDER BY clause -- see error log for more details"
4464 buffer_free( order_buf );
4466 buffer_free(group_buf);
4467 buffer_free(sql_buf);
4469 jsonObjectFree(defaultselhash);
4473 const char* class_alias =
4474 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4476 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4479 OSRF_BUFFER_ADD(order_buf, ", ");
4481 order_buf = buffer_init(128);
4483 if( !field || !class_alias ) {
4484 osrfLogError( OSRF_LOG_MARK,
4485 "%s: Missing class or field name in field specification "
4486 "of ORDER BY clause",
4489 osrfAppSessionStatus(
4491 OSRF_STATUS_INTERNALSERVERERROR,
4492 "osrfMethodException",
4494 "Malformed ORDER BY clause -- see error log for more details"
4496 buffer_free( order_buf );
4498 buffer_free(group_buf);
4499 buffer_free(sql_buf);
4501 jsonObjectFree(defaultselhash);
4505 ClassInfo* order_class_info = search_alias( class_alias );
4506 if( ! order_class_info ) {
4507 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4508 "not in FROM clause", MODULENAME, class_alias );
4510 osrfAppSessionStatus(
4512 OSRF_STATUS_INTERNALSERVERERROR,
4513 "osrfMethodException",
4515 "Invalid class referenced in ORDER BY clause -- "
4516 "see error log for more details"
4519 buffer_free(group_buf);
4520 buffer_free(sql_buf);
4522 jsonObjectFree(defaultselhash);
4526 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4528 osrfLogError( OSRF_LOG_MARK,
4529 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4530 MODULENAME, class_alias, field );
4532 osrfAppSessionStatus(
4534 OSRF_STATUS_INTERNALSERVERERROR,
4535 "osrfMethodException",
4537 "Invalid field referenced in ORDER BY clause -- "
4538 "see error log for more details"
4541 buffer_free(group_buf);
4542 buffer_free(sql_buf);
4544 jsonObjectFree(defaultselhash);
4546 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4547 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4548 MODULENAME, field );
4550 osrfAppSessionStatus(
4552 OSRF_STATUS_INTERNALSERVERERROR,
4553 "osrfMethodException",
4555 "Virtual field in ORDER BY clause -- see error log for more details"
4557 buffer_free( order_buf );
4559 buffer_free(group_buf);
4560 buffer_free(sql_buf);
4562 jsonObjectFree(defaultselhash);
4566 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4567 char* transform_str = searchFieldTransform(
4568 class_alias, field_def, order_spec );
4569 if( ! transform_str ) {
4571 osrfAppSessionStatus(
4573 OSRF_STATUS_INTERNALSERVERERROR,
4574 "osrfMethodException",
4576 "Severe query error in ORDER BY clause -- "
4577 "see error log for more details"
4579 buffer_free( order_buf );
4581 buffer_free(group_buf);
4582 buffer_free(sql_buf);
4584 jsonObjectFree(defaultselhash);
4588 OSRF_BUFFER_ADD( order_buf, transform_str );
4589 free( transform_str );
4592 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4594 const char* direction =
4595 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4597 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4598 OSRF_BUFFER_ADD( order_buf, " DESC" );
4600 OSRF_BUFFER_ADD( order_buf, " ASC" );
4603 } else if( JSON_HASH == order_hash->type ) {
4604 // This hash is keyed on class alias. Each class has either
4605 // an array of field names or a hash keyed on field name.
4606 jsonIterator* class_itr = jsonNewIterator( order_hash );
4607 while ( (snode = jsonIteratorNext( class_itr )) ) {
4609 ClassInfo* order_class_info = search_alias( class_itr->key );
4610 if( ! order_class_info ) {
4611 osrfLogError(OSRF_LOG_MARK,
4612 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4613 MODULENAME, class_itr->key );
4615 osrfAppSessionStatus(
4617 OSRF_STATUS_INTERNALSERVERERROR,
4618 "osrfMethodException",
4620 "Invalid class referenced in ORDER BY clause -- "
4621 "see error log for more details"
4623 jsonIteratorFree( class_itr );
4624 buffer_free( order_buf );
4626 buffer_free(group_buf);
4627 buffer_free(sql_buf);
4629 jsonObjectFree(defaultselhash);
4633 osrfHash* field_list_def = order_class_info->fields;
4635 if ( snode->type == JSON_HASH ) {
4637 // Hash is keyed on field names from the current class. For each field
4638 // there is another layer of hash to define the sorting details, if any,
4639 // or a string to indicate direction of sorting.
4640 jsonIterator* order_itr = jsonNewIterator( snode );
4641 while ( (onode = jsonIteratorNext( order_itr )) ) {
4643 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4645 osrfLogError( OSRF_LOG_MARK,
4646 "%s: Invalid field \"%s\" in ORDER BY clause",
4647 MODULENAME, order_itr->key );
4649 osrfAppSessionStatus(
4651 OSRF_STATUS_INTERNALSERVERERROR,
4652 "osrfMethodException",
4654 "Invalid field in ORDER BY clause -- see error log for more details"
4656 jsonIteratorFree( order_itr );
4657 jsonIteratorFree( class_itr );
4658 buffer_free( order_buf );
4660 buffer_free(group_buf);
4661 buffer_free(sql_buf);
4663 jsonObjectFree(defaultselhash);
4665 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4666 osrfLogError( OSRF_LOG_MARK,
4667 "%s: Virtual field \"%s\" in ORDER BY clause",
4668 MODULENAME, order_itr->key );
4670 osrfAppSessionStatus(
4672 OSRF_STATUS_INTERNALSERVERERROR,
4673 "osrfMethodException",
4675 "Virtual field in ORDER BY clause -- "
4676 "see error log for more details"
4678 jsonIteratorFree( order_itr );
4679 jsonIteratorFree( class_itr );
4680 buffer_free( order_buf );
4682 buffer_free(group_buf);
4683 buffer_free(sql_buf);
4685 jsonObjectFree(defaultselhash);
4689 const char* direction = NULL;
4690 if ( onode->type == JSON_HASH ) {
4691 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4692 string = searchFieldTransform(
4694 osrfHashGet( field_list_def, order_itr->key ),
4698 if( ctx ) osrfAppSessionStatus(
4700 OSRF_STATUS_INTERNALSERVERERROR,
4701 "osrfMethodException",
4703 "Severe query error in ORDER BY clause -- "
4704 "see error log for more details"
4706 jsonIteratorFree( order_itr );
4707 jsonIteratorFree( class_itr );
4709 buffer_free(group_buf);
4710 buffer_free(order_buf);
4711 buffer_free(sql_buf);
4713 jsonObjectFree(defaultselhash);
4717 growing_buffer* field_buf = buffer_init(16);
4718 buffer_fadd( field_buf, "\"%s\".%s",
4719 class_itr->key, order_itr->key );
4720 string = buffer_release(field_buf);
4723 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4724 const char* dir = jsonObjectGetString(tmp_const);
4725 if (!strncasecmp(dir, "d", 1)) {
4726 direction = " DESC";
4732 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4733 osrfLogError( OSRF_LOG_MARK,
4734 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4735 MODULENAME, json_type( onode->type ) );
4737 osrfAppSessionStatus(
4739 OSRF_STATUS_INTERNALSERVERERROR,
4740 "osrfMethodException",
4742 "Malformed ORDER BY clause -- see error log for more details"
4744 jsonIteratorFree( order_itr );
4745 jsonIteratorFree( class_itr );
4747 buffer_free(group_buf);
4748 buffer_free(order_buf);
4749 buffer_free(sql_buf);
4751 jsonObjectFree(defaultselhash);
4755 string = strdup(order_itr->key);
4756 const char* dir = jsonObjectGetString(onode);
4757 if (!strncasecmp(dir, "d", 1)) {
4758 direction = " DESC";
4765 OSRF_BUFFER_ADD(order_buf, ", ");
4767 order_buf = buffer_init(128);
4769 OSRF_BUFFER_ADD(order_buf, string);
4773 OSRF_BUFFER_ADD(order_buf, direction);
4777 jsonIteratorFree(order_itr);
4779 } else if ( snode->type == JSON_ARRAY ) {
4781 // Array is a list of fields from the current class
4782 unsigned long order_idx = 0;
4783 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4785 const char* _f = jsonObjectGetString( onode );
4787 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4789 osrfLogError( OSRF_LOG_MARK,
4790 "%s: Invalid field \"%s\" in ORDER BY clause",
4793 osrfAppSessionStatus(
4795 OSRF_STATUS_INTERNALSERVERERROR,
4796 "osrfMethodException",
4798 "Invalid field in ORDER BY clause -- "
4799 "see error log for more details"
4801 jsonIteratorFree( class_itr );
4802 buffer_free( order_buf );
4804 buffer_free(group_buf);
4805 buffer_free(sql_buf);
4807 jsonObjectFree(defaultselhash);
4809 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4810 osrfLogError( OSRF_LOG_MARK,
4811 "%s: Virtual field \"%s\" in ORDER BY clause",
4814 osrfAppSessionStatus(
4816 OSRF_STATUS_INTERNALSERVERERROR,
4817 "osrfMethodException",
4819 "Virtual field in ORDER BY clause -- "
4820 "see error log for more details"
4822 jsonIteratorFree( class_itr );
4823 buffer_free( order_buf );
4825 buffer_free(group_buf);
4826 buffer_free(sql_buf);
4828 jsonObjectFree(defaultselhash);
4833 OSRF_BUFFER_ADD(order_buf, ", ");
4835 order_buf = buffer_init(128);
4837 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
4841 // IT'S THE OOOOOOOOOOOLD STYLE!
4843 osrfLogError(OSRF_LOG_MARK,
4844 "%s: Possible SQL injection attempt; direct order by is not allowed",
4847 osrfAppSessionStatus(
4849 OSRF_STATUS_INTERNALSERVERERROR,
4850 "osrfMethodException",
4852 "Severe query error -- see error log for more details"
4857 buffer_free(group_buf);
4858 buffer_free(order_buf);
4859 buffer_free(sql_buf);
4861 jsonObjectFree(defaultselhash);
4862 jsonIteratorFree(class_itr);
4866 jsonIteratorFree( class_itr );
4868 osrfLogError(OSRF_LOG_MARK,
4869 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4870 MODULENAME, json_type( order_hash->type ) );
4872 osrfAppSessionStatus(
4874 OSRF_STATUS_INTERNALSERVERERROR,
4875 "osrfMethodException",
4877 "Malformed ORDER BY clause -- see error log for more details"
4879 buffer_free( order_buf );
4881 buffer_free(group_buf);
4882 buffer_free(sql_buf);
4884 jsonObjectFree(defaultselhash);
4889 order_by_list = buffer_release( order_buf );
4893 string = buffer_release(group_buf);
4895 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4896 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4897 OSRF_BUFFER_ADD( sql_buf, string );
4902 if( having_buf && *having_buf ) {
4903 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4904 OSRF_BUFFER_ADD( sql_buf, having_buf );
4908 if( order_by_list ) {
4910 if ( *order_by_list ) {
4911 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4912 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4915 free( order_by_list );
4919 const char* str = jsonObjectGetString(limit);
4920 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
4924 const char* str = jsonObjectGetString(offset);
4925 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
4928 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4931 jsonObjectFree(defaultselhash);
4933 return buffer_release(sql_buf);
4935 } // end of SELECT()
4937 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4939 const char* locale = osrf_message_get_last_locale();
4941 osrfHash* fields = osrfHashGet(meta, "fields");
4942 char* core_class = osrfHashGet(meta, "classname");
4944 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4946 jsonObject* node = NULL;
4947 jsonObject* snode = NULL;
4948 jsonObject* onode = NULL;
4949 const jsonObject* _tmp = NULL;
4950 jsonObject* selhash = NULL;
4951 jsonObject* defaultselhash = NULL;
4953 growing_buffer* sql_buf = buffer_init(128);
4954 growing_buffer* select_buf = buffer_init(128);
4956 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4957 defaultselhash = jsonNewObjectType(JSON_HASH);
4958 selhash = defaultselhash;
4961 // If there's no SELECT list for the core class, build one
4962 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4963 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4965 // Add every non-virtual field to the field list
4966 osrfHash* field_def = NULL;
4967 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4968 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4969 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4970 const char* field = osrfHashIteratorKey( field_itr );
4971 jsonObjectPush( field_list, jsonNewObject( field ) );
4974 osrfHashIteratorFree( field_itr );
4975 jsonObjectSetKey( selhash, core_class, field_list );
4979 jsonIterator* class_itr = jsonNewIterator( selhash );
4980 while ( (snode = jsonIteratorNext( class_itr )) ) {
4982 const char* cname = class_itr->key;
4983 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4987 if (strcmp(core_class,class_itr->key)) {
4991 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4993 jsonObjectFree(found);
4997 jsonObjectFree(found);
5000 jsonIterator* select_itr = jsonNewIterator( snode );
5001 while ( (node = jsonIteratorNext( select_itr )) ) {
5002 const char* item_str = jsonObjectGetString( node );
5003 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5004 char* fname = osrfHashGet(field, "name");
5012 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
5017 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
5018 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5021 i18n = osrfHashGet(field, "i18n");
5023 if( str_is_true( i18n ) ) {
5024 char* pkey = osrfHashGet(idlClass, "primarykey");
5025 char* tname = osrfHashGet(idlClass, "tablename");
5027 buffer_fadd(select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5028 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5029 tname, cname, fname, pkey, cname, pkey, locale, fname);
5031 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
5034 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
5038 jsonIteratorFree(select_itr);
5041 jsonIteratorFree(class_itr);
5043 char* col_list = buffer_release(select_buf);
5044 char* table = getRelation(meta);
5046 table = strdup( "(null)" );
5048 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5052 // Clear the query stack (as a fail-safe precaution against possible
5053 // leftover garbage); then push the first query frame onto the stack.
5054 clear_query_stack();
5056 if( add_query_core( NULL, core_class ) ) {
5058 osrfAppSessionStatus(
5060 OSRF_STATUS_INTERNALSERVERERROR,
5061 "osrfMethodException",
5063 "Unable to build query frame for core class"
5069 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5070 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
5071 OSRF_BUFFER_ADD(sql_buf, join_clause);
5075 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5076 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
5078 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
5080 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5082 osrfAppSessionStatus(
5084 OSRF_STATUS_INTERNALSERVERERROR,
5085 "osrfMethodException",
5087 "Severe query error -- see error log for more details"
5089 buffer_free(sql_buf);
5091 jsonObjectFree(defaultselhash);
5092 clear_query_stack();
5095 buffer_add(sql_buf, pred);
5100 char* string = NULL;
5101 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
5103 growing_buffer* order_buf = buffer_init(128);
5106 jsonIterator* class_itr = jsonNewIterator( _tmp );
5107 while ( (snode = jsonIteratorNext( class_itr )) ) {
5109 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
5112 if ( snode->type == JSON_HASH ) {
5114 jsonIterator* order_itr = jsonNewIterator( snode );
5115 while ( (onode = jsonIteratorNext( order_itr )) ) {
5117 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
5118 class_itr->key, order_itr->key );
5122 char* direction = NULL;
5123 if ( onode->type == JSON_HASH ) {
5124 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
5125 string = searchFieldTransform( class_itr->key, field_def, onode );
5127 osrfAppSessionStatus(
5129 OSRF_STATUS_INTERNALSERVERERROR,
5130 "osrfMethodException",
5132 "Severe query error in ORDER BY clause -- "
5133 "see error log for more details"
5135 jsonIteratorFree( order_itr );
5136 jsonIteratorFree( class_itr );
5137 buffer_free( order_buf );
5138 buffer_free( sql_buf );
5139 if( defaultselhash )
5140 jsonObjectFree( defaultselhash );
5141 clear_query_stack();
5145 growing_buffer* field_buf = buffer_init(16);
5146 buffer_fadd( field_buf, "\"%s\".%s",
5147 class_itr->key, order_itr->key );
5148 string = buffer_release(field_buf);
5151 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
5152 const char* dir = jsonObjectGetString(_tmp);
5153 if (!strncasecmp(dir, "d", 1)) {
5154 direction = " DESC";
5160 string = strdup(order_itr->key);
5161 const char* dir = jsonObjectGetString(onode);
5162 if (!strncasecmp(dir, "d", 1)) {
5163 direction = " DESC";
5172 buffer_add(order_buf, ", ");
5175 buffer_add(order_buf, string);
5179 buffer_add(order_buf, direction);
5183 jsonIteratorFree(order_itr);
5186 const char* str = jsonObjectGetString(snode);
5187 buffer_add(order_buf, str);
5193 jsonIteratorFree(class_itr);
5195 string = buffer_release(order_buf);
5198 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5199 OSRF_BUFFER_ADD( sql_buf, string );
5205 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
5206 const char* str = jsonObjectGetString(_tmp);
5214 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
5216 const char* str = jsonObjectGetString(_tmp);
5226 jsonObjectFree(defaultselhash);
5227 clear_query_stack();
5229 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
5230 return buffer_release(sql_buf);
5233 int doJSONSearch ( osrfMethodContext* ctx ) {
5234 if(osrfMethodVerifyContext( ctx )) {
5235 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5239 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
5244 dbhandle = writehandle;
5246 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
5250 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
5251 flags |= SELECT_DISTINCT;
5253 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
5254 flags |= DISABLE_I18N;
5256 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
5257 clear_query_stack(); // a possibly needless precaution
5258 char* sql = buildQuery( ctx, hash, flags );
5259 clear_query_stack();
5266 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
5267 dbi_result result = dbi_conn_query(dbhandle, sql);
5270 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
5272 if (dbi_result_first_row(result)) {
5273 /* JSONify the result */
5274 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
5277 jsonObject* return_val = oilsMakeJSONFromResult( result );
5278 osrfAppRespond( ctx, return_val );
5279 jsonObjectFree( return_val );
5280 } while (dbi_result_next_row(result));
5283 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
5286 osrfAppRespondComplete( ctx, NULL );
5288 /* clean up the query */
5289 dbi_result_free(result);
5293 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
5294 osrfAppSessionStatus(
5296 OSRF_STATUS_INTERNALSERVERERROR,
5297 "osrfMethodException",
5299 "Severe query error -- see error log for more details"
5307 // The last parameter, err, is used to report an error condition by updating an int owned by
5308 // the calling code.
5310 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5311 // It is the responsibility of the calling code to initialize *err before the
5312 // call, so that it will be able to make sense of the result.
5314 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5315 // redundant anyway.
5316 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
5317 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5320 dbhandle = writehandle;
5322 char* core_class = osrfHashGet( class_meta, "classname" );
5323 char* pkey = osrfHashGet( class_meta, "primarykey" );
5325 const jsonObject* _tmp;
5327 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5329 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
5334 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
5336 dbi_result result = dbi_conn_query(dbhandle, sql);
5337 if( NULL == result ) {
5338 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
5339 MODULENAME, osrfHashGet( class_meta, "fieldmapper" ), sql);
5340 osrfAppSessionStatus(
5342 OSRF_STATUS_INTERNALSERVERERROR,
5343 "osrfMethodException",
5345 "Severe query error -- see error log for more details"
5352 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
5355 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
5356 jsonObject* row_obj = NULL;
5358 if (dbi_result_first_row(result)) {
5360 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5361 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5362 // eliminate the duplicates.
5363 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
5364 osrfHash* dedup = osrfNewHash();
5366 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5367 char* pkey_val = oilsFMGetString( row_obj, pkey );
5368 if ( osrfHashGet( dedup, pkey_val ) ) {
5369 jsonObjectFree( row_obj );
5372 osrfHashSet( dedup, pkey_val, pkey_val );
5373 jsonObjectPush( res_list, row_obj );
5375 } while (dbi_result_next_row(result));
5376 osrfHashFree(dedup);
5379 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
5383 /* clean up the query */
5384 dbi_result_free(result);
5387 // If we're asked to flesh, and there's anything to flesh, then flesh.
5388 if (res_list->size && query_hash) {
5389 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5391 // Get the flesh depth
5392 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5393 if ( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5394 flesh_depth = max_flesh_depth;
5396 // We need a non-zero flesh depth, and a list of fields to flesh
5397 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5398 if ( temp_blob && flesh_depth > 0 ) {
5400 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5401 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5403 osrfStringArray* link_fields = NULL;
5404 osrfHash* links = osrfHashGet( class_meta, "links" );
5406 // Make an osrfStringArray of the names of fields to be fleshed
5408 if (flesh_fields->size == 1) {
5409 const char* _t = jsonObjectGetString(
5410 jsonObjectGetIndex( flesh_fields, 0 ) );
5411 if (!strcmp(_t,"*"))
5412 link_fields = osrfHashKeys( links );
5417 link_fields = osrfNewStringArray(1);
5418 jsonIterator* _i = jsonNewIterator( flesh_fields );
5419 while ((_f = jsonIteratorNext( _i ))) {
5420 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5422 jsonIteratorFree(_i);
5426 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5428 // Iterate over the JSON_ARRAY of rows
5430 unsigned long res_idx = 0;
5431 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5434 const char* link_field;
5436 // Iterate over the list of fleshable fields
5437 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5439 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
5441 osrfHash* kid_link = osrfHashGet(links, link_field);
5443 continue; // Not a link field; skip it
5445 osrfHash* field = osrfHashGet(fields, link_field);
5447 continue; // Not a field at all; skip it (IDL is ill-formed)
5449 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
5451 continue; // The class it links to doesn't exist; skip it
5453 const char* reltype = osrfHashGet( kid_link, "reltype" );
5455 continue; // No reltype; skip it (IDL is ill-formed)
5457 osrfHash* value_field = field;
5459 if ( !strcmp( reltype, "has_many" )
5460 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5461 value_field = osrfHashGet(
5462 fields, osrfHashGet( class_meta, "primarykey" ) );
5465 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5467 if (link_map->size > 0) {
5468 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
5471 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5476 osrfHashGet(kid_link, "class"),
5483 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5484 osrfHashGet(kid_link, "field"),
5485 osrfHashGet(kid_link, "class"),
5486 osrfHashGet(kid_link, "key"),
5487 osrfHashGet(kid_link, "reltype")
5490 const char* search_key = jsonObjectGetString(
5491 jsonObjectGetIndex( cur,
5492 atoi( osrfHashGet(value_field, "array_position") )
5497 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
5501 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
5503 // construct WHERE clause
5504 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
5507 osrfHashGet(kid_link, "key"),
5508 jsonNewObject( search_key )
5511 // construct the rest of the query, mostly
5512 // by copying pieces of the previous level of query
5513 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
5514 jsonObjectSetKey( rest_of_query, "flesh",
5515 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5519 jsonObjectSetKey( rest_of_query, "flesh_fields",
5520 jsonObjectClone(flesh_blob) );
5522 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
5523 jsonObjectSetKey( rest_of_query, "order_by",
5524 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
5528 if (jsonObjectGetKeyConst(query_hash, "select")) {
5529 jsonObjectSetKey( rest_of_query, "select",
5530 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
5534 // do the query, recursively, to expand the fleshable field
5535 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5536 where_clause, rest_of_query, err);
5538 jsonObjectFree( where_clause );
5539 jsonObjectFree( rest_of_query );
5542 osrfStringArrayFree(link_fields);
5543 jsonObjectFree(res_list);
5544 jsonObjectFree(flesh_blob);
5548 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects",
5549 osrfHashGet(kid_link, "class"), kids->size);
5551 // Traverse the result set
5552 jsonObject* X = NULL;
5553 if ( link_map->size > 0 && kids->size > 0 ) {
5555 kids = jsonNewObjectType(JSON_ARRAY);
5557 jsonObject* _k_node;
5558 unsigned long res_idx = 0;
5559 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5565 (unsigned long)atoi(
5571 osrfHashGet(kid_link, "class")
5575 osrfStringArrayGetString( link_map, 0 )
5583 } // end while loop traversing X
5586 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" ))
5587 || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
5588 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5589 osrfHashGet(kid_link, "field"));
5592 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5593 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
5597 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) {
5599 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5600 osrfHashGet( kid_link, "field" ) );
5603 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5604 jsonObjectClone( kids )
5609 jsonObjectFree(kids);
5613 jsonObjectFree( kids );
5615 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5616 osrfHashGet( kid_link, "field" ) );
5617 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
5619 } // end while loop traversing list of fleshable fields
5620 } // end while loop traversing res_list
5621 jsonObjectFree( flesh_blob );
5622 osrfStringArrayFree(link_fields);
5631 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
5633 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5635 jsonObject* target = NULL;
5637 target = jsonObjectGetIndex( ctx->params, 1 );
5639 target = jsonObjectGetIndex( ctx->params, 0 );
5641 if (!verifyObjectClass(ctx, target)) {
5646 if( getXactId( ctx ) == NULL ) {
5647 osrfAppSessionStatus(
5649 OSRF_STATUS_BADREQUEST,
5650 "osrfMethodException",
5652 "No active transaction -- required for UPDATE"
5658 // The following test is harmless but redundant. If a class is
5659 // readonly, we don't register an update method for it.
5660 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5661 osrfAppSessionStatus(
5663 OSRF_STATUS_BADREQUEST,
5664 "osrfMethodException",
5666 "Cannot UPDATE readonly class"
5672 dbhandle = writehandle;
5673 const char* trans_id = getXactId( ctx );
5675 // Set the last_xact_id
5676 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5678 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5679 trans_id, target->classname, index);
5680 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
5683 char* pkey = osrfHashGet(meta, "primarykey");
5684 osrfHash* fields = osrfHashGet(meta, "fields");
5686 char* id = oilsFMGetString( target, pkey );
5690 "%s updating %s object with %s = %s",
5692 osrfHashGet(meta, "fieldmapper"),
5697 growing_buffer* sql = buffer_init(128);
5698 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
5701 osrfHash* field_def = NULL;
5702 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5703 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
5705 // Skip virtual fields, and the primary key
5706 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5709 const char* field_name = osrfHashIteratorKey( field_itr );
5710 if( ! strcmp( field_name, pkey ) )
5713 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5715 int value_is_numeric = 0; // boolean
5717 if (field_object && field_object->classname) {
5718 value = oilsFMGetString(
5720 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
5722 } else if( field_object && JSON_BOOL == field_object->type ) {
5723 if( jsonBoolIsTrue( field_object ) )
5724 value = strdup( "t" );
5726 value = strdup( "f" );
5728 value = jsonObjectToSimpleString( field_object );
5729 if( field_object && JSON_NUMBER == field_object->type )
5730 value_is_numeric = 1;
5733 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5734 osrfHashGet(meta, "fieldmapper"), field_name, value);
5736 if (!field_object || field_object->type == JSON_NULL) {
5737 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
5738 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5739 if (first) first = 0;
5740 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5741 buffer_fadd( sql, " %s = NULL", field_name );
5744 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5745 if (first) first = 0;
5746 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5748 const char* numtype = get_datatype( field_def );
5749 if ( !strncmp( numtype, "INT", 3 ) ) {
5750 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
5751 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
5752 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
5754 // Must really be intended as a string, so quote it
5755 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5756 buffer_fadd( sql, " %s = %s", field_name, value );
5758 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5760 osrfAppSessionStatus(
5762 OSRF_STATUS_INTERNALSERVERERROR,
5763 "osrfMethodException",
5765 "Error quoting string -- please see the error log for more details"
5769 osrfHashIteratorFree( field_itr );
5776 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5779 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5780 if (first) first = 0;
5781 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5782 buffer_fadd( sql, " %s = %s", field_name, value );
5785 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5786 osrfAppSessionStatus(
5788 OSRF_STATUS_INTERNALSERVERERROR,
5789 "osrfMethodException",
5791 "Error quoting string -- please see the error log for more details"
5795 osrfHashIteratorFree( field_itr );
5806 osrfHashIteratorFree( field_itr );
5808 jsonObject* obj = jsonNewObject(id);
5810 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5811 dbi_conn_quote_string(dbhandle, &id);
5813 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5815 char* query = buffer_release(sql);
5816 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
5818 dbi_result result = dbi_conn_query(dbhandle, query);
5822 jsonObjectFree(obj);
5823 obj = jsonNewObject(NULL);
5826 "%s ERROR updating %s object with %s = %s",
5828 osrfHashGet(meta, "fieldmapper"),
5839 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
5841 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5843 if( getXactId( ctx ) == NULL ) {
5844 osrfAppSessionStatus(
5846 OSRF_STATUS_BADREQUEST,
5847 "osrfMethodException",
5849 "No active transaction -- required for DELETE"
5855 // The following test is harmless but redundant. If a class is
5856 // readonly, we don't register a delete method for it.
5857 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5858 osrfAppSessionStatus(
5860 OSRF_STATUS_BADREQUEST,
5861 "osrfMethodException",
5863 "Cannot DELETE readonly class"
5869 dbhandle = writehandle;
5873 char* pkey = osrfHashGet(meta, "primarykey");
5880 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
5881 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5886 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5888 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5892 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
5897 "%s deleting %s object with %s = %s",
5899 osrfHashGet(meta, "fieldmapper"),
5904 obj = jsonNewObject(id);
5906 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5907 dbi_conn_quote_string(writehandle, &id);
5909 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;",
5910 osrfHashGet(meta, "tablename"), pkey, id);
5913 jsonObjectFree(obj);
5914 obj = jsonNewObject(NULL);
5917 "%s ERROR deleting %s object with %s = %s",
5919 osrfHashGet(meta, "fieldmapper"),
5931 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5932 @param result An iterator for a result set; we only look at the current row.
5933 @param @meta Pointer to the class metadata for the core class.
5934 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5936 If a column is not defined in the IDL, or if it has no array_position defined for it in
5937 the IDL, or if it is defined as virtual, ignore it.
5939 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5940 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5941 array_position in the IDL.
5943 A field defined in the IDL but not represented in the returned row will leave a hole
5944 in the JSON_ARRAY. In effect it will be treated as a null value.
5946 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5947 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5948 classname corresponding to the @a meta argument.
5950 The calling code is responsible for freeing the the resulting jsonObject by calling
5953 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5954 if(!(result && meta)) return jsonNULL;
5956 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5957 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
5958 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
5960 osrfHash* fields = osrfHashGet(meta, "fields");
5962 int columnIndex = 1;
5963 const char* columnName;
5965 /* cycle through the columns in the row returned from the database */
5966 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5968 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5970 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
5972 /* determine the field type and storage attributes */
5973 unsigned short type = dbi_result_get_field_type_idx(result, columnIndex);
5974 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5976 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
5977 // or if it has no sequence number there, or if it's virtual, skip it.
5978 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
5981 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
5982 continue; // skip this column: IDL says it's virtual
5984 const char* pos = (char*)osrfHashGet(_f, "array_position");
5985 if ( !pos ) // IDL has no sequence number for it. This shouldn't happen,
5986 continue; // since we assign sequence numbers dynamically as we load the IDL.
5988 fmIndex = atoi( pos );
5989 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
5991 continue; // This field is not defined in the IDL
5994 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
5995 // sequence number from the IDL (which is likely to be different from the sequence
5996 // of columns in the SELECT clause).
5997 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5998 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
6003 case DBI_TYPE_INTEGER :
6005 if( attr & DBI_INTEGER_SIZE8 )
6006 jsonObjectSetIndex( object, fmIndex,
6007 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
6009 jsonObjectSetIndex( object, fmIndex,
6010 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
6014 case DBI_TYPE_DECIMAL :
6015 jsonObjectSetIndex( object, fmIndex,
6016 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
6019 case DBI_TYPE_STRING :
6024 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
6029 case DBI_TYPE_DATETIME : {
6031 char dt_string[256] = "";
6034 // Fetch the date column as a time_t
6035 time_t _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
6037 // Translate the time_t to a human-readable string
6038 if (!(attr & DBI_DATETIME_DATE)) {
6039 gmtime_r( &_tmp_dt, &gmdt );
6040 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
6041 } else if (!(attr & DBI_DATETIME_TIME)) {
6042 localtime_r( &_tmp_dt, &gmdt );
6043 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
6045 localtime_r( &_tmp_dt, &gmdt );
6046 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
6049 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
6053 case DBI_TYPE_BINARY :
6054 osrfLogError( OSRF_LOG_MARK,
6055 "Can't do binary at column %s : index %d", columnName, columnIndex);
6064 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6065 if(!result) return jsonNULL;
6067 jsonObject* object = jsonNewObject(NULL);
6070 char dt_string[256];
6074 int columnIndex = 1;
6076 unsigned short type;
6077 const char* columnName;
6079 /* cycle through the column list */
6080 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
6082 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
6084 fmIndex = -1; // reset the position
6086 /* determine the field type and storage attributes */
6087 type = dbi_result_get_field_type_idx(result, columnIndex);
6088 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
6090 if (dbi_result_field_is_null_idx(result, columnIndex)) {
6091 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
6096 case DBI_TYPE_INTEGER :
6098 if( attr & DBI_INTEGER_SIZE8 )
6099 jsonObjectSetKey( object, columnName,
6100 jsonNewNumberObject(dbi_result_get_longlong_idx(
6101 result, columnIndex)) );
6103 jsonObjectSetKey( object, columnName,
6104 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
6107 case DBI_TYPE_DECIMAL :
6108 jsonObjectSetKey( object, columnName,
6109 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
6112 case DBI_TYPE_STRING :
6113 jsonObjectSetKey( object, columnName,
6114 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
6117 case DBI_TYPE_DATETIME :
6119 memset(dt_string, '\0', sizeof(dt_string));
6120 memset(&gmdt, '\0', sizeof(gmdt));
6122 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
6125 if (!(attr & DBI_DATETIME_DATE)) {
6126 gmtime_r( &_tmp_dt, &gmdt );
6127 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
6128 } else if (!(attr & DBI_DATETIME_TIME)) {
6129 localtime_r( &_tmp_dt, &gmdt );
6130 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
6132 localtime_r( &_tmp_dt, &gmdt );
6133 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
6136 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
6139 case DBI_TYPE_BINARY :
6140 osrfLogError( OSRF_LOG_MARK,
6141 "Can't do binary at column %s : index %d", columnName, columnIndex );
6145 } // end while loop traversing result
6150 // Interpret a string as true or false
6151 static int str_is_true( const char* str ) {
6152 if( NULL == str || strcasecmp( str, "true" ) )
6158 // Interpret a jsonObject as true or false
6159 static int obj_is_true( const jsonObject* obj ) {
6162 else switch( obj->type )
6170 if( strcasecmp( obj->value.s, "true" ) )
6174 case JSON_NUMBER : // Support 1/0 for perl's sake
6175 if( jsonObjectGetNumber( obj ) == 1.0 )
6184 // Translate a numeric code into a text string identifying a type of
6185 // jsonObject. To be used for building error messages.
6186 static const char* json_type( int code ) {
6192 return "JSON_ARRAY";
6194 return "JSON_STRING";
6196 return "JSON_NUMBER";
6202 return "(unrecognized)";
6206 // Extract the "primitive" attribute from an IDL field definition.
6207 // If we haven't initialized the app, then we must be running in
6208 // some kind of testbed. In that case, default to "string".
6209 static const char* get_primitive( osrfHash* field ) {
6210 const char* s = osrfHashGet( field, "primitive" );
6212 if( child_initialized )
6215 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6217 osrfHashGet( field, "name" )
6225 // Extract the "datatype" attribute from an IDL field definition.
6226 // If we haven't initialized the app, then we must be running in
6227 // some kind of testbed. In that case, default to to NUMERIC,
6228 // since we look at the datatype only for numbers.
6229 static const char* get_datatype( osrfHash* field ) {
6230 const char* s = osrfHashGet( field, "datatype" );
6232 if( child_initialized )
6235 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6237 osrfHashGet( field, "name" )
6246 @brief Determine whether a string is potentially a valid SQL identifier.
6247 @param s The identifier to be tested.
6248 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6250 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6251 need to follow all the rules exactly, such as requiring that the first character not
6254 We allow leading and trailing white space. In between, we do not allow punctuation
6255 (except for underscores and dollar signs), control characters, or embedded white space.
6257 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6258 for the foreseeable future such quoted identifiers are not likely to be an issue.
6260 static int is_identifier( const char* s) {
6264 // Skip leading white space
6265 while( isspace( (unsigned char) *s ) )
6269 return 0; // Nothing but white space? Not okay.
6271 // Check each character until we reach white space or
6272 // end-of-string. Letters, digits, underscores, and
6273 // dollar signs are okay. With the exception of periods
6274 // (as in schema.identifier), control characters and other
6275 // punctuation characters are not okay. Anything else
6276 // is okay -- it could for example be part of a multibyte
6277 // UTF8 character such as a letter with diacritical marks,
6278 // and those are allowed.
6280 if( isalnum( (unsigned char) *s )
6284 ; // Fine; keep going
6285 else if( ispunct( (unsigned char) *s )
6286 || iscntrl( (unsigned char) *s ) )
6289 } while( *s && ! isspace( (unsigned char) *s ) );
6291 // If we found any white space in the above loop,
6292 // the rest had better be all white space.
6294 while( isspace( (unsigned char) *s ) )
6298 return 0; // White space was embedded within non-white space
6304 @brief Determine whether to accept a character string as a comparison operator.
6305 @param op The candidate comparison operator.
6306 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6308 We don't validate the operator for real. We just make sure that it doesn't contain
6309 any semicolons or white space (with special exceptions for a few specific operators).
6310 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6311 space but it's still not a valid operator, then the database will complain.
6313 Another approach would be to compare the string against a short list of approved operators.
6314 We don't do that because we want to allow custom operators like ">100*", which at this
6315 writing would be difficult or impossible to express otherwise in a JSON query.
6317 static int is_good_operator( const char* op ) {
6318 if( !op ) return 0; // Sanity check
6322 if( isspace( (unsigned char) *s ) ) {
6323 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6324 // and IS NOT DISTINCT FROM.
6325 if( !strcasecmp( op, "similar to" ) )
6327 else if( !strcasecmp( op, "is distinct from" ) )
6329 else if( !strcasecmp( op, "is not distinct from" ) )
6334 else if( ';' == *s )
6342 @name Query Frame Management
6344 The following machinery supports a stack of query frames for use by SELECT().
6346 A query frame caches information about one level of a SELECT query. When we enter
6347 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6349 The query frame stores information about the core class, and about any joined classes
6352 The main purpose is to map table aliases to classes and tables, so that a query can
6353 join to the same table more than once. A secondary goal is to reduce the number of
6354 lookups in the IDL by caching the results.
6358 #define STATIC_CLASS_INFO_COUNT 3
6360 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6363 @brief Allocate a ClassInfo as raw memory.
6364 @return Pointer to the newly allocated ClassInfo.
6366 Except for the in_use flag, which is used only by the allocation and deallocation
6367 logic, we don't initialize the ClassInfo here.
6369 static ClassInfo* allocate_class_info( void ) {
6370 // In order to reduce the number of mallocs and frees, we return a static
6371 // instance of ClassInfo, if we can find one that we're not already using.
6372 // We rely on the fact that the compiler will implicitly initialize the
6373 // static instances so that in_use == 0.
6376 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6377 if( ! static_class_info[ i ].in_use ) {
6378 static_class_info[ i ].in_use = 1;
6379 return static_class_info + i;
6383 // The static ones are all in use. Malloc one.
6385 return safe_malloc( sizeof( ClassInfo ) );
6389 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6390 @param info Pointer to the ClassInfo to be cleared.
6392 static void clear_class_info( ClassInfo* info ) {
6397 // Free any malloc'd strings
6399 if( info->alias != info->alias_store )
6400 free( info->alias );
6402 if( info->class_name != info->class_name_store )
6403 free( info->class_name );
6405 free( info->source_def );
6407 info->alias = info->class_name = info->source_def = NULL;
6412 @brief Free a ClassInfo and everything it owns.
6413 @param info Pointer to the ClassInfo to be freed.
6415 static void free_class_info( ClassInfo* info ) {
6420 clear_class_info( info );
6422 // If it's one of the static instances, just mark it as not in use
6425 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6426 if( info == static_class_info + i ) {
6427 static_class_info[ i ].in_use = 0;
6432 // Otherwise it must have been malloc'd, so free it
6438 @brief Populate an already-allocated ClassInfo.
6439 @param info Pointer to the ClassInfo to be populated.
6440 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6442 @param class Name of the class.
6443 @return Zero if successful, or 1 if not.
6445 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6446 the relevant portions of the IDL for the specified class.
6448 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6451 osrfLogError( OSRF_LOG_MARK,
6452 "%s ERROR: No ClassInfo available to populate", MODULENAME );
6453 info->alias = info->class_name = info->source_def = NULL;
6454 info->class_def = info->fields = info->links = NULL;
6459 osrfLogError( OSRF_LOG_MARK,
6460 "%s ERROR: No class name provided for lookup", MODULENAME );
6461 info->alias = info->class_name = info->source_def = NULL;
6462 info->class_def = info->fields = info->links = NULL;
6466 // Alias defaults to class name if not supplied
6467 if( ! alias || ! alias[ 0 ] )
6470 // Look up class info in the IDL
6471 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6473 osrfLogError( OSRF_LOG_MARK,
6474 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
6475 info->alias = info->class_name = info->source_def = NULL;
6476 info->class_def = info->fields = info->links = NULL;
6478 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6479 osrfLogError( OSRF_LOG_MARK,
6480 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
6481 info->alias = info->class_name = info->source_def = NULL;
6482 info->class_def = info->fields = info->links = NULL;
6486 osrfHash* links = osrfHashGet( class_def, "links" );
6488 osrfLogError( OSRF_LOG_MARK,
6489 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
6490 info->alias = info->class_name = info->source_def = NULL;
6491 info->class_def = info->fields = info->links = NULL;
6495 osrfHash* fields = osrfHashGet( class_def, "fields" );
6497 osrfLogError( OSRF_LOG_MARK,
6498 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
6499 info->alias = info->class_name = info->source_def = NULL;
6500 info->class_def = info->fields = info->links = NULL;
6504 char* source_def = getRelation( class_def );
6508 // We got everything we need, so populate the ClassInfo
6509 if( strlen( alias ) > ALIAS_STORE_SIZE )
6510 info->alias = strdup( alias );
6512 strcpy( info->alias_store, alias );
6513 info->alias = info->alias_store;
6516 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6517 info->class_name = strdup( class );
6519 strcpy( info->class_name_store, class );
6520 info->class_name = info->class_name_store;
6523 info->source_def = source_def;
6525 info->class_def = class_def;
6526 info->links = links;
6527 info->fields = fields;
6532 #define STATIC_FRAME_COUNT 3
6534 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6537 @brief Allocate a QueryFrame as raw memory.
6538 @return Pointer to the newly allocated QueryFrame.
6540 Except for the in_use flag, which is used only by the allocation and deallocation
6541 logic, we don't initialize the QueryFrame here.
6543 static QueryFrame* allocate_frame( void ) {
6544 // In order to reduce the number of mallocs and frees, we return a static
6545 // instance of QueryFrame, if we can find one that we're not already using.
6546 // We rely on the fact that the compiler will implicitly initialize the
6547 // static instances so that in_use == 0.
6550 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6551 if( ! static_frame[ i ].in_use ) {
6552 static_frame[ i ].in_use = 1;
6553 return static_frame + i;
6557 // The static ones are all in use. Malloc one.
6559 return safe_malloc( sizeof( QueryFrame ) );
6563 @brief Free a QueryFrame, and all the memory it owns.
6564 @param frame Pointer to the QueryFrame to be freed.
6566 static void free_query_frame( QueryFrame* frame ) {
6571 clear_class_info( &frame->core );
6573 // Free the join list
6575 ClassInfo* info = frame->join_list;
6578 free_class_info( info );
6582 frame->join_list = NULL;
6585 // If the frame is a static instance, just mark it as unused
6587 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6588 if( frame == static_frame + i ) {
6589 static_frame[ i ].in_use = 0;
6594 // Otherwise it must have been malloc'd, so free it
6600 @brief Search a given QueryFrame for a specified alias.
6601 @param frame Pointer to the QueryFrame to be searched.
6602 @param target The alias for which to search.
6603 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6605 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6606 if( ! frame || ! target ) {
6610 ClassInfo* found_class = NULL;
6612 if( !strcmp( target, frame->core.alias ) )
6613 return &(frame->core);
6615 ClassInfo* curr_class = frame->join_list;
6616 while( curr_class ) {
6617 if( strcmp( target, curr_class->alias ) )
6618 curr_class = curr_class->next;
6620 found_class = curr_class;
6630 @brief Push a new (blank) QueryFrame onto the stack.
6632 static void push_query_frame( void ) {
6633 QueryFrame* frame = allocate_frame();
6634 frame->join_list = NULL;
6635 frame->next = curr_query;
6637 // Initialize the ClassInfo for the core class
6638 ClassInfo* core = &frame->core;
6639 core->alias = core->class_name = core->source_def = NULL;
6640 core->class_def = core->fields = core->links = NULL;
6646 @brief Pop a QueryFrame off the stack and destroy it.
6648 static void pop_query_frame( void ) {
6653 QueryFrame* popped = curr_query;
6654 curr_query = popped->next;
6656 free_query_frame( popped );
6660 @brief Populate the ClassInfo for the core class.
6661 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6662 class name as an alias.
6663 @param class_name Name of the core class.
6664 @return Zero if successful, or 1 if not.
6666 Populate the ClassInfo of the core class with copies of the alias and class name, and
6667 with pointers to the relevant portions of the IDL for the core class.
6669 static int add_query_core( const char* alias, const char* class_name ) {
6672 if( ! curr_query ) {
6673 osrfLogError( OSRF_LOG_MARK,
6674 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
6676 } else if( curr_query->core.alias ) {
6677 osrfLogError( OSRF_LOG_MARK,
6678 "%s ERROR: Core class %s already populated as %s",
6679 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
6683 build_class_info( &curr_query->core, alias, class_name );
6684 if( curr_query->core.alias )
6687 osrfLogError( OSRF_LOG_MARK,
6688 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
6694 @brief Search the current QueryFrame for a specified alias.
6695 @param target The alias for which to search.
6696 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6698 static inline ClassInfo* search_alias( const char* target ) {
6699 return search_alias_in_frame( curr_query, target );
6703 @brief Search all levels of query for a specified alias, starting with the current query.
6704 @param target The alias for which to search.
6705 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6707 static ClassInfo* search_all_alias( const char* target ) {
6708 ClassInfo* found_class = NULL;
6709 QueryFrame* curr_frame = curr_query;
6711 while( curr_frame ) {
6712 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6715 curr_frame = curr_frame->next;
6722 @brief Add a class to the list of classes joined to the current query.
6723 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6724 the class name as an alias.
6725 @param classname The name of the class to be added.
6726 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6728 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6730 if( ! classname || ! *classname ) { // sanity check
6731 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6738 const ClassInfo* conflict = search_alias( alias );
6740 osrfLogError( OSRF_LOG_MARK,
6741 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6742 MODULENAME, alias, conflict->class_name );
6746 ClassInfo* info = allocate_class_info();
6748 if( build_class_info( info, alias, classname ) ) {
6749 free_class_info( info );
6753 // Add the new ClassInfo to the join list of the current QueryFrame
6754 info->next = curr_query->join_list;
6755 curr_query->join_list = info;
6761 @brief Destroy all nodes on the query stack.
6763 static void clear_query_stack( void ) {