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* ctx );
110 static int doSearch( osrfMethodContext* ctx );
111 static int doIdList( osrfMethodContext* ctx );
112 static int doCreate( osrfMethodContext* ctx );
113 static int doRetrieve( osrfMethodContext* ctx );
114 static int doUpdate( osrfMethodContext* ctx );
115 static int doDelete ( osrfMethodContext* ctx );
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 ) {
1273 // Get the method type, then can branch on it
1274 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1275 const char* methodtype = osrfHashGet( method_meta, "methodtype" );
1277 if( !strcmp( methodtype, "create" ))
1278 return doCreate( ctx );
1279 else if( !strcmp(methodtype, "retrieve" ))
1280 return doRetrieve( ctx );
1281 else if( !strcmp(methodtype, "update" ))
1282 return doUpdate( ctx );
1283 else if( !strcmp(methodtype, "delete" ))
1284 return doDelete( ctx );
1285 else if( !strcmp(methodtype, "search" ))
1286 return doSearch( ctx );
1287 else if( !strcmp(methodtype, "id_list" ))
1288 return doIdList( ctx );
1290 osrfAppRespondComplete( ctx, NULL ); // should be unreachable...
1296 @brief Implement the "search" method.
1297 @param ctx Pointer to the method context.
1298 @return Zero if successful, or -1 if not.
1301 - authkey (PCRUD only)
1302 - WHERE clause, as jsonObject
1303 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1305 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1306 Optionally flesh linked fields.
1308 static int doSearch( osrfMethodContext* ctx ) {
1309 if(osrfMethodVerifyContext( ctx )) {
1310 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1315 timeout_needs_resetting = 1;
1317 jsonObject* where_clause;
1318 jsonObject* rest_of_query;
1320 if( enforce_pcrud ) {
1321 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1322 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1324 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1325 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1328 // Get the class metadata
1329 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1330 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1334 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1336 osrfAppRespondComplete( ctx, NULL );
1340 // Return each row to the client (except that some may be suppressed by PCRUD)
1341 jsonObject* cur = 0;
1342 unsigned long res_idx = 0;
1343 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1344 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1346 osrfAppRespond( ctx, cur );
1348 jsonObjectFree( obj );
1350 osrfAppRespondComplete( ctx, NULL );
1355 @brief Implement the "id_list" method.
1356 @param ctx Pointer to the method context.
1357 @param err Pointer through which to return an error code.
1358 @return Zero if successful, or -1 if not.
1361 - authkey (PCRUD only)
1362 - WHERE clause, as jsonObject
1363 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1365 Return to client: The primary key values for all rows of the relevant class that
1366 satisfy a specified WHERE clause.
1368 This method relies on the assumption that every class has a primary key consisting of
1371 static int doIdList( osrfMethodContext* ctx ) {
1372 if(osrfMethodVerifyContext( ctx )) {
1373 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1378 timeout_needs_resetting = 1;
1380 jsonObject* where_clause;
1381 jsonObject* rest_of_query;
1383 // We use the where clause without change. But we need to massage the rest of the
1384 // query, so we work with a copy of it instead of modifying the original.
1386 if( enforce_pcrud ) {
1387 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1388 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1390 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1391 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1394 // Eliminate certain SQL clauses, if present.
1395 if ( rest_of_query ) {
1396 jsonObjectRemoveKey( rest_of_query, "select" );
1397 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1398 jsonObjectRemoveKey( rest_of_query, "flesh" );
1399 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
1401 rest_of_query = jsonNewObjectType( JSON_HASH );
1404 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1406 // Get the class metadata
1407 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1408 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1410 // Build a SELECT list containing just the primary key,
1411 // i.e. like { "classname":["keyname"] }
1412 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1414 // Load array with name of primary key
1415 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1416 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1417 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1419 jsonObjectSetKey( rest_of_query, "select", select_clause );
1424 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1426 jsonObjectFree( rest_of_query );
1428 osrfAppRespondComplete( ctx, NULL );
1432 // Return each primary key value to the client
1434 unsigned long res_idx = 0;
1435 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1436 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1437 continue; // Suppress due to lack of permission
1439 osrfAppRespond( ctx,
1440 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1443 jsonObjectFree( obj );
1444 osrfAppRespondComplete( ctx, NULL );
1449 @brief Verify that we have a valid class reference.
1450 @param ctx Pointer to the method context.
1451 @param param Pointer to the method parameters.
1452 @return 1 if the class reference is valid, or zero if it isn't.
1454 The class of the method params must match the class to which the method id devoted.
1455 For PCRUD there are additional restrictions.
1457 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1459 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1460 osrfHash* class = osrfHashGet( method_meta, "class" );
1462 // Compare the method's class to the parameters' class
1463 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1465 // Oops -- they don't match. Complain.
1466 growing_buffer* msg = buffer_init(128);
1469 "%s: %s method for type %s was passed a %s",
1471 osrfHashGet(method_meta, "methodtype"),
1472 osrfHashGet(class, "classname"),
1473 param->classname ? param->classname : "(null)"
1476 char* m = buffer_release(msg);
1477 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1485 return verifyObjectPCRUD( ctx, param );
1491 @brief (PCRUD only) Verify that the user is properly logged in.
1492 @param ctx Pointer to the method context.
1493 @return If the user is logged in, a pointer to the user object from the authentication
1494 server; otherwise NULL.
1496 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1498 // Get the authkey (the first method parameter)
1499 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1501 // See if we have the same authkey, and a user object,
1502 // locally cached from a previous call
1503 const char* cached_authkey = getAuthkey( ctx );
1504 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1505 const jsonObject* cached_user = getUserLogin( ctx );
1510 // We have no matching authentication data in the cache. Authenticate from scratch.
1511 jsonObject* auth_object = jsonNewObject(auth);
1513 // Fetch the user object from the authentication server
1514 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1516 jsonObjectFree(auth_object);
1518 if (!user->classname || strcmp(user->classname, "au")) {
1520 growing_buffer* msg = buffer_init(128);
1523 "%s: permacrud received a bad auth token: %s",
1528 char* m = buffer_release(msg);
1529 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1533 jsonObjectFree(user);
1537 setUserLogin( ctx, user );
1538 setAuthkey( ctx, auth );
1540 // Allow ourselves up to a second before we have to reset the login timeout.
1541 // It would be nice to use some fraction of the timeout interval enforced by the
1542 // authentication server, but that value is not readily available at this point.
1543 // Instead, we use a conservative default interval.
1544 time_next_reset = time( NULL ) + 1;
1549 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1551 dbhandle = writehandle;
1553 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1554 osrfHash* class = osrfHashGet( method_metadata, "class" );
1555 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1558 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
1559 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1560 } else if ( *method_type == 'u' || *method_type == 'd' ) {
1561 fetch = 1; // MUST go to the db for the object for update and delete
1564 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
1567 // No permacrud for this method type on this class
1569 growing_buffer* msg = buffer_init(128);
1572 "%s: %s on class %s has no permacrud IDL entry",
1574 osrfHashGet(method_metadata, "methodtype"),
1575 osrfHashGet(class, "classname")
1578 char* m = buffer_release(msg);
1579 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1580 "osrfMethodException", ctx->request, m );
1587 const jsonObject* user = verifyUserPCRUD( ctx );
1591 int userid = atoi( oilsFMGetString( user, "id" ) );
1593 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1594 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1595 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1597 osrfStringArray* context_org_array = osrfNewStringArray(1);
1600 char* pkey_value = NULL;
1601 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1602 osrfLogDebug( OSRF_LOG_MARK,
1603 "global-level permissions required, fetching top of the org tree" );
1605 // check for perm at top of org tree
1606 char* org_tree_root_id = org_tree_root( ctx );
1607 if( org_tree_root_id ) {
1608 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1609 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1611 osrfStringArrayFree( context_org_array );
1616 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1617 "fetching context org ids" );
1618 const char* pkey = osrfHashGet(class, "primarykey");
1619 jsonObject *param = NULL;
1621 if (obj->classname) {
1622 pkey_value = oilsFMGetString( obj, pkey );
1624 param = jsonObjectClone(obj);
1625 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1628 pkey_value = jsonObjectToSimpleString( obj );
1630 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1631 "of %s and retrieving from the database", pkey_value );
1635 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1636 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1637 jsonObjectFree(_tmp_params);
1639 param = jsonObjectExtractIndex(_list, 0);
1640 jsonObjectFree(_list);
1644 osrfLogDebug( OSRF_LOG_MARK,
1645 "Object not found in the database with primary key %s of %s",
1648 growing_buffer* msg = buffer_init(128);
1651 "%s: no object found with primary key %s of %s",
1657 char* m = buffer_release(msg);
1658 osrfAppSessionStatus(
1660 OSRF_STATUS_INTERNALSERVERERROR,
1661 "osrfMethodException",
1667 if (pkey_value) free(pkey_value);
1672 if (local_context->size > 0) {
1673 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1674 local_context->size);
1676 const char* lcontext = NULL;
1677 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1678 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1681 "adding class-local field %s (value: %s) to the context org list",
1683 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1688 if (foreign_context) {
1689 unsigned long class_count = osrfHashGetCount( foreign_context );
1690 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1692 if (class_count > 0) {
1694 osrfHash* fcontext = NULL;
1695 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1696 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1697 const char* class_name = osrfHashIteratorKey( class_itr );
1698 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1702 "%d foreign context fields(s) specified for class %s",
1703 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1707 char* foreign_pkey = osrfHashGet(fcontext, "field");
1708 char* foreign_pkey_value =
1709 oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1711 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1713 jsonObject* _list = doFieldmapperSearch(
1714 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1716 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1717 jsonObjectFree(_tmp_params);
1718 jsonObjectFree(_list);
1720 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1722 if (_fparam && jump_list) {
1723 const char* flink = NULL;
1725 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1726 free(foreign_pkey_value);
1728 osrfHash* foreign_link_hash =
1729 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1731 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1732 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1734 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1736 _list = doFieldmapperSearch(
1738 osrfHashGet( oilsIDL(),
1739 osrfHashGet( foreign_link_hash, "class" ) ),
1745 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1746 jsonObjectFree(_tmp_params);
1747 jsonObjectFree(_list);
1753 growing_buffer* msg = buffer_init(128);
1756 "%s: no object found with primary key %s of %s",
1762 char* m = buffer_release(msg);
1763 osrfAppSessionStatus(
1765 OSRF_STATUS_INTERNALSERVERERROR,
1766 "osrfMethodException",
1772 osrfHashIteratorFree(class_itr);
1773 free(foreign_pkey_value);
1774 jsonObjectFree(param);
1779 free(foreign_pkey_value);
1782 const char* foreign_field = NULL;
1783 while ( (foreign_field = osrfStringArrayGetString(
1784 osrfHashGet(fcontext,"context" ), j++)) ) {
1785 osrfStringArrayAdd( context_org_array,
1786 oilsFMGetString( _fparam, foreign_field ) );
1789 "adding foreign class %s field %s (value: %s) to the context org list",
1792 osrfStringArrayGetString(
1793 context_org_array, context_org_array->size - 1)
1797 jsonObjectFree(_fparam);
1800 osrfHashIteratorFree( class_itr );
1804 jsonObjectFree(param);
1807 const char* context_org = NULL;
1808 const char* perm = NULL;
1811 if (permission->size == 0) {
1812 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1817 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1819 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1825 "Checking object permission [%s] for user %d "
1826 "on object %s (class %s) at org %d",
1830 osrfHashGet(class, "classname"),
1834 result = dbi_conn_queryf(
1836 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1839 osrfHashGet(class, "classname"),
1847 "Received a result for object permission [%s] "
1848 "for user %d on object %s (class %s) at org %d",
1852 osrfHashGet(class, "classname"),
1856 if (dbi_result_first_row(result)) {
1857 jsonObject* return_val = oilsMakeJSONFromResult( result );
1858 const char* has_perm = jsonObjectGetString(
1859 jsonObjectGetKeyConst(return_val, "has_perm") );
1863 "Status of object permission [%s] for user %d "
1864 "on object %s (class %s) at org %d is %s",
1868 osrfHashGet(class, "classname"),
1873 if ( *has_perm == 't' ) OK = 1;
1874 jsonObjectFree(return_val);
1877 dbi_result_free(result);
1883 osrfLogDebug( OSRF_LOG_MARK,
1884 "Checking non-object permission [%s] for user %d at org %d",
1885 perm, userid, atoi(context_org) );
1886 result = dbi_conn_queryf(
1888 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1895 osrfLogDebug( OSRF_LOG_MARK,
1896 "Received a result for permission [%s] for user %d at org %d",
1897 perm, userid, atoi(context_org) );
1898 if ( dbi_result_first_row(result) ) {
1899 jsonObject* return_val = oilsMakeJSONFromResult( result );
1900 const char* has_perm = jsonObjectGetString(
1901 jsonObjectGetKeyConst(return_val, "has_perm") );
1902 osrfLogDebug( OSRF_LOG_MARK,
1903 "Status of permission [%s] for user %d at org %d is [%s]",
1904 perm, userid, atoi(context_org), has_perm );
1905 if ( *has_perm == 't' )
1907 jsonObjectFree(return_val);
1910 dbi_result_free(result);
1919 if (pkey_value) free(pkey_value);
1920 osrfStringArrayFree(context_org_array);
1926 @brief Look up the root of the org_unit tree.
1927 @param ctx Pointer to the method context.
1928 @return The id of the root org unit, as a character string.
1930 Query actor.org_unit where parent_ou is null, and return the id as a string.
1932 This function assumes that there is only one root org unit, i.e. that we
1933 have a single tree, not a forest.
1935 The calling code is responsible for freeing the returned string.
1937 static char* org_tree_root( osrfMethodContext* ctx ) {
1939 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1940 static time_t last_lookup_time = 0;
1941 time_t current_time = time( NULL );
1943 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1944 // We successfully looked this up less than an hour ago.
1945 // It's not likely to have changed since then.
1946 return strdup( cached_root_id );
1948 last_lookup_time = current_time;
1951 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1952 jsonObject* result = doFieldmapperSearch(
1953 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1954 jsonObjectFree( where_clause );
1956 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1959 jsonObjectFree( result );
1961 growing_buffer* msg = buffer_init(128);
1962 OSRF_BUFFER_ADD( msg, modulename );
1963 OSRF_BUFFER_ADD( msg,
1964 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1966 char* m = buffer_release(msg);
1967 osrfAppSessionStatus( ctx->session,
1968 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1971 cached_root_id[ 0 ] = '\0';
1975 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1976 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1978 jsonObjectFree( result );
1980 strcpy( cached_root_id, root_org_unit_id );
1981 return root_org_unit_id;
1985 @brief Create a JSON_HASH with a single key/value pair.
1986 @param key The key of the key/value pair.
1987 @param value the value of the key/value pair.
1988 @return Pointer to a newly created jsonObject of type JSON_HASH.
1990 The value of the key/value is either a string or (if @a value is NULL) a null.
1992 static jsonObject* single_hash( const char* key, const char* value ) {
1994 if( ! key ) key = "";
1996 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1997 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2002 static int doCreate( osrfMethodContext* ctx ) {
2003 if(osrfMethodVerifyContext( ctx )) {
2004 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2009 timeout_needs_resetting = 1;
2011 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2012 jsonObject* target = NULL;
2013 jsonObject* options = NULL;
2015 if( enforce_pcrud ) {
2016 target = jsonObjectGetIndex( ctx->params, 1 );
2017 options = jsonObjectGetIndex( ctx->params, 2 );
2019 target = jsonObjectGetIndex( ctx->params, 0 );
2020 options = jsonObjectGetIndex( ctx->params, 1 );
2023 if ( !verifyObjectClass( ctx, target )) {
2024 osrfAppRespondComplete( ctx, NULL );
2028 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2030 const char* trans_id = getXactId( ctx );
2032 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2034 osrfAppSessionStatus(
2036 OSRF_STATUS_BADREQUEST,
2037 "osrfMethodException",
2039 "No active transaction -- required for CREATE"
2041 osrfAppRespondComplete( ctx, NULL );
2045 // The following test is harmless but redundant. If a class is
2046 // readonly, we don't register a create method for it.
2047 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2048 osrfAppSessionStatus(
2050 OSRF_STATUS_BADREQUEST,
2051 "osrfMethodException",
2053 "Cannot INSERT readonly class"
2055 osrfAppRespondComplete( ctx, NULL );
2059 // Set the last_xact_id
2060 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2062 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2063 trans_id, target->classname, index);
2064 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
2067 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2069 dbhandle = writehandle;
2071 osrfHash* fields = osrfHashGet(meta, "fields");
2072 char* pkey = osrfHashGet(meta, "primarykey");
2073 char* seq = osrfHashGet(meta, "sequence");
2075 growing_buffer* table_buf = buffer_init(128);
2076 growing_buffer* col_buf = buffer_init(128);
2077 growing_buffer* val_buf = buffer_init(128);
2079 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
2080 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
2081 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2082 buffer_add(val_buf,"VALUES (");
2086 osrfHash* field = NULL;
2087 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2088 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2090 const char* field_name = osrfHashIteratorKey( field_itr );
2092 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2095 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2098 if (field_object && field_object->classname) {
2099 value = oilsFMGetString(
2101 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
2103 } else if( field_object && JSON_BOOL == field_object->type ) {
2104 if( jsonBoolIsTrue( field_object ) )
2105 value = strdup( "t" );
2107 value = strdup( "f" );
2109 value = jsonObjectToSimpleString( field_object );
2115 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2116 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2119 buffer_add(col_buf, field_name);
2121 if (!field_object || field_object->type == JSON_NULL) {
2122 buffer_add( val_buf, "DEFAULT" );
2124 } else if ( !strcmp(get_primitive( field ), "number") ) {
2125 const char* numtype = get_datatype( field );
2126 if ( !strcmp( numtype, "INT8") ) {
2127 buffer_fadd( val_buf, "%lld", atoll(value) );
2129 } else if ( !strcmp( numtype, "INT") ) {
2130 buffer_fadd( val_buf, "%d", atoi(value) );
2132 } else if ( !strcmp( numtype, "NUMERIC") ) {
2133 buffer_fadd( val_buf, "%f", atof(value) );
2136 if ( dbi_conn_quote_string(writehandle, &value) ) {
2137 OSRF_BUFFER_ADD( val_buf, value );
2140 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value);
2141 osrfAppSessionStatus(
2143 OSRF_STATUS_INTERNALSERVERERROR,
2144 "osrfMethodException",
2146 "Error quoting string -- please see the error log for more details"
2149 buffer_free(table_buf);
2150 buffer_free(col_buf);
2151 buffer_free(val_buf);
2152 osrfAppRespondComplete( ctx, NULL );
2161 osrfHashIteratorFree( field_itr );
2163 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2164 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2166 char* table_str = buffer_release(table_buf);
2167 char* col_str = buffer_release(col_buf);
2168 char* val_str = buffer_release(val_buf);
2169 growing_buffer* sql = buffer_init(128);
2170 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2175 char* query = buffer_release(sql);
2177 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query);
2179 jsonObject* obj = NULL;
2182 dbi_result result = dbi_conn_query( writehandle, query );
2184 obj = jsonNewObject(NULL);
2187 "%s ERROR inserting %s object using query [%s]",
2189 osrfHashGet(meta, "fieldmapper"),
2192 osrfAppSessionStatus(
2194 OSRF_STATUS_INTERNALSERVERERROR,
2195 "osrfMethodException",
2197 "INSERT error -- please see the error log for more details"
2202 char* id = oilsFMGetString(target, pkey);
2204 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
2205 growing_buffer* _id = buffer_init(10);
2206 buffer_fadd(_id, "%lld", new_id);
2207 id = buffer_release(_id);
2210 // Find quietness specification, if present
2211 const char* quiet_str = NULL;
2213 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2215 quiet_str = jsonObjectGetString( quiet_obj );
2218 if( str_is_true( quiet_str ) ) { // if quietness is specified
2219 obj = jsonNewObject(id);
2223 // Fetch the row that we just inserted, so that we can return it to the client
2224 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2225 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
2228 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2232 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2234 jsonObjectFree( list );
2235 jsonObjectFree( where_clause );
2242 osrfAppRespondComplete( ctx, obj );
2243 jsonObjectFree( obj );
2248 @brief Implement the retrieve method.
2249 @param ctx Pointer to the method context.
2250 @param err Pointer through which to return an error code.
2251 @return If successful, a pointer to the result to be returned to the client;
2254 From the method's class, fetch a row with a specified value in the primary key. This
2255 method relies on the database design convention that a primary key consists of a single
2259 - authkey (PCRUD only)
2260 - value of the primary key for the desired row, for building the WHERE clause
2261 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2263 Return to client: One row from the query.
2265 static int doRetrieve(osrfMethodContext* ctx ) {
2266 if(osrfMethodVerifyContext( ctx )) {
2267 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2272 timeout_needs_resetting = 1;
2277 if( enforce_pcrud ) {
2282 // Get the class metadata
2283 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2285 // Get the value of the primary key, from a method parameter
2286 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos);
2290 "%s retrieving %s object with primary key value of %s",
2292 osrfHashGet( class_def, "fieldmapper" ),
2293 jsonObjectGetString( id_obj )
2296 // Build a WHERE clause based on the key value
2297 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2300 osrfHashGet( class_def, "primarykey" ), // name of key column
2301 jsonObjectClone( id_obj ) // value of key column
2304 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
2308 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2310 jsonObjectFree( where_clause );
2312 osrfAppRespondComplete( ctx, NULL );
2316 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2317 jsonObjectFree( list );
2319 if( enforce_pcrud ) {
2320 if(!verifyObjectPCRUD(ctx, obj)) {
2321 jsonObjectFree(obj);
2323 growing_buffer* msg = buffer_init(128);
2324 OSRF_BUFFER_ADD( msg, modulename );
2325 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2327 char* m = buffer_release(msg);
2328 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2332 osrfAppRespondComplete( ctx, NULL );
2337 osrfAppRespondComplete( ctx, obj );
2338 jsonObjectFree( obj );
2342 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
2343 growing_buffer* val_buf = buffer_init(32);
2344 const char* numtype = get_datatype( field );
2346 if ( !strncmp( numtype, "INT", 3 ) ) {
2347 if (value->type == JSON_NUMBER)
2348 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2349 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2351 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2354 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
2355 if (value->type == JSON_NUMBER)
2356 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2358 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2362 // Presumably this was really intended ot be a string, so quote it
2363 char* str = jsonObjectToSimpleString( value );
2364 if ( dbi_conn_quote_string(dbhandle, &str) ) {
2365 OSRF_BUFFER_ADD( val_buf, str );
2368 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str);
2370 buffer_free(val_buf);
2375 return buffer_release(val_buf);
2378 static char* searchINPredicate (const char* class_alias, osrfHash* field,
2379 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2380 growing_buffer* sql_buf = buffer_init(32);
2386 osrfHashGet(field, "name")
2390 buffer_add(sql_buf, "IN (");
2391 } else if (!(strcasecmp(op,"not in"))) {
2392 buffer_add(sql_buf, "NOT IN (");
2394 buffer_add(sql_buf, "IN (");
2397 if (node->type == JSON_HASH) {
2398 // subquery predicate
2399 char* subpred = buildQuery( ctx, node, SUBSELECT );
2401 buffer_free( sql_buf );
2405 buffer_add(sql_buf, subpred);
2408 } else if (node->type == JSON_ARRAY) {
2409 // literal value list
2410 int in_item_index = 0;
2411 int in_item_first = 1;
2412 const jsonObject* in_item;
2413 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
2418 buffer_add(sql_buf, ", ");
2421 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2422 osrfLogError( OSRF_LOG_MARK,
2423 "%s: Expected string or number within IN list; found %s",
2424 modulename, json_type( in_item->type ) );
2425 buffer_free(sql_buf);
2429 // Append the literal value -- quoted if not a number
2430 if ( JSON_NUMBER == in_item->type ) {
2431 char* val = jsonNumberToDBString( field, in_item );
2432 OSRF_BUFFER_ADD( sql_buf, val );
2435 } else if ( !strcmp( get_primitive( field ), "number") ) {
2436 char* val = jsonNumberToDBString( field, in_item );
2437 OSRF_BUFFER_ADD( sql_buf, val );
2441 char* key_string = jsonObjectToSimpleString(in_item);
2442 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
2443 OSRF_BUFFER_ADD( sql_buf, key_string );
2446 osrfLogError(OSRF_LOG_MARK,
2447 "%s: Error quoting key string [%s]", modulename, key_string);
2449 buffer_free(sql_buf);
2455 if( in_item_first ) {
2456 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2457 buffer_free( sql_buf );
2461 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2462 modulename, json_type( node->type ) );
2463 buffer_free(sql_buf);
2467 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2469 return buffer_release(sql_buf);
2472 // Receive a JSON_ARRAY representing a function call. The first
2473 // entry in the array is the function name. The rest are parameters.
2474 static char* searchValueTransform( const jsonObject* array ) {
2476 if( array->size < 1 ) {
2477 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
2481 // Get the function name
2482 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2483 if( func_item->type != JSON_STRING ) {
2484 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2485 MODULENAME, json_type( func_item->type ) );
2489 growing_buffer* sql_buf = buffer_init(32);
2491 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2492 OSRF_BUFFER_ADD( sql_buf, "( " );
2494 // Get the parameters
2495 int func_item_index = 1; // We already grabbed the zeroth entry
2496 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2498 // Add a separator comma, if we need one
2499 if( func_item_index > 2 )
2500 buffer_add( sql_buf, ", " );
2502 // Add the current parameter
2503 if (func_item->type == JSON_NULL) {
2504 buffer_add( sql_buf, "NULL" );
2506 char* val = jsonObjectToSimpleString(func_item);
2507 if ( dbi_conn_quote_string(dbhandle, &val) ) {
2508 OSRF_BUFFER_ADD( sql_buf, val );
2511 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2512 buffer_free(sql_buf);
2519 buffer_add( sql_buf, " )" );
2521 return buffer_release(sql_buf);
2524 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
2525 const jsonObject* node, const char* op) {
2527 if( ! is_good_operator( op ) ) {
2528 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2532 char* val = searchValueTransform(node);
2536 growing_buffer* sql_buf = buffer_init(32);
2541 osrfHashGet(field, "name"),
2548 return buffer_release(sql_buf);
2551 // class_alias is a class name or other table alias
2552 // field is a field definition as stored in the IDL
2553 // node comes from the method parameter, and may represent an entry in the SELECT list
2554 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
2555 growing_buffer* sql_buf = buffer_init(32);
2557 const char* field_transform = jsonObjectGetString(
2558 jsonObjectGetKeyConst( node, "transform" ) );
2559 const char* transform_subcolumn = jsonObjectGetString(
2560 jsonObjectGetKeyConst( node, "result_field" ) );
2562 if(transform_subcolumn) {
2563 if( ! is_identifier( transform_subcolumn ) ) {
2564 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2565 MODULENAME, transform_subcolumn );
2566 buffer_free( sql_buf );
2569 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2572 if (field_transform) {
2574 if( ! is_identifier( field_transform ) ) {
2575 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2576 MODULENAME, field_transform );
2577 buffer_free( sql_buf );
2581 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2582 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2583 field_transform, class_alias, osrfHashGet(field, "name"));
2585 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2586 field_transform, class_alias, osrfHashGet(field, "name"));
2589 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2592 if( array->type != JSON_ARRAY ) {
2593 osrfLogError( OSRF_LOG_MARK,
2594 "%s: Expected JSON_ARRAY for function params; found %s",
2595 MODULENAME, json_type( array->type ) );
2596 buffer_free( sql_buf );
2599 int func_item_index = 0;
2600 jsonObject* func_item;
2601 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2603 char* val = jsonObjectToSimpleString(func_item);
2606 buffer_add( sql_buf, ",NULL" );
2607 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
2608 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2609 OSRF_BUFFER_ADD( sql_buf, val );
2611 osrfLogError( OSRF_LOG_MARK,
2612 "%s: Error quoting key string [%s]", MODULENAME, val);
2614 buffer_free(sql_buf);
2621 buffer_add( sql_buf, " )" );
2624 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2627 if (transform_subcolumn)
2628 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2630 return buffer_release(sql_buf);
2633 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2634 const jsonObject* node, const char* op ) {
2636 if( ! is_good_operator( op ) ) {
2637 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2641 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2642 if( ! field_transform )
2645 int extra_parens = 0; // boolean
2647 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2648 if ( ! value_obj ) {
2649 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2651 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2653 free(field_transform);
2657 } else if ( value_obj->type == JSON_ARRAY ) {
2658 value = searchValueTransform( value_obj );
2660 osrfLogError(OSRF_LOG_MARK,
2661 "%s: Error building value transform for field transform", MODULENAME);
2662 free( field_transform );
2665 } else if ( value_obj->type == JSON_HASH ) {
2666 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2668 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2670 free(field_transform);
2674 } else if ( value_obj->type == JSON_NUMBER ) {
2675 value = jsonNumberToDBString( field, value_obj );
2676 } else if ( value_obj->type == JSON_NULL ) {
2677 osrfLogError( OSRF_LOG_MARK,
2678 "%s: Error building predicate for field transform: null value", MODULENAME );
2679 free(field_transform);
2681 } else if ( value_obj->type == JSON_BOOL ) {
2682 osrfLogError( OSRF_LOG_MARK,
2683 "%s: Error building predicate for field transform: boolean value", MODULENAME );
2684 free(field_transform);
2687 if ( !strcmp( get_primitive( field ), "number") ) {
2688 value = jsonNumberToDBString( field, value_obj );
2690 value = jsonObjectToSimpleString( value_obj );
2691 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2692 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2695 free(field_transform);
2701 const char* left_parens = "";
2702 const char* right_parens = "";
2704 if( extra_parens ) {
2709 growing_buffer* sql_buf = buffer_init(32);
2713 "%s%s %s %s %s %s%s",
2724 free(field_transform);
2726 return buffer_release(sql_buf);
2729 static char* searchSimplePredicate (const char* op, const char* class_alias,
2730 osrfHash* field, const jsonObject* node) {
2732 if( ! is_good_operator( op ) ) {
2733 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2739 // Get the value to which we are comparing the specified column
2740 if (node->type != JSON_NULL) {
2741 if ( node->type == JSON_NUMBER ) {
2742 val = jsonNumberToDBString( field, node );
2743 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2744 val = jsonNumberToDBString( field, node );
2746 val = jsonObjectToSimpleString(node);
2751 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2752 // Value is not numeric; enclose it in quotes
2753 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2754 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2761 // Compare to a null value
2762 val = strdup( "NULL" );
2763 if (strcmp( op, "=" ))
2769 growing_buffer* sql_buf = buffer_init(32);
2770 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2771 char* pred = buffer_release( sql_buf );
2778 static char* searchBETWEENPredicate (const char* class_alias,
2779 osrfHash* field, const jsonObject* node) {
2781 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2782 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2784 if( NULL == y_node ) {
2785 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2788 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2789 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2796 if ( !strcmp( get_primitive( field ), "number") ) {
2797 x_string = jsonNumberToDBString(field, x_node);
2798 y_string = jsonNumberToDBString(field, y_node);
2801 x_string = jsonObjectToSimpleString(x_node);
2802 y_string = jsonObjectToSimpleString(y_node);
2803 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2804 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2805 MODULENAME, x_string, y_string);
2812 growing_buffer* sql_buf = buffer_init(32);
2813 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2814 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2818 return buffer_release(sql_buf);
2821 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2822 jsonObject* node, osrfMethodContext* ctx ) {
2825 if (node->type == JSON_ARRAY) { // equality IN search
2826 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2827 } else if (node->type == JSON_HASH) { // other search
2828 jsonIterator* pred_itr = jsonNewIterator( node );
2829 if( !jsonIteratorHasNext( pred_itr ) ) {
2830 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2831 MODULENAME, osrfHashGet(field, "name") );
2833 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2835 // Verify that there are no additional predicates
2836 if( jsonIteratorHasNext( pred_itr ) ) {
2837 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2838 MODULENAME, osrfHashGet(field, "name") );
2839 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2840 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2841 else if ( !(strcasecmp( pred_itr->key,"in" ))
2842 || !(strcasecmp( pred_itr->key,"not in" )) )
2843 pred = searchINPredicate(
2844 class_info->alias, field, pred_node, pred_itr->key, ctx );
2845 else if ( pred_node->type == JSON_ARRAY )
2846 pred = searchFunctionPredicate(
2847 class_info->alias, field, pred_node, pred_itr->key );
2848 else if ( pred_node->type == JSON_HASH )
2849 pred = searchFieldTransformPredicate(
2850 class_info, field, pred_node, pred_itr->key );
2852 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2854 jsonIteratorFree(pred_itr);
2856 } else if (node->type == JSON_NULL) { // IS NULL search
2857 growing_buffer* _p = buffer_init(64);
2860 "\"%s\".%s IS NULL",
2861 class_info->class_name,
2862 osrfHashGet(field, "name")
2864 pred = buffer_release(_p);
2865 } else { // equality search
2866 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2885 field : call_number,
2901 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2903 const jsonObject* working_hash;
2904 jsonObject* freeable_hash = NULL;
2906 if (join_hash->type == JSON_HASH) {
2907 working_hash = join_hash;
2908 } else if (join_hash->type == JSON_STRING) {
2909 // turn it into a JSON_HASH by creating a wrapper
2910 // around a copy of the original
2911 const char* _tmp = jsonObjectGetString( join_hash );
2912 freeable_hash = jsonNewObjectType(JSON_HASH);
2913 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2914 working_hash = freeable_hash;
2918 "%s: JOIN failed; expected JSON object type not found",
2924 growing_buffer* join_buf = buffer_init(128);
2925 const char* leftclass = left_info->class_name;
2927 jsonObject* snode = NULL;
2928 jsonIterator* search_itr = jsonNewIterator( working_hash );
2930 while ( (snode = jsonIteratorNext( search_itr )) ) {
2931 const char* right_alias = search_itr->key;
2933 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2935 class = right_alias;
2937 const ClassInfo* right_info = add_joined_class( right_alias, class );
2941 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2945 jsonIteratorFree( search_itr );
2946 buffer_free( join_buf );
2948 jsonObjectFree( freeable_hash );
2951 osrfHash* links = right_info->links;
2952 const char* table = right_info->source_def;
2954 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2955 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2957 if (field && !fkey) {
2958 // Look up the corresponding join column in the IDL.
2959 // The link must be defined in the child table,
2960 // and point to the right parent table.
2961 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2962 const char* reltype = NULL;
2963 const char* other_class = NULL;
2964 reltype = osrfHashGet( idl_link, "reltype" );
2965 if( reltype && strcmp( reltype, "has_many" ) )
2966 other_class = osrfHashGet( idl_link, "class" );
2967 if( other_class && !strcmp( other_class, leftclass ) )
2968 fkey = osrfHashGet( idl_link, "key" );
2972 "%s: JOIN failed. No link defined from %s.%s to %s",
2978 buffer_free(join_buf);
2980 jsonObjectFree(freeable_hash);
2981 jsonIteratorFree(search_itr);
2985 } else if (!field && fkey) {
2986 // Look up the corresponding join column in the IDL.
2987 // The link must be defined in the child table,
2988 // and point to the right parent table.
2989 osrfHash* left_links = left_info->links;
2990 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2991 const char* reltype = NULL;
2992 const char* other_class = NULL;
2993 reltype = osrfHashGet( idl_link, "reltype" );
2994 if( reltype && strcmp( reltype, "has_many" ) )
2995 other_class = osrfHashGet( idl_link, "class" );
2996 if( other_class && !strcmp( other_class, class ) )
2997 field = osrfHashGet( idl_link, "key" );
3001 "%s: JOIN failed. No link defined from %s.%s to %s",
3007 buffer_free(join_buf);
3009 jsonObjectFree(freeable_hash);
3010 jsonIteratorFree(search_itr);
3014 } else if (!field && !fkey) {
3015 osrfHash* left_links = left_info->links;
3017 // For each link defined for the left class:
3018 // see if the link references the joined class
3019 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3020 osrfHash* curr_link = NULL;
3021 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3022 const char* other_class = osrfHashGet( curr_link, "class" );
3023 if( other_class && !strcmp( other_class, class ) ) {
3025 // In the IDL, the parent class doesn't always know then names of the child
3026 // columns that are pointing to it, so don't use that end of the link
3027 const char* reltype = osrfHashGet( curr_link, "reltype" );
3028 if( reltype && strcmp( reltype, "has_many" ) ) {
3029 // Found a link between the classes
3030 fkey = osrfHashIteratorKey( itr );
3031 field = osrfHashGet( curr_link, "key" );
3036 osrfHashIteratorFree( itr );
3038 if (!field || !fkey) {
3039 // Do another such search, with the classes reversed
3041 // For each link defined for the joined class:
3042 // see if the link references the left class
3043 osrfHashIterator* itr = osrfNewHashIterator( links );
3044 osrfHash* curr_link = NULL;
3045 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3046 const char* other_class = osrfHashGet( curr_link, "class" );
3047 if( other_class && !strcmp( other_class, leftclass ) ) {
3049 // In the IDL, the parent class doesn't know then names of the child
3050 // columns that are pointing to it, so don't use that end of the link
3051 const char* reltype = osrfHashGet( curr_link, "reltype" );
3052 if( reltype && strcmp( reltype, "has_many" ) ) {
3053 // Found a link between the classes
3054 field = osrfHashIteratorKey( itr );
3055 fkey = osrfHashGet( curr_link, "key" );
3060 osrfHashIteratorFree( itr );
3063 if (!field || !fkey) {
3066 "%s: JOIN failed. No link defined between %s and %s",
3071 buffer_free(join_buf);
3073 jsonObjectFree(freeable_hash);
3074 jsonIteratorFree(search_itr);
3079 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3081 if ( !strcasecmp(type,"left") ) {
3082 buffer_add(join_buf, " LEFT JOIN");
3083 } else if ( !strcasecmp(type,"right") ) {
3084 buffer_add(join_buf, " RIGHT JOIN");
3085 } else if ( !strcasecmp(type,"full") ) {
3086 buffer_add(join_buf, " FULL JOIN");
3088 buffer_add(join_buf, " INNER JOIN");
3091 buffer_add(join_buf, " INNER JOIN");
3094 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3095 table, right_alias, right_alias, field, left_info->alias, fkey);
3097 // Add any other join conditions as specified by "filter"
3098 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3100 const char* filter_op = jsonObjectGetString(
3101 jsonObjectGetKeyConst( snode, "filter_op" ) );
3102 if ( filter_op && !strcasecmp("or",filter_op) ) {
3103 buffer_add( join_buf, " OR " );
3105 buffer_add( join_buf, " AND " );
3108 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3110 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3111 OSRF_BUFFER_ADD( join_buf, jpred );
3116 "%s: JOIN failed. Invalid conditional expression.",
3119 jsonIteratorFree( search_itr );
3120 buffer_free( join_buf );
3122 jsonObjectFree( freeable_hash );
3127 buffer_add(join_buf, " ) ");
3129 // Recursively add a nested join, if one is present
3130 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3132 char* jpred = searchJOIN( join_filter, right_info );
3134 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3135 OSRF_BUFFER_ADD( join_buf, jpred );
3138 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
3139 jsonIteratorFree( search_itr );
3140 buffer_free( join_buf );
3142 jsonObjectFree( freeable_hash );
3149 jsonObjectFree(freeable_hash);
3150 jsonIteratorFree(search_itr);
3152 return buffer_release(join_buf);
3157 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3158 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3159 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3161 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3163 search_hash is the JSON expression of the conditions.
3164 meta is the class definition from the IDL, for the relevant table.
3165 opjoin_type indicates whether multiple conditions, if present, should be
3166 connected by AND or OR.
3167 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3168 to pass it to other functions -- and all they do with it is to use the session
3169 and request members to send error messages back to the client.
3173 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
3174 int opjoin_type, osrfMethodContext* ctx ) {
3178 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3179 "opjoin_type = %d, ctx addr = %p",
3182 class_info->class_def,
3187 growing_buffer* sql_buf = buffer_init(128);
3189 jsonObject* node = NULL;
3192 if ( search_hash->type == JSON_ARRAY ) {
3193 osrfLogDebug( OSRF_LOG_MARK,
3194 "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME );
3195 if( 0 == search_hash->size ) {
3198 "%s: Invalid predicate structure: empty JSON array",
3201 buffer_free( sql_buf );
3205 unsigned long i = 0;
3206 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
3210 if (opjoin_type == OR_OP_JOIN)
3211 buffer_add(sql_buf, " OR ");
3213 buffer_add(sql_buf, " AND ");
3216 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3218 buffer_free( sql_buf );
3222 buffer_fadd(sql_buf, "( %s )", subpred);
3226 } else if ( search_hash->type == JSON_HASH ) {
3227 osrfLogDebug( OSRF_LOG_MARK,
3228 "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME );
3229 jsonIterator* search_itr = jsonNewIterator( search_hash );
3230 if( !jsonIteratorHasNext( search_itr ) ) {
3233 "%s: Invalid predicate structure: empty JSON object",
3236 jsonIteratorFree( search_itr );
3237 buffer_free( sql_buf );
3241 while ( (node = jsonIteratorNext( search_itr )) ) {
3246 if (opjoin_type == OR_OP_JOIN)
3247 buffer_add(sql_buf, " OR ");
3249 buffer_add(sql_buf, " AND ");
3252 if ( '+' == search_itr->key[ 0 ] ) {
3254 // This plus sign prefixes a class name or other table alias;
3255 // make sure the table alias is in scope
3256 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3257 if( ! alias_info ) {
3260 "%s: Invalid table alias \"%s\" in WHERE clause",
3264 jsonIteratorFree( search_itr );
3265 buffer_free( sql_buf );
3269 if ( node->type == JSON_STRING ) {
3270 // It's the name of a column; make sure it belongs to the class
3271 const char* fieldname = jsonObjectGetString( node );
3272 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3275 "%s: Invalid column name \"%s\" in WHERE clause "
3276 "for table alias \"%s\"",
3281 jsonIteratorFree( search_itr );
3282 buffer_free( sql_buf );
3286 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3288 // It's something more complicated
3289 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3291 jsonIteratorFree( search_itr );
3292 buffer_free( sql_buf );
3296 buffer_fadd(sql_buf, "( %s )", subpred);
3299 } else if ( '-' == search_itr->key[ 0 ] ) {
3300 if ( !strcasecmp("-or",search_itr->key) ) {
3301 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3303 jsonIteratorFree( search_itr );
3304 buffer_free( sql_buf );
3308 buffer_fadd(sql_buf, "( %s )", subpred);
3310 } else if ( !strcasecmp("-and",search_itr->key) ) {
3311 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3313 jsonIteratorFree( search_itr );
3314 buffer_free( sql_buf );
3318 buffer_fadd(sql_buf, "( %s )", subpred);
3320 } else if ( !strcasecmp("-not",search_itr->key) ) {
3321 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3323 jsonIteratorFree( search_itr );
3324 buffer_free( sql_buf );
3328 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
3330 } else if ( !strcasecmp("-exists",search_itr->key) ) {
3331 char* subpred = buildQuery( ctx, node, SUBSELECT );
3333 jsonIteratorFree( search_itr );
3334 buffer_free( sql_buf );
3338 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
3340 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
3341 char* subpred = buildQuery( ctx, node, SUBSELECT );
3343 jsonIteratorFree( search_itr );
3344 buffer_free( sql_buf );
3348 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
3350 } else { // Invalid "minus" operator
3353 "%s: Invalid operator \"%s\" in WHERE clause",
3357 jsonIteratorFree( search_itr );
3358 buffer_free( sql_buf );
3364 const char* class = class_info->class_name;
3365 osrfHash* fields = class_info->fields;
3366 osrfHash* field = osrfHashGet( fields, search_itr->key );
3369 const char* table = class_info->source_def;
3372 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3375 table ? table : "?",
3378 jsonIteratorFree(search_itr);
3379 buffer_free(sql_buf);
3383 char* subpred = searchPredicate( class_info, field, node, ctx );
3385 buffer_free(sql_buf);
3386 jsonIteratorFree(search_itr);
3390 buffer_add( sql_buf, subpred );
3394 jsonIteratorFree(search_itr);
3397 // ERROR ... only hash and array allowed at this level
3398 char* predicate_string = jsonObjectToJSON( search_hash );
3401 "%s: Invalid predicate structure: %s",
3405 buffer_free(sql_buf);
3406 free(predicate_string);
3410 return buffer_release(sql_buf);
3413 /* Build a JSON_ARRAY of field names for a given table alias
3415 static jsonObject* defaultSelectList( const char* table_alias ) {
3420 ClassInfo* class_info = search_all_alias( table_alias );
3421 if( ! class_info ) {
3424 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3431 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3432 osrfHash* field_def = NULL;
3433 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3434 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3435 const char* field_name = osrfHashIteratorKey( field_itr );
3436 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3437 jsonObjectPush( array, jsonNewObject( field_name ) );
3440 osrfHashIteratorFree( field_itr );
3445 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3446 // The jsonObject must be a JSON_HASH with an single entry for "union",
3447 // "intersect", or "except". The data associated with this key must be an
3448 // array of hashes, each hash being a query.
3449 // Also allowed but currently ignored: entries for "order_by" and "alias".
3450 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3452 if( ! combo || combo->type != JSON_HASH )
3453 return NULL; // should be impossible; validated by caller
3455 const jsonObject* query_array = NULL; // array of subordinate queries
3456 const char* op = NULL; // name of operator, e.g. UNION
3457 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3458 int op_count = 0; // for detecting conflicting operators
3459 int excepting = 0; // boolean
3460 int all = 0; // boolean
3461 jsonObject* order_obj = NULL;
3463 // Identify the elements in the hash
3464 jsonIterator* query_itr = jsonNewIterator( combo );
3465 jsonObject* curr_obj = NULL;
3466 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3467 if( ! strcmp( "union", query_itr->key ) ) {
3470 query_array = curr_obj;
3471 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3474 query_array = curr_obj;
3475 } else if( ! strcmp( "except", query_itr->key ) ) {
3479 query_array = curr_obj;
3480 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3483 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3486 order_obj = curr_obj;
3487 } else if( ! strcmp( "alias", query_itr->key ) ) {
3488 if( curr_obj->type != JSON_STRING ) {
3489 jsonIteratorFree( query_itr );
3492 alias = jsonObjectGetString( curr_obj );
3493 } else if( ! strcmp( "all", query_itr->key ) ) {
3494 if( obj_is_true( curr_obj ) )
3498 osrfAppSessionStatus(
3500 OSRF_STATUS_INTERNALSERVERERROR,
3501 "osrfMethodException",
3503 "Malformed query; unexpected entry in query object"
3507 "%s: Unexpected entry for \"%s\" in%squery",
3512 jsonIteratorFree( query_itr );
3516 jsonIteratorFree( query_itr );
3518 // More sanity checks
3519 if( ! query_array ) {
3521 osrfAppSessionStatus(
3523 OSRF_STATUS_INTERNALSERVERERROR,
3524 "osrfMethodException",
3526 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3530 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3533 return NULL; // should be impossible...
3534 } else if( op_count > 1 ) {
3536 osrfAppSessionStatus(
3538 OSRF_STATUS_INTERNALSERVERERROR,
3539 "osrfMethodException",
3541 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3545 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3549 } if( query_array->type != JSON_ARRAY ) {
3551 osrfAppSessionStatus(
3553 OSRF_STATUS_INTERNALSERVERERROR,
3554 "osrfMethodException",
3556 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3560 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3563 json_type( query_array->type )
3566 } if( query_array->size < 2 ) {
3568 osrfAppSessionStatus(
3570 OSRF_STATUS_INTERNALSERVERERROR,
3571 "osrfMethodException",
3573 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3577 "%s:%srequires multiple queries as operands",
3582 } else if( excepting && query_array->size > 2 ) {
3584 osrfAppSessionStatus(
3586 OSRF_STATUS_INTERNALSERVERERROR,
3587 "osrfMethodException",
3589 "EXCEPT operator has too many queries as operands"
3593 "%s:EXCEPT operator has too many queries as operands",
3597 } else if( order_obj && ! alias ) {
3599 osrfAppSessionStatus(
3601 OSRF_STATUS_INTERNALSERVERERROR,
3602 "osrfMethodException",
3604 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3608 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3614 // So far so good. Now build the SQL.
3615 growing_buffer* sql = buffer_init( 256 );
3617 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3618 // Add a layer of parentheses
3619 if( flags & SUBCOMBO )
3620 OSRF_BUFFER_ADD( sql, "( " );
3622 // Traverse the query array. Each entry should be a hash.
3623 int first = 1; // boolean
3625 jsonObject* query = NULL;
3626 while((query = jsonObjectGetIndex( query_array, i++ ) )) {
3627 if( query->type != JSON_HASH ) {
3629 osrfAppSessionStatus(
3631 OSRF_STATUS_INTERNALSERVERERROR,
3632 "osrfMethodException",
3634 "Malformed query under UNION, INTERSECT or EXCEPT"
3638 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3641 json_type( query->type )
3650 OSRF_BUFFER_ADD( sql, op );
3652 OSRF_BUFFER_ADD( sql, "ALL " );
3655 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3659 "%s: Error building query under%s",
3667 OSRF_BUFFER_ADD( sql, query_str );
3670 if( flags & SUBCOMBO )
3671 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3673 if ( !(flags & SUBSELECT) )
3674 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3676 return buffer_release( sql );
3679 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3680 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3681 // or "except" to indicate the type of query.
3682 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3686 osrfAppSessionStatus(
3688 OSRF_STATUS_INTERNALSERVERERROR,
3689 "osrfMethodException",
3691 "Malformed query; no query object"
3693 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", MODULENAME );
3695 } else if( query->type != JSON_HASH ) {
3697 osrfAppSessionStatus(
3699 OSRF_STATUS_INTERNALSERVERERROR,
3700 "osrfMethodException",
3702 "Malformed query object"
3706 "%s: Query object is %s instead of JSON_HASH",
3708 json_type( query->type )
3713 // Determine what kind of query it purports to be, and dispatch accordingly.
3714 if( jsonObjectGetKey( query, "union" ) ||
3715 jsonObjectGetKey( query, "intersect" ) ||
3716 jsonObjectGetKey( query, "except" ) ) {
3717 return doCombo( ctx, query, flags );
3719 // It is presumably a SELECT query
3721 // Push a node onto the stack for the current query. Every level of
3722 // subquery gets its own QueryFrame on the Stack.
3725 // Build an SQL SELECT statement
3728 jsonObjectGetKey( query, "select" ),
3729 jsonObjectGetKey( query, "from" ),
3730 jsonObjectGetKey( query, "where" ),
3731 jsonObjectGetKey( query, "having" ),
3732 jsonObjectGetKey( query, "order_by" ),
3733 jsonObjectGetKey( query, "limit" ),
3734 jsonObjectGetKey( query, "offset" ),
3743 /* method context */ osrfMethodContext* ctx,
3745 /* SELECT */ jsonObject* selhash,
3746 /* FROM */ jsonObject* join_hash,
3747 /* WHERE */ jsonObject* search_hash,
3748 /* HAVING */ jsonObject* having_hash,
3749 /* ORDER BY */ jsonObject* order_hash,
3750 /* LIMIT */ jsonObject* limit,
3751 /* OFFSET */ jsonObject* offset,
3752 /* flags */ int flags
3754 const char* locale = osrf_message_get_last_locale();
3756 // general tmp objects
3757 const jsonObject* tmp_const;
3758 jsonObject* selclass = NULL;
3759 jsonObject* snode = NULL;
3760 jsonObject* onode = NULL;
3762 char* string = NULL;
3763 int from_function = 0;
3768 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3770 // punt if there's no FROM clause
3771 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3774 "%s: FROM clause is missing or empty",
3778 osrfAppSessionStatus(
3780 OSRF_STATUS_INTERNALSERVERERROR,
3781 "osrfMethodException",
3783 "FROM clause is missing or empty in JSON query"
3788 // the core search class
3789 const char* core_class = NULL;
3791 // get the core class -- the only key of the top level FROM clause, or a string
3792 if (join_hash->type == JSON_HASH) {
3793 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3794 snode = jsonIteratorNext( tmp_itr );
3796 // Populate the current QueryFrame with information
3797 // about the core class
3798 if( add_query_core( NULL, tmp_itr->key ) ) {
3800 osrfAppSessionStatus(
3802 OSRF_STATUS_INTERNALSERVERERROR,
3803 "osrfMethodException",
3805 "Unable to look up core class"
3809 core_class = curr_query->core.class_name;
3812 jsonObject* extra = jsonIteratorNext( tmp_itr );
3814 jsonIteratorFree( tmp_itr );
3817 // There shouldn't be more than one entry in join_hash
3821 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3825 osrfAppSessionStatus(
3827 OSRF_STATUS_INTERNALSERVERERROR,
3828 "osrfMethodException",
3830 "Malformed FROM clause in JSON query"
3832 return NULL; // Malformed join_hash; extra entry
3834 } else if (join_hash->type == JSON_ARRAY) {
3835 // We're selecting from a function, not from a table
3837 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
3840 } else if (join_hash->type == JSON_STRING) {
3841 // Populate the current QueryFrame with information
3842 // about the core class
3843 core_class = jsonObjectGetString( join_hash );
3845 if( add_query_core( NULL, core_class ) ) {
3847 osrfAppSessionStatus(
3849 OSRF_STATUS_INTERNALSERVERERROR,
3850 "osrfMethodException",
3852 "Unable to look up core class"
3860 "%s: FROM clause is unexpected JSON type: %s",
3862 json_type( join_hash->type )
3865 osrfAppSessionStatus(
3867 OSRF_STATUS_INTERNALSERVERERROR,
3868 "osrfMethodException",
3870 "Ill-formed FROM clause in JSON query"
3875 // Build the join clause, if any, while filling out the list
3876 // of joined classes in the current QueryFrame.
3877 char* join_clause = NULL;
3878 if( join_hash && ! from_function ) {
3880 join_clause = searchJOIN( join_hash, &curr_query->core );
3881 if( ! join_clause ) {
3883 osrfAppSessionStatus(
3885 OSRF_STATUS_INTERNALSERVERERROR,
3886 "osrfMethodException",
3888 "Unable to construct JOIN clause(s)"
3894 // For in case we don't get a select list
3895 jsonObject* defaultselhash = NULL;
3897 // if there is no select list, build a default select list ...
3898 if (!selhash && !from_function) {
3899 jsonObject* default_list = defaultSelectList( core_class );
3900 if( ! default_list ) {
3902 osrfAppSessionStatus(
3904 OSRF_STATUS_INTERNALSERVERERROR,
3905 "osrfMethodException",
3907 "Unable to build default SELECT clause in JSON query"
3909 free( join_clause );
3914 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3915 jsonObjectSetKey( selhash, core_class, default_list );
3918 // The SELECT clause can be encoded only by a hash
3919 if( !from_function && selhash->type != JSON_HASH ) {
3922 "%s: Expected JSON_HASH for SELECT clause; found %s",
3924 json_type( selhash->type )
3928 osrfAppSessionStatus(
3930 OSRF_STATUS_INTERNALSERVERERROR,
3931 "osrfMethodException",
3933 "Malformed SELECT clause in JSON query"
3935 free( join_clause );
3939 // If you see a null or wild card specifier for the core class, or an
3940 // empty array, replace it with a default SELECT list
3941 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3943 int default_needed = 0; // boolean
3944 if( JSON_STRING == tmp_const->type
3945 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3947 else if( JSON_NULL == tmp_const->type )
3950 if( default_needed ) {
3951 // Build a default SELECT list
3952 jsonObject* default_list = defaultSelectList( core_class );
3953 if( ! default_list ) {
3955 osrfAppSessionStatus(
3957 OSRF_STATUS_INTERNALSERVERERROR,
3958 "osrfMethodException",
3960 "Can't build default SELECT clause in JSON query"
3962 free( join_clause );
3967 jsonObjectSetKey( selhash, core_class, default_list );
3971 // temp buffers for the SELECT list and GROUP BY clause
3972 growing_buffer* select_buf = buffer_init(128);
3973 growing_buffer* group_buf = buffer_init(128);
3975 int aggregate_found = 0; // boolean
3977 // Build a select list
3978 if(from_function) // From a function we select everything
3979 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3982 // Build the SELECT list as SQL
3986 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3987 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3989 const char* cname = selclass_itr->key;
3991 // Make sure the target relation is in the FROM clause.
3993 // At this point join_hash is a step down from the join_hash we
3994 // received as a parameter. If the original was a JSON_STRING,
3995 // then json_hash is now NULL. If the original was a JSON_HASH,
3996 // then json_hash is now the first (and only) entry in it,
3997 // denoting the core class. We've already excluded the
3998 // possibility that the original was a JSON_ARRAY, because in
3999 // that case from_function would be non-NULL, and we wouldn't
4002 // If the current table alias isn't in scope, bail out
4003 ClassInfo* class_info = search_alias( cname );
4004 if( ! class_info ) {
4007 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4012 osrfAppSessionStatus(
4014 OSRF_STATUS_INTERNALSERVERERROR,
4015 "osrfMethodException",
4017 "Selected class not in FROM clause in JSON query"
4019 jsonIteratorFree( selclass_itr );
4020 buffer_free( select_buf );
4021 buffer_free( group_buf );
4022 if( defaultselhash )
4023 jsonObjectFree( defaultselhash );
4024 free( join_clause );
4028 if( selclass->type != JSON_ARRAY ) {
4031 "%s: Malformed SELECT list for class \"%s\"; not an array",
4036 osrfAppSessionStatus(
4038 OSRF_STATUS_INTERNALSERVERERROR,
4039 "osrfMethodException",
4041 "Selected class not in FROM clause in JSON query"
4044 jsonIteratorFree( selclass_itr );
4045 buffer_free( select_buf );
4046 buffer_free( group_buf );
4047 if( defaultselhash )
4048 jsonObjectFree( defaultselhash );
4049 free( join_clause );
4053 // Look up some attributes of the current class
4054 osrfHash* idlClass = class_info->class_def;
4055 osrfHash* class_field_set = class_info->fields;
4056 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4057 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4059 if( 0 == selclass->size ) {
4062 "%s: No columns selected from \"%s\"",
4068 // stitch together the column list for the current table alias...
4069 unsigned long field_idx = 0;
4070 jsonObject* selfield = NULL;
4071 while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4073 // If we need a separator comma, add one
4077 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4080 // if the field specification is a string, add it to the list
4081 if (selfield->type == JSON_STRING) {
4083 // Look up the field in the IDL
4084 const char* col_name = jsonObjectGetString( selfield );
4085 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4087 // No such field in current class
4090 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4096 osrfAppSessionStatus(
4098 OSRF_STATUS_INTERNALSERVERERROR,
4099 "osrfMethodException",
4101 "Selected column not defined in JSON query"
4103 jsonIteratorFree( selclass_itr );
4104 buffer_free( select_buf );
4105 buffer_free( group_buf );
4106 if( defaultselhash )
4107 jsonObjectFree( defaultselhash );
4108 free( join_clause );
4110 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4111 // Virtual field not allowed
4114 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4120 osrfAppSessionStatus(
4122 OSRF_STATUS_INTERNALSERVERERROR,
4123 "osrfMethodException",
4125 "Selected column may not be virtual in JSON query"
4127 jsonIteratorFree( selclass_itr );
4128 buffer_free( select_buf );
4129 buffer_free( group_buf );
4130 if( defaultselhash )
4131 jsonObjectFree( defaultselhash );
4132 free( join_clause );
4138 if (flags & DISABLE_I18N)
4141 i18n = osrfHashGet(field_def, "i18n");
4143 if( str_is_true( i18n ) ) {
4144 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4145 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4146 class_tname, cname, col_name, class_pkey,
4147 cname, class_pkey, locale, col_name );
4149 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"",
4150 cname, col_name, col_name );
4153 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"",
4154 cname, col_name, col_name );
4157 // ... but it could be an object, in which case we check for a Field Transform
4158 } else if (selfield->type == JSON_HASH) {
4160 const char* col_name = jsonObjectGetString(
4161 jsonObjectGetKeyConst( selfield, "column" ) );
4163 // Get the field definition from the IDL
4164 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4166 // No such field in current class
4169 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4175 osrfAppSessionStatus(
4177 OSRF_STATUS_INTERNALSERVERERROR,
4178 "osrfMethodException",
4180 "Selected column is not defined in JSON query"
4182 jsonIteratorFree( selclass_itr );
4183 buffer_free( select_buf );
4184 buffer_free( group_buf );
4185 if( defaultselhash )
4186 jsonObjectFree( defaultselhash );
4187 free( join_clause );
4189 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4190 // No such field in current class
4193 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4199 osrfAppSessionStatus(
4201 OSRF_STATUS_INTERNALSERVERERROR,
4202 "osrfMethodException",
4204 "Selected column is virtual in JSON query"
4206 jsonIteratorFree( selclass_itr );
4207 buffer_free( select_buf );
4208 buffer_free( group_buf );
4209 if( defaultselhash )
4210 jsonObjectFree( defaultselhash );
4211 free( join_clause );
4215 // Decide what to use as a column alias
4217 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4218 _alias = jsonObjectGetString( tmp_const );
4219 } else { // Use field name as the alias
4223 if (jsonObjectGetKeyConst( selfield, "transform" )) {
4224 char* transform_str = searchFieldTransform(
4225 class_info->alias, field_def, selfield );
4226 if( transform_str ) {
4227 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
4228 free(transform_str);
4231 osrfAppSessionStatus(
4233 OSRF_STATUS_INTERNALSERVERERROR,
4234 "osrfMethodException",
4236 "Unable to generate transform function in JSON query"
4238 jsonIteratorFree( selclass_itr );
4239 buffer_free( select_buf );
4240 buffer_free( group_buf );
4241 if( defaultselhash )
4242 jsonObjectFree( defaultselhash );
4243 free( join_clause );
4250 if (flags & DISABLE_I18N)
4253 i18n = osrfHashGet(field_def, "i18n");
4255 if( str_is_true( i18n ) ) {
4256 buffer_fadd( select_buf,
4257 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4258 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4259 class_tname, cname, col_name, class_pkey, cname,
4260 class_pkey, locale, _alias);
4262 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4263 cname, col_name, _alias );
4266 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4267 cname, col_name, _alias);
4274 "%s: Selected item is unexpected JSON type: %s",
4276 json_type( selfield->type )
4279 osrfAppSessionStatus(
4281 OSRF_STATUS_INTERNALSERVERERROR,
4282 "osrfMethodException",
4284 "Ill-formed SELECT item in JSON query"
4286 jsonIteratorFree( selclass_itr );
4287 buffer_free( select_buf );
4288 buffer_free( group_buf );
4289 if( defaultselhash )
4290 jsonObjectFree( defaultselhash );
4291 free( join_clause );
4295 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
4296 if( obj_is_true( agg_obj ) )
4297 aggregate_found = 1;
4299 // Append a comma (except for the first one)
4300 // and add the column to a GROUP BY clause
4304 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4306 buffer_fadd(group_buf, " %d", sel_pos);
4310 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4312 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4313 if ( ! obj_is_true( aggregate_obj ) ) {
4317 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4320 buffer_fadd(group_buf, " %d", sel_pos);
4323 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4327 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4330 _column = searchFieldTransform(class_info->alias, field, selfield);
4331 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4332 OSRF_BUFFER_ADD(group_buf, _column);
4333 _column = searchFieldTransform(class_info->alias, field, selfield);
4340 } // end while -- iterating across SELECT columns
4342 } // end while -- iterating across classes
4344 jsonIteratorFree(selclass_itr);
4348 char* col_list = buffer_release(select_buf);
4350 // Make sure the SELECT list isn't empty. This can happen, for example,
4351 // if we try to build a default SELECT clause from a non-core table.
4354 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
4356 osrfAppSessionStatus(
4358 OSRF_STATUS_INTERNALSERVERERROR,
4359 "osrfMethodException",
4361 "SELECT list is empty"
4364 buffer_free( group_buf );
4365 if( defaultselhash )
4366 jsonObjectFree( defaultselhash );
4367 free( join_clause );
4372 if (from_function) table = searchValueTransform(join_hash);
4373 else table = strdup( curr_query->core.source_def );
4377 osrfAppSessionStatus(
4379 OSRF_STATUS_INTERNALSERVERERROR,
4380 "osrfMethodException",
4382 "Unable to identify table for core class"
4385 buffer_free( group_buf );
4386 if( defaultselhash )
4387 jsonObjectFree( defaultselhash );
4388 free( join_clause );
4392 // Put it all together
4393 growing_buffer* sql_buf = buffer_init(128);
4394 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4398 // Append the join clause, if any
4400 buffer_add(sql_buf, join_clause);
4404 char* order_by_list = NULL;
4405 char* having_buf = NULL;
4407 if (!from_function) {
4409 // Build a WHERE clause, if there is one
4410 if ( search_hash ) {
4411 buffer_add(sql_buf, " WHERE ");
4413 // and it's on the WHERE clause
4414 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4417 osrfAppSessionStatus(
4419 OSRF_STATUS_INTERNALSERVERERROR,
4420 "osrfMethodException",
4422 "Severe query error in WHERE predicate -- see error log for more details"
4425 buffer_free(group_buf);
4426 buffer_free(sql_buf);
4428 jsonObjectFree(defaultselhash);
4432 buffer_add(sql_buf, pred);
4436 // Build a HAVING clause, if there is one
4437 if ( having_hash ) {
4439 // and it's on the the WHERE clause
4440 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4442 if( ! having_buf ) {
4444 osrfAppSessionStatus(
4446 OSRF_STATUS_INTERNALSERVERERROR,
4447 "osrfMethodException",
4449 "Severe query error in HAVING predicate -- see error log for more details"
4452 buffer_free(group_buf);
4453 buffer_free(sql_buf);
4455 jsonObjectFree(defaultselhash);
4460 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4462 // Build an ORDER BY clause, if there is one
4463 if( NULL == order_hash )
4464 ; // No ORDER BY? do nothing
4465 else if( JSON_ARRAY == order_hash->type ) {
4466 // Array of field specifications, each specification being a
4467 // hash to define the class, field, and other details
4469 jsonObject* order_spec;
4470 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4472 if( JSON_HASH != order_spec->type ) {
4473 osrfLogError(OSRF_LOG_MARK,
4474 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4475 MODULENAME, json_type( order_spec->type ) );
4477 osrfAppSessionStatus(
4479 OSRF_STATUS_INTERNALSERVERERROR,
4480 "osrfMethodException",
4482 "Malformed ORDER BY clause -- see error log for more details"
4484 buffer_free( order_buf );
4486 buffer_free(group_buf);
4487 buffer_free(sql_buf);
4489 jsonObjectFree(defaultselhash);
4493 const char* class_alias =
4494 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4496 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4499 OSRF_BUFFER_ADD(order_buf, ", ");
4501 order_buf = buffer_init(128);
4503 if( !field || !class_alias ) {
4504 osrfLogError( OSRF_LOG_MARK,
4505 "%s: Missing class or field name in field specification "
4506 "of ORDER BY clause",
4509 osrfAppSessionStatus(
4511 OSRF_STATUS_INTERNALSERVERERROR,
4512 "osrfMethodException",
4514 "Malformed ORDER BY clause -- see error log for more details"
4516 buffer_free( order_buf );
4518 buffer_free(group_buf);
4519 buffer_free(sql_buf);
4521 jsonObjectFree(defaultselhash);
4525 ClassInfo* order_class_info = search_alias( class_alias );
4526 if( ! order_class_info ) {
4527 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4528 "not in FROM clause", MODULENAME, class_alias );
4530 osrfAppSessionStatus(
4532 OSRF_STATUS_INTERNALSERVERERROR,
4533 "osrfMethodException",
4535 "Invalid class referenced in ORDER BY clause -- "
4536 "see error log for more details"
4539 buffer_free(group_buf);
4540 buffer_free(sql_buf);
4542 jsonObjectFree(defaultselhash);
4546 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4548 osrfLogError( OSRF_LOG_MARK,
4549 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4550 MODULENAME, class_alias, field );
4552 osrfAppSessionStatus(
4554 OSRF_STATUS_INTERNALSERVERERROR,
4555 "osrfMethodException",
4557 "Invalid field referenced in ORDER BY clause -- "
4558 "see error log for more details"
4561 buffer_free(group_buf);
4562 buffer_free(sql_buf);
4564 jsonObjectFree(defaultselhash);
4566 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4567 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4568 MODULENAME, field );
4570 osrfAppSessionStatus(
4572 OSRF_STATUS_INTERNALSERVERERROR,
4573 "osrfMethodException",
4575 "Virtual field in ORDER BY clause -- see error log for more details"
4577 buffer_free( order_buf );
4579 buffer_free(group_buf);
4580 buffer_free(sql_buf);
4582 jsonObjectFree(defaultselhash);
4586 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4587 char* transform_str = searchFieldTransform(
4588 class_alias, field_def, order_spec );
4589 if( ! transform_str ) {
4591 osrfAppSessionStatus(
4593 OSRF_STATUS_INTERNALSERVERERROR,
4594 "osrfMethodException",
4596 "Severe query error in ORDER BY clause -- "
4597 "see error log for more details"
4599 buffer_free( order_buf );
4601 buffer_free(group_buf);
4602 buffer_free(sql_buf);
4604 jsonObjectFree(defaultselhash);
4608 OSRF_BUFFER_ADD( order_buf, transform_str );
4609 free( transform_str );
4612 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4614 const char* direction =
4615 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4617 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4618 OSRF_BUFFER_ADD( order_buf, " DESC" );
4620 OSRF_BUFFER_ADD( order_buf, " ASC" );
4623 } else if( JSON_HASH == order_hash->type ) {
4624 // This hash is keyed on class alias. Each class has either
4625 // an array of field names or a hash keyed on field name.
4626 jsonIterator* class_itr = jsonNewIterator( order_hash );
4627 while ( (snode = jsonIteratorNext( class_itr )) ) {
4629 ClassInfo* order_class_info = search_alias( class_itr->key );
4630 if( ! order_class_info ) {
4631 osrfLogError(OSRF_LOG_MARK,
4632 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4633 MODULENAME, class_itr->key );
4635 osrfAppSessionStatus(
4637 OSRF_STATUS_INTERNALSERVERERROR,
4638 "osrfMethodException",
4640 "Invalid class referenced in ORDER BY clause -- "
4641 "see error log for more details"
4643 jsonIteratorFree( class_itr );
4644 buffer_free( order_buf );
4646 buffer_free(group_buf);
4647 buffer_free(sql_buf);
4649 jsonObjectFree(defaultselhash);
4653 osrfHash* field_list_def = order_class_info->fields;
4655 if ( snode->type == JSON_HASH ) {
4657 // Hash is keyed on field names from the current class. For each field
4658 // there is another layer of hash to define the sorting details, if any,
4659 // or a string to indicate direction of sorting.
4660 jsonIterator* order_itr = jsonNewIterator( snode );
4661 while ( (onode = jsonIteratorNext( order_itr )) ) {
4663 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4665 osrfLogError( OSRF_LOG_MARK,
4666 "%s: Invalid field \"%s\" in ORDER BY clause",
4667 MODULENAME, order_itr->key );
4669 osrfAppSessionStatus(
4671 OSRF_STATUS_INTERNALSERVERERROR,
4672 "osrfMethodException",
4674 "Invalid field in ORDER BY clause -- see error log for more details"
4676 jsonIteratorFree( order_itr );
4677 jsonIteratorFree( class_itr );
4678 buffer_free( order_buf );
4680 buffer_free(group_buf);
4681 buffer_free(sql_buf);
4683 jsonObjectFree(defaultselhash);
4685 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4686 osrfLogError( OSRF_LOG_MARK,
4687 "%s: Virtual field \"%s\" in ORDER BY clause",
4688 MODULENAME, order_itr->key );
4690 osrfAppSessionStatus(
4692 OSRF_STATUS_INTERNALSERVERERROR,
4693 "osrfMethodException",
4695 "Virtual field in ORDER BY clause -- "
4696 "see error log for more details"
4698 jsonIteratorFree( order_itr );
4699 jsonIteratorFree( class_itr );
4700 buffer_free( order_buf );
4702 buffer_free(group_buf);
4703 buffer_free(sql_buf);
4705 jsonObjectFree(defaultselhash);
4709 const char* direction = NULL;
4710 if ( onode->type == JSON_HASH ) {
4711 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4712 string = searchFieldTransform(
4714 osrfHashGet( field_list_def, order_itr->key ),
4718 if( ctx ) osrfAppSessionStatus(
4720 OSRF_STATUS_INTERNALSERVERERROR,
4721 "osrfMethodException",
4723 "Severe query error in ORDER BY clause -- "
4724 "see error log for more details"
4726 jsonIteratorFree( order_itr );
4727 jsonIteratorFree( class_itr );
4729 buffer_free(group_buf);
4730 buffer_free(order_buf);
4731 buffer_free(sql_buf);
4733 jsonObjectFree(defaultselhash);
4737 growing_buffer* field_buf = buffer_init(16);
4738 buffer_fadd( field_buf, "\"%s\".%s",
4739 class_itr->key, order_itr->key );
4740 string = buffer_release(field_buf);
4743 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4744 const char* dir = jsonObjectGetString(tmp_const);
4745 if (!strncasecmp(dir, "d", 1)) {
4746 direction = " DESC";
4752 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4753 osrfLogError( OSRF_LOG_MARK,
4754 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4755 MODULENAME, json_type( onode->type ) );
4757 osrfAppSessionStatus(
4759 OSRF_STATUS_INTERNALSERVERERROR,
4760 "osrfMethodException",
4762 "Malformed ORDER BY clause -- see error log for more details"
4764 jsonIteratorFree( order_itr );
4765 jsonIteratorFree( class_itr );
4767 buffer_free(group_buf);
4768 buffer_free(order_buf);
4769 buffer_free(sql_buf);
4771 jsonObjectFree(defaultselhash);
4775 string = strdup(order_itr->key);
4776 const char* dir = jsonObjectGetString(onode);
4777 if (!strncasecmp(dir, "d", 1)) {
4778 direction = " DESC";
4785 OSRF_BUFFER_ADD(order_buf, ", ");
4787 order_buf = buffer_init(128);
4789 OSRF_BUFFER_ADD(order_buf, string);
4793 OSRF_BUFFER_ADD(order_buf, direction);
4797 jsonIteratorFree(order_itr);
4799 } else if ( snode->type == JSON_ARRAY ) {
4801 // Array is a list of fields from the current class
4802 unsigned long order_idx = 0;
4803 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4805 const char* _f = jsonObjectGetString( onode );
4807 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4809 osrfLogError( OSRF_LOG_MARK,
4810 "%s: Invalid field \"%s\" in ORDER BY clause",
4813 osrfAppSessionStatus(
4815 OSRF_STATUS_INTERNALSERVERERROR,
4816 "osrfMethodException",
4818 "Invalid field in ORDER BY clause -- "
4819 "see error log for more details"
4821 jsonIteratorFree( class_itr );
4822 buffer_free( order_buf );
4824 buffer_free(group_buf);
4825 buffer_free(sql_buf);
4827 jsonObjectFree(defaultselhash);
4829 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4830 osrfLogError( OSRF_LOG_MARK,
4831 "%s: Virtual field \"%s\" in ORDER BY clause",
4834 osrfAppSessionStatus(
4836 OSRF_STATUS_INTERNALSERVERERROR,
4837 "osrfMethodException",
4839 "Virtual field in ORDER BY clause -- "
4840 "see error log for more details"
4842 jsonIteratorFree( class_itr );
4843 buffer_free( order_buf );
4845 buffer_free(group_buf);
4846 buffer_free(sql_buf);
4848 jsonObjectFree(defaultselhash);
4853 OSRF_BUFFER_ADD(order_buf, ", ");
4855 order_buf = buffer_init(128);
4857 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
4861 // IT'S THE OOOOOOOOOOOLD STYLE!
4863 osrfLogError(OSRF_LOG_MARK,
4864 "%s: Possible SQL injection attempt; direct order by is not allowed",
4867 osrfAppSessionStatus(
4869 OSRF_STATUS_INTERNALSERVERERROR,
4870 "osrfMethodException",
4872 "Severe query error -- see error log for more details"
4877 buffer_free(group_buf);
4878 buffer_free(order_buf);
4879 buffer_free(sql_buf);
4881 jsonObjectFree(defaultselhash);
4882 jsonIteratorFree(class_itr);
4886 jsonIteratorFree( class_itr );
4888 osrfLogError(OSRF_LOG_MARK,
4889 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4890 MODULENAME, json_type( order_hash->type ) );
4892 osrfAppSessionStatus(
4894 OSRF_STATUS_INTERNALSERVERERROR,
4895 "osrfMethodException",
4897 "Malformed ORDER BY clause -- see error log for more details"
4899 buffer_free( order_buf );
4901 buffer_free(group_buf);
4902 buffer_free(sql_buf);
4904 jsonObjectFree(defaultselhash);
4909 order_by_list = buffer_release( order_buf );
4913 string = buffer_release(group_buf);
4915 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4916 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4917 OSRF_BUFFER_ADD( sql_buf, string );
4922 if( having_buf && *having_buf ) {
4923 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4924 OSRF_BUFFER_ADD( sql_buf, having_buf );
4928 if( order_by_list ) {
4930 if ( *order_by_list ) {
4931 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4932 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4935 free( order_by_list );
4939 const char* str = jsonObjectGetString(limit);
4940 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
4944 const char* str = jsonObjectGetString(offset);
4945 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
4948 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4951 jsonObjectFree(defaultselhash);
4953 return buffer_release(sql_buf);
4955 } // end of SELECT()
4957 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4959 const char* locale = osrf_message_get_last_locale();
4961 osrfHash* fields = osrfHashGet(meta, "fields");
4962 char* core_class = osrfHashGet(meta, "classname");
4964 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4966 jsonObject* node = NULL;
4967 jsonObject* snode = NULL;
4968 jsonObject* onode = NULL;
4969 const jsonObject* _tmp = NULL;
4970 jsonObject* selhash = NULL;
4971 jsonObject* defaultselhash = NULL;
4973 growing_buffer* sql_buf = buffer_init(128);
4974 growing_buffer* select_buf = buffer_init(128);
4976 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4977 defaultselhash = jsonNewObjectType(JSON_HASH);
4978 selhash = defaultselhash;
4981 // If there's no SELECT list for the core class, build one
4982 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4983 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4985 // Add every non-virtual field to the field list
4986 osrfHash* field_def = NULL;
4987 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4988 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4989 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4990 const char* field = osrfHashIteratorKey( field_itr );
4991 jsonObjectPush( field_list, jsonNewObject( field ) );
4994 osrfHashIteratorFree( field_itr );
4995 jsonObjectSetKey( selhash, core_class, field_list );
4999 jsonIterator* class_itr = jsonNewIterator( selhash );
5000 while ( (snode = jsonIteratorNext( class_itr )) ) {
5002 const char* cname = class_itr->key;
5003 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5007 if (strcmp(core_class,class_itr->key)) {
5011 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
5013 jsonObjectFree(found);
5017 jsonObjectFree(found);
5020 jsonIterator* select_itr = jsonNewIterator( snode );
5021 while ( (node = jsonIteratorNext( select_itr )) ) {
5022 const char* item_str = jsonObjectGetString( node );
5023 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5024 char* fname = osrfHashGet(field, "name");
5032 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
5037 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
5038 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5041 i18n = osrfHashGet(field, "i18n");
5043 if( str_is_true( i18n ) ) {
5044 char* pkey = osrfHashGet(idlClass, "primarykey");
5045 char* tname = osrfHashGet(idlClass, "tablename");
5047 buffer_fadd(select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5048 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5049 tname, cname, fname, pkey, cname, pkey, locale, fname);
5051 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
5054 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
5058 jsonIteratorFree(select_itr);
5061 jsonIteratorFree(class_itr);
5063 char* col_list = buffer_release(select_buf);
5064 char* table = getRelation(meta);
5066 table = strdup( "(null)" );
5068 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5072 // Clear the query stack (as a fail-safe precaution against possible
5073 // leftover garbage); then push the first query frame onto the stack.
5074 clear_query_stack();
5076 if( add_query_core( NULL, core_class ) ) {
5078 osrfAppSessionStatus(
5080 OSRF_STATUS_INTERNALSERVERERROR,
5081 "osrfMethodException",
5083 "Unable to build query frame for core class"
5089 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5090 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
5091 OSRF_BUFFER_ADD(sql_buf, join_clause);
5095 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5096 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
5098 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
5100 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5102 osrfAppSessionStatus(
5104 OSRF_STATUS_INTERNALSERVERERROR,
5105 "osrfMethodException",
5107 "Severe query error -- see error log for more details"
5109 buffer_free(sql_buf);
5111 jsonObjectFree(defaultselhash);
5112 clear_query_stack();
5115 buffer_add(sql_buf, pred);
5120 char* string = NULL;
5121 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
5123 growing_buffer* order_buf = buffer_init(128);
5126 jsonIterator* class_itr = jsonNewIterator( _tmp );
5127 while ( (snode = jsonIteratorNext( class_itr )) ) {
5129 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
5132 if ( snode->type == JSON_HASH ) {
5134 jsonIterator* order_itr = jsonNewIterator( snode );
5135 while ( (onode = jsonIteratorNext( order_itr )) ) {
5137 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
5138 class_itr->key, order_itr->key );
5142 char* direction = NULL;
5143 if ( onode->type == JSON_HASH ) {
5144 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
5145 string = searchFieldTransform( class_itr->key, field_def, onode );
5147 osrfAppSessionStatus(
5149 OSRF_STATUS_INTERNALSERVERERROR,
5150 "osrfMethodException",
5152 "Severe query error in ORDER BY clause -- "
5153 "see error log for more details"
5155 jsonIteratorFree( order_itr );
5156 jsonIteratorFree( class_itr );
5157 buffer_free( order_buf );
5158 buffer_free( sql_buf );
5159 if( defaultselhash )
5160 jsonObjectFree( defaultselhash );
5161 clear_query_stack();
5165 growing_buffer* field_buf = buffer_init(16);
5166 buffer_fadd( field_buf, "\"%s\".%s",
5167 class_itr->key, order_itr->key );
5168 string = buffer_release(field_buf);
5171 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
5172 const char* dir = jsonObjectGetString(_tmp);
5173 if (!strncasecmp(dir, "d", 1)) {
5174 direction = " DESC";
5180 string = strdup(order_itr->key);
5181 const char* dir = jsonObjectGetString(onode);
5182 if (!strncasecmp(dir, "d", 1)) {
5183 direction = " DESC";
5192 buffer_add(order_buf, ", ");
5195 buffer_add(order_buf, string);
5199 buffer_add(order_buf, direction);
5203 jsonIteratorFree(order_itr);
5206 const char* str = jsonObjectGetString(snode);
5207 buffer_add(order_buf, str);
5213 jsonIteratorFree(class_itr);
5215 string = buffer_release(order_buf);
5218 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5219 OSRF_BUFFER_ADD( sql_buf, string );
5225 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
5226 const char* str = jsonObjectGetString(_tmp);
5234 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
5236 const char* str = jsonObjectGetString(_tmp);
5246 jsonObjectFree(defaultselhash);
5247 clear_query_stack();
5249 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
5250 return buffer_release(sql_buf);
5253 int doJSONSearch ( osrfMethodContext* ctx ) {
5254 if(osrfMethodVerifyContext( ctx )) {
5255 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5259 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
5264 dbhandle = writehandle;
5266 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
5270 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
5271 flags |= SELECT_DISTINCT;
5273 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
5274 flags |= DISABLE_I18N;
5276 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
5277 clear_query_stack(); // a possibly needless precaution
5278 char* sql = buildQuery( ctx, hash, flags );
5279 clear_query_stack();
5286 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
5287 dbi_result result = dbi_conn_query(dbhandle, sql);
5290 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
5292 if (dbi_result_first_row(result)) {
5293 /* JSONify the result */
5294 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
5297 jsonObject* return_val = oilsMakeJSONFromResult( result );
5298 osrfAppRespond( ctx, return_val );
5299 jsonObjectFree( return_val );
5300 } while (dbi_result_next_row(result));
5303 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
5306 osrfAppRespondComplete( ctx, NULL );
5308 /* clean up the query */
5309 dbi_result_free(result);
5313 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
5314 osrfAppSessionStatus(
5316 OSRF_STATUS_INTERNALSERVERERROR,
5317 "osrfMethodException",
5319 "Severe query error -- see error log for more details"
5327 // The last parameter, err, is used to report an error condition by updating an int owned by
5328 // the calling code.
5330 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5331 // It is the responsibility of the calling code to initialize *err before the
5332 // call, so that it will be able to make sense of the result.
5334 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5335 // redundant anyway.
5336 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
5337 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5340 dbhandle = writehandle;
5342 char* core_class = osrfHashGet( class_meta, "classname" );
5343 char* pkey = osrfHashGet( class_meta, "primarykey" );
5345 const jsonObject* _tmp;
5347 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5349 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
5354 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
5356 dbi_result result = dbi_conn_query(dbhandle, sql);
5357 if( NULL == result ) {
5358 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
5359 MODULENAME, osrfHashGet( class_meta, "fieldmapper" ), sql);
5360 osrfAppSessionStatus(
5362 OSRF_STATUS_INTERNALSERVERERROR,
5363 "osrfMethodException",
5365 "Severe query error -- see error log for more details"
5372 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
5375 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
5376 jsonObject* row_obj = NULL;
5378 if (dbi_result_first_row(result)) {
5380 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5381 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5382 // eliminate the duplicates.
5383 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
5384 osrfHash* dedup = osrfNewHash();
5386 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5387 char* pkey_val = oilsFMGetString( row_obj, pkey );
5388 if ( osrfHashGet( dedup, pkey_val ) ) {
5389 jsonObjectFree( row_obj );
5392 osrfHashSet( dedup, pkey_val, pkey_val );
5393 jsonObjectPush( res_list, row_obj );
5395 } while (dbi_result_next_row(result));
5396 osrfHashFree(dedup);
5399 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
5403 /* clean up the query */
5404 dbi_result_free(result);
5407 // If we're asked to flesh, and there's anything to flesh, then flesh.
5408 if (res_list->size && query_hash) {
5409 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5411 // Get the flesh depth
5412 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5413 if ( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5414 flesh_depth = max_flesh_depth;
5416 // We need a non-zero flesh depth, and a list of fields to flesh
5417 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5418 if ( temp_blob && flesh_depth > 0 ) {
5420 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5421 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5423 osrfStringArray* link_fields = NULL;
5424 osrfHash* links = osrfHashGet( class_meta, "links" );
5426 // Make an osrfStringArray of the names of fields to be fleshed
5428 if (flesh_fields->size == 1) {
5429 const char* _t = jsonObjectGetString(
5430 jsonObjectGetIndex( flesh_fields, 0 ) );
5431 if (!strcmp(_t,"*"))
5432 link_fields = osrfHashKeys( links );
5437 link_fields = osrfNewStringArray(1);
5438 jsonIterator* _i = jsonNewIterator( flesh_fields );
5439 while ((_f = jsonIteratorNext( _i ))) {
5440 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5442 jsonIteratorFree(_i);
5446 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5448 // Iterate over the JSON_ARRAY of rows
5450 unsigned long res_idx = 0;
5451 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5454 const char* link_field;
5456 // Iterate over the list of fleshable fields
5457 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5459 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
5461 osrfHash* kid_link = osrfHashGet(links, link_field);
5463 continue; // Not a link field; skip it
5465 osrfHash* field = osrfHashGet(fields, link_field);
5467 continue; // Not a field at all; skip it (IDL is ill-formed)
5469 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
5471 continue; // The class it links to doesn't exist; skip it
5473 const char* reltype = osrfHashGet( kid_link, "reltype" );
5475 continue; // No reltype; skip it (IDL is ill-formed)
5477 osrfHash* value_field = field;
5479 if ( !strcmp( reltype, "has_many" )
5480 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5481 value_field = osrfHashGet(
5482 fields, osrfHashGet( class_meta, "primarykey" ) );
5485 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5487 if (link_map->size > 0) {
5488 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
5491 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5496 osrfHashGet(kid_link, "class"),
5503 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5504 osrfHashGet(kid_link, "field"),
5505 osrfHashGet(kid_link, "class"),
5506 osrfHashGet(kid_link, "key"),
5507 osrfHashGet(kid_link, "reltype")
5510 const char* search_key = jsonObjectGetString(
5511 jsonObjectGetIndex( cur,
5512 atoi( osrfHashGet(value_field, "array_position") )
5517 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
5521 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
5523 // construct WHERE clause
5524 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
5527 osrfHashGet(kid_link, "key"),
5528 jsonNewObject( search_key )
5531 // construct the rest of the query, mostly
5532 // by copying pieces of the previous level of query
5533 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
5534 jsonObjectSetKey( rest_of_query, "flesh",
5535 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5539 jsonObjectSetKey( rest_of_query, "flesh_fields",
5540 jsonObjectClone(flesh_blob) );
5542 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
5543 jsonObjectSetKey( rest_of_query, "order_by",
5544 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
5548 if (jsonObjectGetKeyConst(query_hash, "select")) {
5549 jsonObjectSetKey( rest_of_query, "select",
5550 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
5554 // do the query, recursively, to expand the fleshable field
5555 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5556 where_clause, rest_of_query, err);
5558 jsonObjectFree( where_clause );
5559 jsonObjectFree( rest_of_query );
5562 osrfStringArrayFree(link_fields);
5563 jsonObjectFree(res_list);
5564 jsonObjectFree(flesh_blob);
5568 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects",
5569 osrfHashGet(kid_link, "class"), kids->size);
5571 // Traverse the result set
5572 jsonObject* X = NULL;
5573 if ( link_map->size > 0 && kids->size > 0 ) {
5575 kids = jsonNewObjectType(JSON_ARRAY);
5577 jsonObject* _k_node;
5578 unsigned long res_idx = 0;
5579 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5585 (unsigned long)atoi(
5591 osrfHashGet(kid_link, "class")
5595 osrfStringArrayGetString( link_map, 0 )
5603 } // end while loop traversing X
5606 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" ))
5607 || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
5608 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5609 osrfHashGet(kid_link, "field"));
5612 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5613 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
5617 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) {
5619 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5620 osrfHashGet( kid_link, "field" ) );
5623 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5624 jsonObjectClone( kids )
5629 jsonObjectFree(kids);
5633 jsonObjectFree( kids );
5635 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5636 osrfHashGet( kid_link, "field" ) );
5637 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
5639 } // end while loop traversing list of fleshable fields
5640 } // end while loop traversing res_list
5641 jsonObjectFree( flesh_blob );
5642 osrfStringArrayFree(link_fields);
5651 static int doUpdate(osrfMethodContext* ctx ) {
5652 if(osrfMethodVerifyContext( ctx )) {
5653 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5658 timeout_needs_resetting = 1;
5660 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5662 jsonObject* target = NULL;
5664 target = jsonObjectGetIndex( ctx->params, 1 );
5666 target = jsonObjectGetIndex( ctx->params, 0 );
5668 if (!verifyObjectClass(ctx, target)) {
5669 osrfAppRespondComplete( ctx, NULL );
5673 if( getXactId( ctx ) == NULL ) {
5674 osrfAppSessionStatus(
5676 OSRF_STATUS_BADREQUEST,
5677 "osrfMethodException",
5679 "No active transaction -- required for UPDATE"
5681 osrfAppRespondComplete( ctx, NULL );
5685 // The following test is harmless but redundant. If a class is
5686 // readonly, we don't register an update method for it.
5687 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5688 osrfAppSessionStatus(
5690 OSRF_STATUS_BADREQUEST,
5691 "osrfMethodException",
5693 "Cannot UPDATE readonly class"
5695 osrfAppRespondComplete( ctx, NULL );
5699 dbhandle = writehandle;
5700 const char* trans_id = getXactId( ctx );
5702 // Set the last_xact_id
5703 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5705 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5706 trans_id, target->classname, index);
5707 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
5710 char* pkey = osrfHashGet(meta, "primarykey");
5711 osrfHash* fields = osrfHashGet(meta, "fields");
5713 char* id = oilsFMGetString( target, pkey );
5717 "%s updating %s object with %s = %s",
5719 osrfHashGet(meta, "fieldmapper"),
5724 growing_buffer* sql = buffer_init(128);
5725 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
5728 osrfHash* field_def = NULL;
5729 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5730 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
5732 // Skip virtual fields, and the primary key
5733 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5736 const char* field_name = osrfHashIteratorKey( field_itr );
5737 if( ! strcmp( field_name, pkey ) )
5740 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5742 int value_is_numeric = 0; // boolean
5744 if (field_object && field_object->classname) {
5745 value = oilsFMGetString(
5747 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
5749 } else if( field_object && JSON_BOOL == field_object->type ) {
5750 if( jsonBoolIsTrue( field_object ) )
5751 value = strdup( "t" );
5753 value = strdup( "f" );
5755 value = jsonObjectToSimpleString( field_object );
5756 if( field_object && JSON_NUMBER == field_object->type )
5757 value_is_numeric = 1;
5760 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5761 osrfHashGet(meta, "fieldmapper"), field_name, value);
5763 if (!field_object || field_object->type == JSON_NULL) {
5764 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
5765 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5769 OSRF_BUFFER_ADD_CHAR(sql, ',');
5770 buffer_fadd( sql, " %s = NULL", field_name );
5773 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5777 OSRF_BUFFER_ADD_CHAR(sql, ',');
5779 const char* numtype = get_datatype( field_def );
5780 if ( !strncmp( numtype, "INT", 3 ) ) {
5781 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
5782 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
5783 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
5785 // Must really be intended as a string, so quote it
5786 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5787 buffer_fadd( sql, " %s = %s", field_name, value );
5789 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5791 osrfAppSessionStatus(
5793 OSRF_STATUS_INTERNALSERVERERROR,
5794 "osrfMethodException",
5796 "Error quoting string -- please see the error log for more details"
5800 osrfHashIteratorFree( field_itr );
5802 osrfAppRespondComplete( ctx, NULL );
5807 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5810 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5814 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5815 buffer_fadd( sql, " %s = %s", field_name, value );
5817 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5818 osrfAppSessionStatus(
5820 OSRF_STATUS_INTERNALSERVERERROR,
5821 "osrfMethodException",
5823 "Error quoting string -- please see the error log for more details"
5827 osrfHashIteratorFree( field_itr );
5829 osrfAppRespondComplete( ctx, NULL );
5838 osrfHashIteratorFree( field_itr );
5840 jsonObject* obj = jsonNewObject(id);
5842 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5843 dbi_conn_quote_string(dbhandle, &id);
5845 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5847 char* query = buffer_release(sql);
5848 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
5850 dbi_result result = dbi_conn_query(dbhandle, query);
5854 jsonObjectFree(obj);
5855 obj = jsonNewObject(NULL);
5858 "%s ERROR updating %s object with %s = %s",
5860 osrfHashGet(meta, "fieldmapper"),
5867 osrfAppRespondComplete( ctx, obj );
5868 jsonObjectFree( obj );
5872 static int doDelete( osrfMethodContext* ctx ) {
5873 if(osrfMethodVerifyContext( ctx )) {
5874 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5879 timeout_needs_resetting = 1;
5881 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5883 if( getXactId( ctx ) == NULL ) {
5884 osrfAppSessionStatus(
5886 OSRF_STATUS_BADREQUEST,
5887 "osrfMethodException",
5889 "No active transaction -- required for DELETE"
5891 osrfAppRespondComplete( ctx, NULL );
5895 // The following test is harmless but redundant. If a class is
5896 // readonly, we don't register a delete method for it.
5897 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5898 osrfAppSessionStatus(
5900 OSRF_STATUS_BADREQUEST,
5901 "osrfMethodException",
5903 "Cannot DELETE readonly class"
5905 osrfAppRespondComplete( ctx, NULL );
5909 dbhandle = writehandle;
5911 char* pkey = osrfHashGet(meta, "primarykey");
5918 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
5919 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5920 osrfAppRespondComplete( ctx, NULL );
5924 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5926 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5927 osrfAppRespondComplete( ctx, NULL );
5930 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
5935 "%s deleting %s object with %s = %s",
5937 osrfHashGet(meta, "fieldmapper"),
5942 jsonObject* obj = jsonNewObject(id);
5944 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5945 dbi_conn_quote_string(writehandle, &id);
5947 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;",
5948 osrfHashGet(meta, "tablename"), pkey, id);
5951 jsonObjectFree(obj);
5952 obj = jsonNewObject(NULL);
5955 "%s ERROR deleting %s object with %s = %s",
5957 osrfHashGet(meta, "fieldmapper"),
5965 osrfAppRespondComplete( ctx, obj );
5966 jsonObjectFree( obj );
5971 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5972 @param result An iterator for a result set; we only look at the current row.
5973 @param @meta Pointer to the class metadata for the core class.
5974 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5976 If a column is not defined in the IDL, or if it has no array_position defined for it in
5977 the IDL, or if it is defined as virtual, ignore it.
5979 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5980 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5981 array_position in the IDL.
5983 A field defined in the IDL but not represented in the returned row will leave a hole
5984 in the JSON_ARRAY. In effect it will be treated as a null value.
5986 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5987 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5988 classname corresponding to the @a meta argument.
5990 The calling code is responsible for freeing the the resulting jsonObject by calling
5993 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5994 if(!(result && meta)) return jsonNULL;
5996 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5997 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
5998 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
6000 osrfHash* fields = osrfHashGet(meta, "fields");
6002 int columnIndex = 1;
6003 const char* columnName;
6005 /* cycle through the columns in the row returned from the database */
6006 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
6008 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
6010 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6012 /* determine the field type and storage attributes */
6013 unsigned short type = dbi_result_get_field_type_idx(result, columnIndex);
6014 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
6016 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6017 // or if it has no sequence number there, or if it's virtual, skip it.
6018 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6021 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
6022 continue; // skip this column: IDL says it's virtual
6024 const char* pos = (char*)osrfHashGet(_f, "array_position");
6025 if ( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6026 continue; // since we assign sequence numbers dynamically as we load the IDL.
6028 fmIndex = atoi( pos );
6029 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
6031 continue; // This field is not defined in the IDL
6034 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6035 // sequence number from the IDL (which is likely to be different from the sequence
6036 // of columns in the SELECT clause).
6037 if (dbi_result_field_is_null_idx(result, columnIndex)) {
6038 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
6043 case DBI_TYPE_INTEGER :
6045 if( attr & DBI_INTEGER_SIZE8 )
6046 jsonObjectSetIndex( object, fmIndex,
6047 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
6049 jsonObjectSetIndex( object, fmIndex,
6050 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
6054 case DBI_TYPE_DECIMAL :
6055 jsonObjectSetIndex( object, fmIndex,
6056 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
6059 case DBI_TYPE_STRING :
6064 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
6069 case DBI_TYPE_DATETIME : {
6071 char dt_string[256] = "";
6074 // Fetch the date column as a time_t
6075 time_t _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
6077 // Translate the time_t to a human-readable string
6078 if (!(attr & DBI_DATETIME_DATE)) {
6079 gmtime_r( &_tmp_dt, &gmdt );
6080 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
6081 } else if (!(attr & DBI_DATETIME_TIME)) {
6082 localtime_r( &_tmp_dt, &gmdt );
6083 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
6085 localtime_r( &_tmp_dt, &gmdt );
6086 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
6089 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
6093 case DBI_TYPE_BINARY :
6094 osrfLogError( OSRF_LOG_MARK,
6095 "Can't do binary at column %s : index %d", columnName, columnIndex);
6104 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6105 if(!result) return jsonNULL;
6107 jsonObject* object = jsonNewObject(NULL);
6110 char dt_string[256];
6114 int columnIndex = 1;
6116 unsigned short type;
6117 const char* columnName;
6119 /* cycle through the column list */
6120 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
6122 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
6124 fmIndex = -1; // reset the position
6126 /* determine the field type and storage attributes */
6127 type = dbi_result_get_field_type_idx(result, columnIndex);
6128 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
6130 if (dbi_result_field_is_null_idx(result, columnIndex)) {
6131 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
6136 case DBI_TYPE_INTEGER :
6138 if( attr & DBI_INTEGER_SIZE8 )
6139 jsonObjectSetKey( object, columnName,
6140 jsonNewNumberObject(dbi_result_get_longlong_idx(
6141 result, columnIndex)) );
6143 jsonObjectSetKey( object, columnName,
6144 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
6147 case DBI_TYPE_DECIMAL :
6148 jsonObjectSetKey( object, columnName,
6149 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
6152 case DBI_TYPE_STRING :
6153 jsonObjectSetKey( object, columnName,
6154 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
6157 case DBI_TYPE_DATETIME :
6159 memset(dt_string, '\0', sizeof(dt_string));
6160 memset(&gmdt, '\0', sizeof(gmdt));
6162 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
6165 if (!(attr & DBI_DATETIME_DATE)) {
6166 gmtime_r( &_tmp_dt, &gmdt );
6167 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
6168 } else if (!(attr & DBI_DATETIME_TIME)) {
6169 localtime_r( &_tmp_dt, &gmdt );
6170 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
6172 localtime_r( &_tmp_dt, &gmdt );
6173 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
6176 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
6179 case DBI_TYPE_BINARY :
6180 osrfLogError( OSRF_LOG_MARK,
6181 "Can't do binary at column %s : index %d", columnName, columnIndex );
6185 } // end while loop traversing result
6190 // Interpret a string as true or false
6191 static int str_is_true( const char* str ) {
6192 if( NULL == str || strcasecmp( str, "true" ) )
6198 // Interpret a jsonObject as true or false
6199 static int obj_is_true( const jsonObject* obj ) {
6202 else switch( obj->type )
6210 if( strcasecmp( obj->value.s, "true" ) )
6214 case JSON_NUMBER : // Support 1/0 for perl's sake
6215 if( jsonObjectGetNumber( obj ) == 1.0 )
6224 // Translate a numeric code into a text string identifying a type of
6225 // jsonObject. To be used for building error messages.
6226 static const char* json_type( int code ) {
6232 return "JSON_ARRAY";
6234 return "JSON_STRING";
6236 return "JSON_NUMBER";
6242 return "(unrecognized)";
6246 // Extract the "primitive" attribute from an IDL field definition.
6247 // If we haven't initialized the app, then we must be running in
6248 // some kind of testbed. In that case, default to "string".
6249 static const char* get_primitive( osrfHash* field ) {
6250 const char* s = osrfHashGet( field, "primitive" );
6252 if( child_initialized )
6255 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6257 osrfHashGet( field, "name" )
6265 // Extract the "datatype" attribute from an IDL field definition.
6266 // If we haven't initialized the app, then we must be running in
6267 // some kind of testbed. In that case, default to to NUMERIC,
6268 // since we look at the datatype only for numbers.
6269 static const char* get_datatype( osrfHash* field ) {
6270 const char* s = osrfHashGet( field, "datatype" );
6272 if( child_initialized )
6275 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6277 osrfHashGet( field, "name" )
6286 @brief Determine whether a string is potentially a valid SQL identifier.
6287 @param s The identifier to be tested.
6288 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6290 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6291 need to follow all the rules exactly, such as requiring that the first character not
6294 We allow leading and trailing white space. In between, we do not allow punctuation
6295 (except for underscores and dollar signs), control characters, or embedded white space.
6297 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6298 for the foreseeable future such quoted identifiers are not likely to be an issue.
6300 static int is_identifier( const char* s) {
6304 // Skip leading white space
6305 while( isspace( (unsigned char) *s ) )
6309 return 0; // Nothing but white space? Not okay.
6311 // Check each character until we reach white space or
6312 // end-of-string. Letters, digits, underscores, and
6313 // dollar signs are okay. With the exception of periods
6314 // (as in schema.identifier), control characters and other
6315 // punctuation characters are not okay. Anything else
6316 // is okay -- it could for example be part of a multibyte
6317 // UTF8 character such as a letter with diacritical marks,
6318 // and those are allowed.
6320 if( isalnum( (unsigned char) *s )
6324 ; // Fine; keep going
6325 else if( ispunct( (unsigned char) *s )
6326 || iscntrl( (unsigned char) *s ) )
6329 } while( *s && ! isspace( (unsigned char) *s ) );
6331 // If we found any white space in the above loop,
6332 // the rest had better be all white space.
6334 while( isspace( (unsigned char) *s ) )
6338 return 0; // White space was embedded within non-white space
6344 @brief Determine whether to accept a character string as a comparison operator.
6345 @param op The candidate comparison operator.
6346 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6348 We don't validate the operator for real. We just make sure that it doesn't contain
6349 any semicolons or white space (with special exceptions for a few specific operators).
6350 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6351 space but it's still not a valid operator, then the database will complain.
6353 Another approach would be to compare the string against a short list of approved operators.
6354 We don't do that because we want to allow custom operators like ">100*", which at this
6355 writing would be difficult or impossible to express otherwise in a JSON query.
6357 static int is_good_operator( const char* op ) {
6358 if( !op ) return 0; // Sanity check
6362 if( isspace( (unsigned char) *s ) ) {
6363 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6364 // and IS NOT DISTINCT FROM.
6365 if( !strcasecmp( op, "similar to" ) )
6367 else if( !strcasecmp( op, "is distinct from" ) )
6369 else if( !strcasecmp( op, "is not distinct from" ) )
6374 else if( ';' == *s )
6382 @name Query Frame Management
6384 The following machinery supports a stack of query frames for use by SELECT().
6386 A query frame caches information about one level of a SELECT query. When we enter
6387 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6389 The query frame stores information about the core class, and about any joined classes
6392 The main purpose is to map table aliases to classes and tables, so that a query can
6393 join to the same table more than once. A secondary goal is to reduce the number of
6394 lookups in the IDL by caching the results.
6398 #define STATIC_CLASS_INFO_COUNT 3
6400 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6403 @brief Allocate a ClassInfo as raw memory.
6404 @return Pointer to the newly allocated ClassInfo.
6406 Except for the in_use flag, which is used only by the allocation and deallocation
6407 logic, we don't initialize the ClassInfo here.
6409 static ClassInfo* allocate_class_info( void ) {
6410 // In order to reduce the number of mallocs and frees, we return a static
6411 // instance of ClassInfo, if we can find one that we're not already using.
6412 // We rely on the fact that the compiler will implicitly initialize the
6413 // static instances so that in_use == 0.
6416 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6417 if( ! static_class_info[ i ].in_use ) {
6418 static_class_info[ i ].in_use = 1;
6419 return static_class_info + i;
6423 // The static ones are all in use. Malloc one.
6425 return safe_malloc( sizeof( ClassInfo ) );
6429 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6430 @param info Pointer to the ClassInfo to be cleared.
6432 static void clear_class_info( ClassInfo* info ) {
6437 // Free any malloc'd strings
6439 if( info->alias != info->alias_store )
6440 free( info->alias );
6442 if( info->class_name != info->class_name_store )
6443 free( info->class_name );
6445 free( info->source_def );
6447 info->alias = info->class_name = info->source_def = NULL;
6452 @brief Free a ClassInfo and everything it owns.
6453 @param info Pointer to the ClassInfo to be freed.
6455 static void free_class_info( ClassInfo* info ) {
6460 clear_class_info( info );
6462 // If it's one of the static instances, just mark it as not in use
6465 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6466 if( info == static_class_info + i ) {
6467 static_class_info[ i ].in_use = 0;
6472 // Otherwise it must have been malloc'd, so free it
6478 @brief Populate an already-allocated ClassInfo.
6479 @param info Pointer to the ClassInfo to be populated.
6480 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6482 @param class Name of the class.
6483 @return Zero if successful, or 1 if not.
6485 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6486 the relevant portions of the IDL for the specified class.
6488 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6491 osrfLogError( OSRF_LOG_MARK,
6492 "%s ERROR: No ClassInfo available to populate", MODULENAME );
6493 info->alias = info->class_name = info->source_def = NULL;
6494 info->class_def = info->fields = info->links = NULL;
6499 osrfLogError( OSRF_LOG_MARK,
6500 "%s ERROR: No class name provided for lookup", MODULENAME );
6501 info->alias = info->class_name = info->source_def = NULL;
6502 info->class_def = info->fields = info->links = NULL;
6506 // Alias defaults to class name if not supplied
6507 if( ! alias || ! alias[ 0 ] )
6510 // Look up class info in the IDL
6511 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6513 osrfLogError( OSRF_LOG_MARK,
6514 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
6515 info->alias = info->class_name = info->source_def = NULL;
6516 info->class_def = info->fields = info->links = NULL;
6518 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6519 osrfLogError( OSRF_LOG_MARK,
6520 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
6521 info->alias = info->class_name = info->source_def = NULL;
6522 info->class_def = info->fields = info->links = NULL;
6526 osrfHash* links = osrfHashGet( class_def, "links" );
6528 osrfLogError( OSRF_LOG_MARK,
6529 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
6530 info->alias = info->class_name = info->source_def = NULL;
6531 info->class_def = info->fields = info->links = NULL;
6535 osrfHash* fields = osrfHashGet( class_def, "fields" );
6537 osrfLogError( OSRF_LOG_MARK,
6538 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
6539 info->alias = info->class_name = info->source_def = NULL;
6540 info->class_def = info->fields = info->links = NULL;
6544 char* source_def = getRelation( class_def );
6548 // We got everything we need, so populate the ClassInfo
6549 if( strlen( alias ) > ALIAS_STORE_SIZE )
6550 info->alias = strdup( alias );
6552 strcpy( info->alias_store, alias );
6553 info->alias = info->alias_store;
6556 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6557 info->class_name = strdup( class );
6559 strcpy( info->class_name_store, class );
6560 info->class_name = info->class_name_store;
6563 info->source_def = source_def;
6565 info->class_def = class_def;
6566 info->links = links;
6567 info->fields = fields;
6572 #define STATIC_FRAME_COUNT 3
6574 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6577 @brief Allocate a QueryFrame as raw memory.
6578 @return Pointer to the newly allocated QueryFrame.
6580 Except for the in_use flag, which is used only by the allocation and deallocation
6581 logic, we don't initialize the QueryFrame here.
6583 static QueryFrame* allocate_frame( void ) {
6584 // In order to reduce the number of mallocs and frees, we return a static
6585 // instance of QueryFrame, if we can find one that we're not already using.
6586 // We rely on the fact that the compiler will implicitly initialize the
6587 // static instances so that in_use == 0.
6590 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6591 if( ! static_frame[ i ].in_use ) {
6592 static_frame[ i ].in_use = 1;
6593 return static_frame + i;
6597 // The static ones are all in use. Malloc one.
6599 return safe_malloc( sizeof( QueryFrame ) );
6603 @brief Free a QueryFrame, and all the memory it owns.
6604 @param frame Pointer to the QueryFrame to be freed.
6606 static void free_query_frame( QueryFrame* frame ) {
6611 clear_class_info( &frame->core );
6613 // Free the join list
6615 ClassInfo* info = frame->join_list;
6618 free_class_info( info );
6622 frame->join_list = NULL;
6625 // If the frame is a static instance, just mark it as unused
6627 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6628 if( frame == static_frame + i ) {
6629 static_frame[ i ].in_use = 0;
6634 // Otherwise it must have been malloc'd, so free it
6640 @brief Search a given QueryFrame for a specified alias.
6641 @param frame Pointer to the QueryFrame to be searched.
6642 @param target The alias for which to search.
6643 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6645 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6646 if( ! frame || ! target ) {
6650 ClassInfo* found_class = NULL;
6652 if( !strcmp( target, frame->core.alias ) )
6653 return &(frame->core);
6655 ClassInfo* curr_class = frame->join_list;
6656 while( curr_class ) {
6657 if( strcmp( target, curr_class->alias ) )
6658 curr_class = curr_class->next;
6660 found_class = curr_class;
6670 @brief Push a new (blank) QueryFrame onto the stack.
6672 static void push_query_frame( void ) {
6673 QueryFrame* frame = allocate_frame();
6674 frame->join_list = NULL;
6675 frame->next = curr_query;
6677 // Initialize the ClassInfo for the core class
6678 ClassInfo* core = &frame->core;
6679 core->alias = core->class_name = core->source_def = NULL;
6680 core->class_def = core->fields = core->links = NULL;
6686 @brief Pop a QueryFrame off the stack and destroy it.
6688 static void pop_query_frame( void ) {
6693 QueryFrame* popped = curr_query;
6694 curr_query = popped->next;
6696 free_query_frame( popped );
6700 @brief Populate the ClassInfo for the core class.
6701 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6702 class name as an alias.
6703 @param class_name Name of the core class.
6704 @return Zero if successful, or 1 if not.
6706 Populate the ClassInfo of the core class with copies of the alias and class name, and
6707 with pointers to the relevant portions of the IDL for the core class.
6709 static int add_query_core( const char* alias, const char* class_name ) {
6712 if( ! curr_query ) {
6713 osrfLogError( OSRF_LOG_MARK,
6714 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
6716 } else if( curr_query->core.alias ) {
6717 osrfLogError( OSRF_LOG_MARK,
6718 "%s ERROR: Core class %s already populated as %s",
6719 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
6723 build_class_info( &curr_query->core, alias, class_name );
6724 if( curr_query->core.alias )
6727 osrfLogError( OSRF_LOG_MARK,
6728 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
6734 @brief Search the current QueryFrame for a specified alias.
6735 @param target The alias for which to search.
6736 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6738 static inline ClassInfo* search_alias( const char* target ) {
6739 return search_alias_in_frame( curr_query, target );
6743 @brief Search all levels of query for a specified alias, starting with the current query.
6744 @param target The alias for which to search.
6745 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6747 static ClassInfo* search_all_alias( const char* target ) {
6748 ClassInfo* found_class = NULL;
6749 QueryFrame* curr_frame = curr_query;
6751 while( curr_frame ) {
6752 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6755 curr_frame = curr_frame->next;
6762 @brief Add a class to the list of classes joined to the current query.
6763 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6764 the class name as an alias.
6765 @param classname The name of the class to be added.
6766 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6768 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6770 if( ! classname || ! *classname ) { // sanity check
6771 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6778 const ClassInfo* conflict = search_alias( alias );
6780 osrfLogError( OSRF_LOG_MARK,
6781 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6782 MODULENAME, alias, conflict->class_name );
6786 ClassInfo* info = allocate_class_info();
6788 if( build_class_info( info, alias, classname ) ) {
6789 free_class_info( info );
6793 // Add the new ClassInfo to the join list of the current QueryFrame
6794 info->next = curr_query->join_list;
6795 curr_query->join_list = info;
6801 @brief Destroy all nodes on the query stack.
6803 static void clear_query_stack( void ) {