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]",
2693 modulename, value );
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 -- "
4675 "see error log for more details"
4677 jsonIteratorFree( order_itr );
4678 jsonIteratorFree( class_itr );
4679 buffer_free( order_buf );
4681 buffer_free(group_buf);
4682 buffer_free(sql_buf);
4684 jsonObjectFree(defaultselhash);
4686 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4687 osrfLogError( OSRF_LOG_MARK,
4688 "%s: Virtual field \"%s\" in ORDER BY clause",
4689 modulename, order_itr->key );
4691 osrfAppSessionStatus(
4693 OSRF_STATUS_INTERNALSERVERERROR,
4694 "osrfMethodException",
4696 "Virtual field in ORDER BY clause -- "
4697 "see error log for more details"
4699 jsonIteratorFree( order_itr );
4700 jsonIteratorFree( class_itr );
4701 buffer_free( order_buf );
4703 buffer_free(group_buf);
4704 buffer_free(sql_buf);
4706 jsonObjectFree(defaultselhash);
4710 const char* direction = NULL;
4711 if ( onode->type == JSON_HASH ) {
4712 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4713 string = searchFieldTransform(
4715 osrfHashGet( field_list_def, order_itr->key ),
4719 if( ctx ) osrfAppSessionStatus(
4721 OSRF_STATUS_INTERNALSERVERERROR,
4722 "osrfMethodException",
4724 "Severe query error in ORDER BY clause -- "
4725 "see error log for more details"
4727 jsonIteratorFree( order_itr );
4728 jsonIteratorFree( class_itr );
4730 buffer_free(group_buf);
4731 buffer_free(order_buf);
4732 buffer_free(sql_buf);
4734 jsonObjectFree(defaultselhash);
4738 growing_buffer* field_buf = buffer_init(16);
4739 buffer_fadd( field_buf, "\"%s\".%s",
4740 class_itr->key, order_itr->key );
4741 string = buffer_release(field_buf);
4744 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4745 const char* dir = jsonObjectGetString(tmp_const);
4746 if (!strncasecmp(dir, "d", 1)) {
4747 direction = " DESC";
4753 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4754 osrfLogError( OSRF_LOG_MARK,
4755 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4756 modulename, json_type( onode->type ) );
4758 osrfAppSessionStatus(
4760 OSRF_STATUS_INTERNALSERVERERROR,
4761 "osrfMethodException",
4763 "Malformed ORDER BY clause -- see error log for more details"
4765 jsonIteratorFree( order_itr );
4766 jsonIteratorFree( class_itr );
4768 buffer_free(group_buf);
4769 buffer_free(order_buf);
4770 buffer_free(sql_buf);
4772 jsonObjectFree(defaultselhash);
4776 string = strdup(order_itr->key);
4777 const char* dir = jsonObjectGetString(onode);
4778 if (!strncasecmp(dir, "d", 1)) {
4779 direction = " DESC";
4786 OSRF_BUFFER_ADD(order_buf, ", ");
4788 order_buf = buffer_init(128);
4790 OSRF_BUFFER_ADD(order_buf, string);
4794 OSRF_BUFFER_ADD(order_buf, direction);
4798 jsonIteratorFree(order_itr);
4800 } else if ( snode->type == JSON_ARRAY ) {
4802 // Array is a list of fields from the current class
4803 unsigned long order_idx = 0;
4804 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4806 const char* _f = jsonObjectGetString( onode );
4808 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4810 osrfLogError( OSRF_LOG_MARK,
4811 "%s: Invalid field \"%s\" in ORDER BY clause",
4814 osrfAppSessionStatus(
4816 OSRF_STATUS_INTERNALSERVERERROR,
4817 "osrfMethodException",
4819 "Invalid field in ORDER BY clause -- "
4820 "see error log for more details"
4822 jsonIteratorFree( class_itr );
4823 buffer_free( order_buf );
4825 buffer_free(group_buf);
4826 buffer_free(sql_buf);
4828 jsonObjectFree(defaultselhash);
4830 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4831 osrfLogError( OSRF_LOG_MARK,
4832 "%s: Virtual field \"%s\" in ORDER BY clause",
4835 osrfAppSessionStatus(
4837 OSRF_STATUS_INTERNALSERVERERROR,
4838 "osrfMethodException",
4840 "Virtual field in ORDER BY clause -- "
4841 "see error log for more details"
4843 jsonIteratorFree( class_itr );
4844 buffer_free( order_buf );
4846 buffer_free(group_buf);
4847 buffer_free(sql_buf);
4849 jsonObjectFree(defaultselhash);
4854 OSRF_BUFFER_ADD(order_buf, ", ");
4856 order_buf = buffer_init(128);
4858 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
4862 // IT'S THE OOOOOOOOOOOLD STYLE!
4864 osrfLogError( OSRF_LOG_MARK,
4865 "%s: Possible SQL injection attempt; direct order by is not allowed",
4868 osrfAppSessionStatus(
4870 OSRF_STATUS_INTERNALSERVERERROR,
4871 "osrfMethodException",
4873 "Severe query error -- see error log for more details"
4878 buffer_free(group_buf);
4879 buffer_free(order_buf);
4880 buffer_free(sql_buf);
4882 jsonObjectFree(defaultselhash);
4883 jsonIteratorFree(class_itr);
4887 jsonIteratorFree( class_itr );
4889 osrfLogError(OSRF_LOG_MARK,
4890 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4891 modulename, json_type( order_hash->type ) );
4893 osrfAppSessionStatus(
4895 OSRF_STATUS_INTERNALSERVERERROR,
4896 "osrfMethodException",
4898 "Malformed ORDER BY clause -- see error log for more details"
4900 buffer_free( order_buf );
4902 buffer_free(group_buf);
4903 buffer_free(sql_buf);
4905 jsonObjectFree(defaultselhash);
4910 order_by_list = buffer_release( order_buf );
4914 string = buffer_release(group_buf);
4916 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4917 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4918 OSRF_BUFFER_ADD( sql_buf, string );
4923 if( having_buf && *having_buf ) {
4924 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4925 OSRF_BUFFER_ADD( sql_buf, having_buf );
4929 if( order_by_list ) {
4931 if ( *order_by_list ) {
4932 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4933 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4936 free( order_by_list );
4940 const char* str = jsonObjectGetString(limit);
4941 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
4945 const char* str = jsonObjectGetString(offset);
4946 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
4949 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4952 jsonObjectFree(defaultselhash);
4954 return buffer_release(sql_buf);
4956 } // end of SELECT()
4958 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4960 const char* locale = osrf_message_get_last_locale();
4962 osrfHash* fields = osrfHashGet(meta, "fields");
4963 char* core_class = osrfHashGet(meta, "classname");
4965 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4967 jsonObject* node = NULL;
4968 jsonObject* snode = NULL;
4969 jsonObject* onode = NULL;
4970 const jsonObject* _tmp = NULL;
4971 jsonObject* selhash = NULL;
4972 jsonObject* defaultselhash = NULL;
4974 growing_buffer* sql_buf = buffer_init(128);
4975 growing_buffer* select_buf = buffer_init(128);
4977 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4978 defaultselhash = jsonNewObjectType(JSON_HASH);
4979 selhash = defaultselhash;
4982 // If there's no SELECT list for the core class, build one
4983 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4984 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4986 // Add every non-virtual field to the field list
4987 osrfHash* field_def = NULL;
4988 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4989 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4990 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4991 const char* field = osrfHashIteratorKey( field_itr );
4992 jsonObjectPush( field_list, jsonNewObject( field ) );
4995 osrfHashIteratorFree( field_itr );
4996 jsonObjectSetKey( selhash, core_class, field_list );
5000 jsonIterator* class_itr = jsonNewIterator( selhash );
5001 while ( (snode = jsonIteratorNext( class_itr )) ) {
5003 const char* cname = class_itr->key;
5004 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5008 if (strcmp(core_class,class_itr->key)) {
5012 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
5014 jsonObjectFree(found);
5018 jsonObjectFree(found);
5021 jsonIterator* select_itr = jsonNewIterator( snode );
5022 while ( (node = jsonIteratorNext( select_itr )) ) {
5023 const char* item_str = jsonObjectGetString( node );
5024 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5025 char* fname = osrfHashGet(field, "name");
5033 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
5038 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
5039 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5042 i18n = osrfHashGet(field, "i18n");
5044 if( str_is_true( i18n ) ) {
5045 char* pkey = osrfHashGet(idlClass, "primarykey");
5046 char* tname = osrfHashGet(idlClass, "tablename");
5048 buffer_fadd(select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5049 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5050 tname, cname, fname, pkey, cname, pkey, locale, fname);
5052 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
5055 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
5059 jsonIteratorFree(select_itr);
5062 jsonIteratorFree(class_itr);
5064 char* col_list = buffer_release(select_buf);
5065 char* table = getRelation(meta);
5067 table = strdup( "(null)" );
5069 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5073 // Clear the query stack (as a fail-safe precaution against possible
5074 // leftover garbage); then push the first query frame onto the stack.
5075 clear_query_stack();
5077 if( add_query_core( NULL, core_class ) ) {
5079 osrfAppSessionStatus(
5081 OSRF_STATUS_INTERNALSERVERERROR,
5082 "osrfMethodException",
5084 "Unable to build query frame for core class"
5090 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5091 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
5092 OSRF_BUFFER_ADD(sql_buf, join_clause);
5096 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5097 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5099 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
5101 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5103 osrfAppSessionStatus(
5105 OSRF_STATUS_INTERNALSERVERERROR,
5106 "osrfMethodException",
5108 "Severe query error -- see error log for more details"
5110 buffer_free(sql_buf);
5112 jsonObjectFree(defaultselhash);
5113 clear_query_stack();
5116 buffer_add(sql_buf, pred);
5121 char* string = NULL;
5122 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
5124 growing_buffer* order_buf = buffer_init(128);
5127 jsonIterator* class_itr = jsonNewIterator( _tmp );
5128 while ( (snode = jsonIteratorNext( class_itr )) ) {
5130 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
5133 if ( snode->type == JSON_HASH ) {
5135 jsonIterator* order_itr = jsonNewIterator( snode );
5136 while ( (onode = jsonIteratorNext( order_itr )) ) {
5138 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
5139 class_itr->key, order_itr->key );
5143 char* direction = NULL;
5144 if ( onode->type == JSON_HASH ) {
5145 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
5146 string = searchFieldTransform( class_itr->key, field_def, onode );
5148 osrfAppSessionStatus(
5150 OSRF_STATUS_INTERNALSERVERERROR,
5151 "osrfMethodException",
5153 "Severe query error in ORDER BY clause -- "
5154 "see error log for more details"
5156 jsonIteratorFree( order_itr );
5157 jsonIteratorFree( class_itr );
5158 buffer_free( order_buf );
5159 buffer_free( sql_buf );
5160 if( defaultselhash )
5161 jsonObjectFree( defaultselhash );
5162 clear_query_stack();
5166 growing_buffer* field_buf = buffer_init(16);
5167 buffer_fadd( field_buf, "\"%s\".%s",
5168 class_itr->key, order_itr->key );
5169 string = buffer_release(field_buf);
5172 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
5173 const char* dir = jsonObjectGetString(_tmp);
5174 if (!strncasecmp(dir, "d", 1)) {
5175 direction = " DESC";
5181 string = strdup(order_itr->key);
5182 const char* dir = jsonObjectGetString(onode);
5183 if (!strncasecmp(dir, "d", 1)) {
5184 direction = " DESC";
5193 buffer_add(order_buf, ", ");
5196 buffer_add(order_buf, string);
5200 buffer_add(order_buf, direction);
5204 jsonIteratorFree(order_itr);
5207 const char* str = jsonObjectGetString(snode);
5208 buffer_add(order_buf, str);
5214 jsonIteratorFree(class_itr);
5216 string = buffer_release(order_buf);
5219 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5220 OSRF_BUFFER_ADD( sql_buf, string );
5226 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
5227 const char* str = jsonObjectGetString(_tmp);
5235 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
5237 const char* str = jsonObjectGetString(_tmp);
5247 jsonObjectFree(defaultselhash);
5248 clear_query_stack();
5250 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
5251 return buffer_release(sql_buf);
5254 int doJSONSearch ( osrfMethodContext* ctx ) {
5255 if(osrfMethodVerifyContext( ctx )) {
5256 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5260 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
5265 dbhandle = writehandle;
5267 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
5271 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
5272 flags |= SELECT_DISTINCT;
5274 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
5275 flags |= DISABLE_I18N;
5277 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
5278 clear_query_stack(); // a possibly needless precaution
5279 char* sql = buildQuery( ctx, hash, flags );
5280 clear_query_stack();
5287 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", modulename, sql);
5288 dbi_result result = dbi_conn_query(dbhandle, sql);
5291 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
5293 if (dbi_result_first_row(result)) {
5294 /* JSONify the result */
5295 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
5298 jsonObject* return_val = oilsMakeJSONFromResult( result );
5299 osrfAppRespond( ctx, return_val );
5300 jsonObjectFree( return_val );
5301 } while (dbi_result_next_row(result));
5304 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql);
5307 osrfAppRespondComplete( ctx, NULL );
5309 /* clean up the query */
5310 dbi_result_free(result);
5314 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", modulename, sql);
5315 osrfAppSessionStatus(
5317 OSRF_STATUS_INTERNALSERVERERROR,
5318 "osrfMethodException",
5320 "Severe query error -- see error log for more details"
5328 // The last parameter, err, is used to report an error condition by updating an int owned by
5329 // the calling code.
5331 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5332 // It is the responsibility of the calling code to initialize *err before the
5333 // call, so that it will be able to make sense of the result.
5335 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5336 // redundant anyway.
5337 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
5338 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5341 dbhandle = writehandle;
5343 char* core_class = osrfHashGet( class_meta, "classname" );
5344 char* pkey = osrfHashGet( class_meta, "primarykey" );
5346 const jsonObject* _tmp;
5348 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5350 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
5355 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", modulename, sql);
5357 dbi_result result = dbi_conn_query(dbhandle, sql);
5358 if( NULL == result ) {
5359 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
5360 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql);
5361 osrfAppSessionStatus(
5363 OSRF_STATUS_INTERNALSERVERERROR,
5364 "osrfMethodException",
5366 "Severe query error -- see error log for more details"
5373 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
5376 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
5377 jsonObject* row_obj = NULL;
5379 if (dbi_result_first_row(result)) {
5381 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5382 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5383 // eliminate the duplicates.
5384 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
5385 osrfHash* dedup = osrfNewHash();
5387 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5388 char* pkey_val = oilsFMGetString( row_obj, pkey );
5389 if ( osrfHashGet( dedup, pkey_val ) ) {
5390 jsonObjectFree( row_obj );
5393 osrfHashSet( dedup, pkey_val, pkey_val );
5394 jsonObjectPush( res_list, row_obj );
5396 } while (dbi_result_next_row(result));
5397 osrfHashFree(dedup);
5400 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
5404 /* clean up the query */
5405 dbi_result_free(result);
5408 // If we're asked to flesh, and there's anything to flesh, then flesh.
5409 if (res_list->size && query_hash) {
5410 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5412 // Get the flesh depth
5413 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5414 if ( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5415 flesh_depth = max_flesh_depth;
5417 // We need a non-zero flesh depth, and a list of fields to flesh
5418 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5419 if ( temp_blob && flesh_depth > 0 ) {
5421 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5422 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5424 osrfStringArray* link_fields = NULL;
5425 osrfHash* links = osrfHashGet( class_meta, "links" );
5427 // Make an osrfStringArray of the names of fields to be fleshed
5429 if (flesh_fields->size == 1) {
5430 const char* _t = jsonObjectGetString(
5431 jsonObjectGetIndex( flesh_fields, 0 ) );
5432 if (!strcmp(_t,"*"))
5433 link_fields = osrfHashKeys( links );
5438 link_fields = osrfNewStringArray(1);
5439 jsonIterator* _i = jsonNewIterator( flesh_fields );
5440 while ((_f = jsonIteratorNext( _i ))) {
5441 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5443 jsonIteratorFree(_i);
5447 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5449 // Iterate over the JSON_ARRAY of rows
5451 unsigned long res_idx = 0;
5452 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5455 const char* link_field;
5457 // Iterate over the list of fleshable fields
5458 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5460 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
5462 osrfHash* kid_link = osrfHashGet(links, link_field);
5464 continue; // Not a link field; skip it
5466 osrfHash* field = osrfHashGet(fields, link_field);
5468 continue; // Not a field at all; skip it (IDL is ill-formed)
5470 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
5472 continue; // The class it links to doesn't exist; skip it
5474 const char* reltype = osrfHashGet( kid_link, "reltype" );
5476 continue; // No reltype; skip it (IDL is ill-formed)
5478 osrfHash* value_field = field;
5480 if ( !strcmp( reltype, "has_many" )
5481 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5482 value_field = osrfHashGet(
5483 fields, osrfHashGet( class_meta, "primarykey" ) );
5486 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5488 if (link_map->size > 0) {
5489 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
5492 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5497 osrfHashGet(kid_link, "class"),
5504 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5505 osrfHashGet(kid_link, "field"),
5506 osrfHashGet(kid_link, "class"),
5507 osrfHashGet(kid_link, "key"),
5508 osrfHashGet(kid_link, "reltype")
5511 const char* search_key = jsonObjectGetString(
5512 jsonObjectGetIndex( cur,
5513 atoi( osrfHashGet(value_field, "array_position") )
5518 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
5522 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
5524 // construct WHERE clause
5525 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
5528 osrfHashGet(kid_link, "key"),
5529 jsonNewObject( search_key )
5532 // construct the rest of the query, mostly
5533 // by copying pieces of the previous level of query
5534 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
5535 jsonObjectSetKey( rest_of_query, "flesh",
5536 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5540 jsonObjectSetKey( rest_of_query, "flesh_fields",
5541 jsonObjectClone(flesh_blob) );
5543 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
5544 jsonObjectSetKey( rest_of_query, "order_by",
5545 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
5549 if (jsonObjectGetKeyConst(query_hash, "select")) {
5550 jsonObjectSetKey( rest_of_query, "select",
5551 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
5555 // do the query, recursively, to expand the fleshable field
5556 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5557 where_clause, rest_of_query, err);
5559 jsonObjectFree( where_clause );
5560 jsonObjectFree( rest_of_query );
5563 osrfStringArrayFree(link_fields);
5564 jsonObjectFree(res_list);
5565 jsonObjectFree(flesh_blob);
5569 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects",
5570 osrfHashGet(kid_link, "class"), kids->size);
5572 // Traverse the result set
5573 jsonObject* X = NULL;
5574 if ( link_map->size > 0 && kids->size > 0 ) {
5576 kids = jsonNewObjectType(JSON_ARRAY);
5578 jsonObject* _k_node;
5579 unsigned long res_idx = 0;
5580 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5586 (unsigned long)atoi(
5592 osrfHashGet(kid_link, "class")
5596 osrfStringArrayGetString( link_map, 0 )
5604 } // end while loop traversing X
5607 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" ))
5608 || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
5609 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5610 osrfHashGet(kid_link, "field"));
5613 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5614 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
5618 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) {
5620 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5621 osrfHashGet( kid_link, "field" ) );
5624 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5625 jsonObjectClone( kids )
5630 jsonObjectFree(kids);
5634 jsonObjectFree( kids );
5636 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5637 osrfHashGet( kid_link, "field" ) );
5638 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
5640 } // end while loop traversing list of fleshable fields
5641 } // end while loop traversing res_list
5642 jsonObjectFree( flesh_blob );
5643 osrfStringArrayFree(link_fields);
5652 static int doUpdate(osrfMethodContext* ctx ) {
5653 if(osrfMethodVerifyContext( ctx )) {
5654 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5659 timeout_needs_resetting = 1;
5661 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5663 jsonObject* target = NULL;
5665 target = jsonObjectGetIndex( ctx->params, 1 );
5667 target = jsonObjectGetIndex( ctx->params, 0 );
5669 if (!verifyObjectClass(ctx, target)) {
5670 osrfAppRespondComplete( ctx, NULL );
5674 if( getXactId( ctx ) == NULL ) {
5675 osrfAppSessionStatus(
5677 OSRF_STATUS_BADREQUEST,
5678 "osrfMethodException",
5680 "No active transaction -- required for UPDATE"
5682 osrfAppRespondComplete( ctx, NULL );
5686 // The following test is harmless but redundant. If a class is
5687 // readonly, we don't register an update method for it.
5688 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5689 osrfAppSessionStatus(
5691 OSRF_STATUS_BADREQUEST,
5692 "osrfMethodException",
5694 "Cannot UPDATE readonly class"
5696 osrfAppRespondComplete( ctx, NULL );
5700 dbhandle = writehandle;
5701 const char* trans_id = getXactId( ctx );
5703 // Set the last_xact_id
5704 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5706 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5707 trans_id, target->classname, index);
5708 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
5711 char* pkey = osrfHashGet(meta, "primarykey");
5712 osrfHash* fields = osrfHashGet(meta, "fields");
5714 char* id = oilsFMGetString( target, pkey );
5718 "%s updating %s object with %s = %s",
5720 osrfHashGet(meta, "fieldmapper"),
5725 growing_buffer* sql = buffer_init(128);
5726 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
5729 osrfHash* field_def = NULL;
5730 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5731 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
5733 // Skip virtual fields, and the primary key
5734 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5737 const char* field_name = osrfHashIteratorKey( field_itr );
5738 if( ! strcmp( field_name, pkey ) )
5741 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5743 int value_is_numeric = 0; // boolean
5745 if (field_object && field_object->classname) {
5746 value = oilsFMGetString(
5748 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
5750 } else if( field_object && JSON_BOOL == field_object->type ) {
5751 if( jsonBoolIsTrue( field_object ) )
5752 value = strdup( "t" );
5754 value = strdup( "f" );
5756 value = jsonObjectToSimpleString( field_object );
5757 if( field_object && JSON_NUMBER == field_object->type )
5758 value_is_numeric = 1;
5761 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5762 osrfHashGet(meta, "fieldmapper"), field_name, value);
5764 if (!field_object || field_object->type == JSON_NULL) {
5765 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
5766 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5770 OSRF_BUFFER_ADD_CHAR(sql, ',');
5771 buffer_fadd( sql, " %s = NULL", field_name );
5774 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5778 OSRF_BUFFER_ADD_CHAR(sql, ',');
5780 const char* numtype = get_datatype( field_def );
5781 if ( !strncmp( numtype, "INT", 3 ) ) {
5782 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
5783 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
5784 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
5786 // Must really be intended as a string, so quote it
5787 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5788 buffer_fadd( sql, " %s = %s", field_name, value );
5790 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5792 osrfAppSessionStatus(
5794 OSRF_STATUS_INTERNALSERVERERROR,
5795 "osrfMethodException",
5797 "Error quoting string -- please see the error log for more details"
5801 osrfHashIteratorFree( field_itr );
5803 osrfAppRespondComplete( ctx, NULL );
5808 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5811 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5815 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5816 buffer_fadd( sql, " %s = %s", field_name, value );
5818 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value);
5819 osrfAppSessionStatus(
5821 OSRF_STATUS_INTERNALSERVERERROR,
5822 "osrfMethodException",
5824 "Error quoting string -- please see the error log for more details"
5828 osrfHashIteratorFree( field_itr );
5830 osrfAppRespondComplete( ctx, NULL );
5839 osrfHashIteratorFree( field_itr );
5841 jsonObject* obj = jsonNewObject(id);
5843 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5844 dbi_conn_quote_string(dbhandle, &id);
5846 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5848 char* query = buffer_release(sql);
5849 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query);
5851 dbi_result result = dbi_conn_query(dbhandle, query);
5855 jsonObjectFree(obj);
5856 obj = jsonNewObject(NULL);
5859 "%s ERROR updating %s object with %s = %s",
5861 osrfHashGet(meta, "fieldmapper"),
5868 osrfAppRespondComplete( ctx, obj );
5869 jsonObjectFree( obj );
5873 static int doDelete( osrfMethodContext* ctx ) {
5874 if(osrfMethodVerifyContext( ctx )) {
5875 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5880 timeout_needs_resetting = 1;
5882 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5884 if( getXactId( ctx ) == NULL ) {
5885 osrfAppSessionStatus(
5887 OSRF_STATUS_BADREQUEST,
5888 "osrfMethodException",
5890 "No active transaction -- required for DELETE"
5892 osrfAppRespondComplete( ctx, NULL );
5896 // The following test is harmless but redundant. If a class is
5897 // readonly, we don't register a delete method for it.
5898 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5899 osrfAppSessionStatus(
5901 OSRF_STATUS_BADREQUEST,
5902 "osrfMethodException",
5904 "Cannot DELETE readonly class"
5906 osrfAppRespondComplete( ctx, NULL );
5910 dbhandle = writehandle;
5912 char* pkey = osrfHashGet(meta, "primarykey");
5919 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
5920 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5921 osrfAppRespondComplete( ctx, NULL );
5925 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5927 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5928 osrfAppRespondComplete( ctx, NULL );
5931 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
5936 "%s deleting %s object with %s = %s",
5938 osrfHashGet(meta, "fieldmapper"),
5943 jsonObject* obj = jsonNewObject(id);
5945 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5946 dbi_conn_quote_string(writehandle, &id);
5948 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;",
5949 osrfHashGet(meta, "tablename"), pkey, id);
5952 jsonObjectFree(obj);
5953 obj = jsonNewObject(NULL);
5956 "%s ERROR deleting %s object with %s = %s",
5958 osrfHashGet(meta, "fieldmapper"),
5966 osrfAppRespondComplete( ctx, obj );
5967 jsonObjectFree( obj );
5972 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
5973 @param result An iterator for a result set; we only look at the current row.
5974 @param @meta Pointer to the class metadata for the core class.
5975 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
5977 If a column is not defined in the IDL, or if it has no array_position defined for it in
5978 the IDL, or if it is defined as virtual, ignore it.
5980 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
5981 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
5982 array_position in the IDL.
5984 A field defined in the IDL but not represented in the returned row will leave a hole
5985 in the JSON_ARRAY. In effect it will be treated as a null value.
5987 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
5988 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
5989 classname corresponding to the @a meta argument.
5991 The calling code is responsible for freeing the the resulting jsonObject by calling
5994 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5995 if(!(result && meta)) return jsonNULL;
5997 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
5998 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
5999 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
6001 osrfHash* fields = osrfHashGet(meta, "fields");
6003 int columnIndex = 1;
6004 const char* columnName;
6006 /* cycle through the columns in the row returned from the database */
6007 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
6009 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
6011 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6013 /* determine the field type and storage attributes */
6014 unsigned short type = dbi_result_get_field_type_idx(result, columnIndex);
6015 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
6017 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6018 // or if it has no sequence number there, or if it's virtual, skip it.
6019 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6022 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
6023 continue; // skip this column: IDL says it's virtual
6025 const char* pos = (char*)osrfHashGet(_f, "array_position");
6026 if ( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6027 continue; // since we assign sequence numbers dynamically as we load the IDL.
6029 fmIndex = atoi( pos );
6030 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
6032 continue; // This field is not defined in the IDL
6035 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6036 // sequence number from the IDL (which is likely to be different from the sequence
6037 // of columns in the SELECT clause).
6038 if (dbi_result_field_is_null_idx(result, columnIndex)) {
6039 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
6044 case DBI_TYPE_INTEGER :
6046 if( attr & DBI_INTEGER_SIZE8 )
6047 jsonObjectSetIndex( object, fmIndex,
6048 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
6050 jsonObjectSetIndex( object, fmIndex,
6051 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
6055 case DBI_TYPE_DECIMAL :
6056 jsonObjectSetIndex( object, fmIndex,
6057 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
6060 case DBI_TYPE_STRING :
6065 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
6070 case DBI_TYPE_DATETIME : {
6072 char dt_string[256] = "";
6075 // Fetch the date column as a time_t
6076 time_t _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
6078 // Translate the time_t to a human-readable string
6079 if (!(attr & DBI_DATETIME_DATE)) {
6080 gmtime_r( &_tmp_dt, &gmdt );
6081 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
6082 } else if (!(attr & DBI_DATETIME_TIME)) {
6083 localtime_r( &_tmp_dt, &gmdt );
6084 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
6086 localtime_r( &_tmp_dt, &gmdt );
6087 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
6090 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
6094 case DBI_TYPE_BINARY :
6095 osrfLogError( OSRF_LOG_MARK,
6096 "Can't do binary at column %s : index %d", columnName, columnIndex);
6105 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6106 if(!result) return jsonNULL;
6108 jsonObject* object = jsonNewObject(NULL);
6111 char dt_string[256];
6115 int columnIndex = 1;
6117 unsigned short type;
6118 const char* columnName;
6120 /* cycle through the column list */
6121 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
6123 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
6125 fmIndex = -1; // reset the position
6127 /* determine the field type and storage attributes */
6128 type = dbi_result_get_field_type_idx(result, columnIndex);
6129 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
6131 if (dbi_result_field_is_null_idx(result, columnIndex)) {
6132 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
6137 case DBI_TYPE_INTEGER :
6139 if( attr & DBI_INTEGER_SIZE8 )
6140 jsonObjectSetKey( object, columnName,
6141 jsonNewNumberObject(dbi_result_get_longlong_idx(
6142 result, columnIndex)) );
6144 jsonObjectSetKey( object, columnName,
6145 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
6148 case DBI_TYPE_DECIMAL :
6149 jsonObjectSetKey( object, columnName,
6150 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
6153 case DBI_TYPE_STRING :
6154 jsonObjectSetKey( object, columnName,
6155 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
6158 case DBI_TYPE_DATETIME :
6160 memset(dt_string, '\0', sizeof(dt_string));
6161 memset(&gmdt, '\0', sizeof(gmdt));
6163 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
6166 if (!(attr & DBI_DATETIME_DATE)) {
6167 gmtime_r( &_tmp_dt, &gmdt );
6168 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
6169 } else if (!(attr & DBI_DATETIME_TIME)) {
6170 localtime_r( &_tmp_dt, &gmdt );
6171 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
6173 localtime_r( &_tmp_dt, &gmdt );
6174 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
6177 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
6180 case DBI_TYPE_BINARY :
6181 osrfLogError( OSRF_LOG_MARK,
6182 "Can't do binary at column %s : index %d", columnName, columnIndex );
6186 } // end while loop traversing result
6191 // Interpret a string as true or false
6192 static int str_is_true( const char* str ) {
6193 if( NULL == str || strcasecmp( str, "true" ) )
6199 // Interpret a jsonObject as true or false
6200 static int obj_is_true( const jsonObject* obj ) {
6203 else switch( obj->type )
6211 if( strcasecmp( obj->value.s, "true" ) )
6215 case JSON_NUMBER : // Support 1/0 for perl's sake
6216 if( jsonObjectGetNumber( obj ) == 1.0 )
6225 // Translate a numeric code into a text string identifying a type of
6226 // jsonObject. To be used for building error messages.
6227 static const char* json_type( int code ) {
6233 return "JSON_ARRAY";
6235 return "JSON_STRING";
6237 return "JSON_NUMBER";
6243 return "(unrecognized)";
6247 // Extract the "primitive" attribute from an IDL field definition.
6248 // If we haven't initialized the app, then we must be running in
6249 // some kind of testbed. In that case, default to "string".
6250 static const char* get_primitive( osrfHash* field ) {
6251 const char* s = osrfHashGet( field, "primitive" );
6253 if( child_initialized )
6256 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6258 osrfHashGet( field, "name" )
6266 // Extract the "datatype" attribute from an IDL field definition.
6267 // If we haven't initialized the app, then we must be running in
6268 // some kind of testbed. In that case, default to to NUMERIC,
6269 // since we look at the datatype only for numbers.
6270 static const char* get_datatype( osrfHash* field ) {
6271 const char* s = osrfHashGet( field, "datatype" );
6273 if( child_initialized )
6276 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6278 osrfHashGet( field, "name" )
6287 @brief Determine whether a string is potentially a valid SQL identifier.
6288 @param s The identifier to be tested.
6289 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6291 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6292 need to follow all the rules exactly, such as requiring that the first character not
6295 We allow leading and trailing white space. In between, we do not allow punctuation
6296 (except for underscores and dollar signs), control characters, or embedded white space.
6298 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6299 for the foreseeable future such quoted identifiers are not likely to be an issue.
6301 static int is_identifier( const char* s) {
6305 // Skip leading white space
6306 while( isspace( (unsigned char) *s ) )
6310 return 0; // Nothing but white space? Not okay.
6312 // Check each character until we reach white space or
6313 // end-of-string. Letters, digits, underscores, and
6314 // dollar signs are okay. With the exception of periods
6315 // (as in schema.identifier), control characters and other
6316 // punctuation characters are not okay. Anything else
6317 // is okay -- it could for example be part of a multibyte
6318 // UTF8 character such as a letter with diacritical marks,
6319 // and those are allowed.
6321 if( isalnum( (unsigned char) *s )
6325 ; // Fine; keep going
6326 else if( ispunct( (unsigned char) *s )
6327 || iscntrl( (unsigned char) *s ) )
6330 } while( *s && ! isspace( (unsigned char) *s ) );
6332 // If we found any white space in the above loop,
6333 // the rest had better be all white space.
6335 while( isspace( (unsigned char) *s ) )
6339 return 0; // White space was embedded within non-white space
6345 @brief Determine whether to accept a character string as a comparison operator.
6346 @param op The candidate comparison operator.
6347 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6349 We don't validate the operator for real. We just make sure that it doesn't contain
6350 any semicolons or white space (with special exceptions for a few specific operators).
6351 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6352 space but it's still not a valid operator, then the database will complain.
6354 Another approach would be to compare the string against a short list of approved operators.
6355 We don't do that because we want to allow custom operators like ">100*", which at this
6356 writing would be difficult or impossible to express otherwise in a JSON query.
6358 static int is_good_operator( const char* op ) {
6359 if( !op ) return 0; // Sanity check
6363 if( isspace( (unsigned char) *s ) ) {
6364 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6365 // and IS NOT DISTINCT FROM.
6366 if( !strcasecmp( op, "similar to" ) )
6368 else if( !strcasecmp( op, "is distinct from" ) )
6370 else if( !strcasecmp( op, "is not distinct from" ) )
6375 else if( ';' == *s )
6383 @name Query Frame Management
6385 The following machinery supports a stack of query frames for use by SELECT().
6387 A query frame caches information about one level of a SELECT query. When we enter
6388 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6390 The query frame stores information about the core class, and about any joined classes
6393 The main purpose is to map table aliases to classes and tables, so that a query can
6394 join to the same table more than once. A secondary goal is to reduce the number of
6395 lookups in the IDL by caching the results.
6399 #define STATIC_CLASS_INFO_COUNT 3
6401 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6404 @brief Allocate a ClassInfo as raw memory.
6405 @return Pointer to the newly allocated ClassInfo.
6407 Except for the in_use flag, which is used only by the allocation and deallocation
6408 logic, we don't initialize the ClassInfo here.
6410 static ClassInfo* allocate_class_info( void ) {
6411 // In order to reduce the number of mallocs and frees, we return a static
6412 // instance of ClassInfo, if we can find one that we're not already using.
6413 // We rely on the fact that the compiler will implicitly initialize the
6414 // static instances so that in_use == 0.
6417 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6418 if( ! static_class_info[ i ].in_use ) {
6419 static_class_info[ i ].in_use = 1;
6420 return static_class_info + i;
6424 // The static ones are all in use. Malloc one.
6426 return safe_malloc( sizeof( ClassInfo ) );
6430 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6431 @param info Pointer to the ClassInfo to be cleared.
6433 static void clear_class_info( ClassInfo* info ) {
6438 // Free any malloc'd strings
6440 if( info->alias != info->alias_store )
6441 free( info->alias );
6443 if( info->class_name != info->class_name_store )
6444 free( info->class_name );
6446 free( info->source_def );
6448 info->alias = info->class_name = info->source_def = NULL;
6453 @brief Free a ClassInfo and everything it owns.
6454 @param info Pointer to the ClassInfo to be freed.
6456 static void free_class_info( ClassInfo* info ) {
6461 clear_class_info( info );
6463 // If it's one of the static instances, just mark it as not in use
6466 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6467 if( info == static_class_info + i ) {
6468 static_class_info[ i ].in_use = 0;
6473 // Otherwise it must have been malloc'd, so free it
6479 @brief Populate an already-allocated ClassInfo.
6480 @param info Pointer to the ClassInfo to be populated.
6481 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6483 @param class Name of the class.
6484 @return Zero if successful, or 1 if not.
6486 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6487 the relevant portions of the IDL for the specified class.
6489 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6492 osrfLogError( OSRF_LOG_MARK,
6493 "%s ERROR: No ClassInfo available to populate", modulename );
6494 info->alias = info->class_name = info->source_def = NULL;
6495 info->class_def = info->fields = info->links = NULL;
6500 osrfLogError( OSRF_LOG_MARK,
6501 "%s ERROR: No class name provided for lookup", modulename );
6502 info->alias = info->class_name = info->source_def = NULL;
6503 info->class_def = info->fields = info->links = NULL;
6507 // Alias defaults to class name if not supplied
6508 if( ! alias || ! alias[ 0 ] )
6511 // Look up class info in the IDL
6512 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6514 osrfLogError( OSRF_LOG_MARK,
6515 "%s ERROR: Class %s not defined in IDL", modulename, class );
6516 info->alias = info->class_name = info->source_def = NULL;
6517 info->class_def = info->fields = info->links = NULL;
6519 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6520 osrfLogError( OSRF_LOG_MARK,
6521 "%s ERROR: Class %s is defined as virtual", modulename, class );
6522 info->alias = info->class_name = info->source_def = NULL;
6523 info->class_def = info->fields = info->links = NULL;
6527 osrfHash* links = osrfHashGet( class_def, "links" );
6529 osrfLogError( OSRF_LOG_MARK,
6530 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6531 info->alias = info->class_name = info->source_def = NULL;
6532 info->class_def = info->fields = info->links = NULL;
6536 osrfHash* fields = osrfHashGet( class_def, "fields" );
6538 osrfLogError( OSRF_LOG_MARK,
6539 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6540 info->alias = info->class_name = info->source_def = NULL;
6541 info->class_def = info->fields = info->links = NULL;
6545 char* source_def = getRelation( class_def );
6549 // We got everything we need, so populate the ClassInfo
6550 if( strlen( alias ) > ALIAS_STORE_SIZE )
6551 info->alias = strdup( alias );
6553 strcpy( info->alias_store, alias );
6554 info->alias = info->alias_store;
6557 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6558 info->class_name = strdup( class );
6560 strcpy( info->class_name_store, class );
6561 info->class_name = info->class_name_store;
6564 info->source_def = source_def;
6566 info->class_def = class_def;
6567 info->links = links;
6568 info->fields = fields;
6573 #define STATIC_FRAME_COUNT 3
6575 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6578 @brief Allocate a QueryFrame as raw memory.
6579 @return Pointer to the newly allocated QueryFrame.
6581 Except for the in_use flag, which is used only by the allocation and deallocation
6582 logic, we don't initialize the QueryFrame here.
6584 static QueryFrame* allocate_frame( void ) {
6585 // In order to reduce the number of mallocs and frees, we return a static
6586 // instance of QueryFrame, if we can find one that we're not already using.
6587 // We rely on the fact that the compiler will implicitly initialize the
6588 // static instances so that in_use == 0.
6591 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6592 if( ! static_frame[ i ].in_use ) {
6593 static_frame[ i ].in_use = 1;
6594 return static_frame + i;
6598 // The static ones are all in use. Malloc one.
6600 return safe_malloc( sizeof( QueryFrame ) );
6604 @brief Free a QueryFrame, and all the memory it owns.
6605 @param frame Pointer to the QueryFrame to be freed.
6607 static void free_query_frame( QueryFrame* frame ) {
6612 clear_class_info( &frame->core );
6614 // Free the join list
6616 ClassInfo* info = frame->join_list;
6619 free_class_info( info );
6623 frame->join_list = NULL;
6626 // If the frame is a static instance, just mark it as unused
6628 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6629 if( frame == static_frame + i ) {
6630 static_frame[ i ].in_use = 0;
6635 // Otherwise it must have been malloc'd, so free it
6641 @brief Search a given QueryFrame for a specified alias.
6642 @param frame Pointer to the QueryFrame to be searched.
6643 @param target The alias for which to search.
6644 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6646 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6647 if( ! frame || ! target ) {
6651 ClassInfo* found_class = NULL;
6653 if( !strcmp( target, frame->core.alias ) )
6654 return &(frame->core);
6656 ClassInfo* curr_class = frame->join_list;
6657 while( curr_class ) {
6658 if( strcmp( target, curr_class->alias ) )
6659 curr_class = curr_class->next;
6661 found_class = curr_class;
6671 @brief Push a new (blank) QueryFrame onto the stack.
6673 static void push_query_frame( void ) {
6674 QueryFrame* frame = allocate_frame();
6675 frame->join_list = NULL;
6676 frame->next = curr_query;
6678 // Initialize the ClassInfo for the core class
6679 ClassInfo* core = &frame->core;
6680 core->alias = core->class_name = core->source_def = NULL;
6681 core->class_def = core->fields = core->links = NULL;
6687 @brief Pop a QueryFrame off the stack and destroy it.
6689 static void pop_query_frame( void ) {
6694 QueryFrame* popped = curr_query;
6695 curr_query = popped->next;
6697 free_query_frame( popped );
6701 @brief Populate the ClassInfo for the core class.
6702 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6703 class name as an alias.
6704 @param class_name Name of the core class.
6705 @return Zero if successful, or 1 if not.
6707 Populate the ClassInfo of the core class with copies of the alias and class name, and
6708 with pointers to the relevant portions of the IDL for the core class.
6710 static int add_query_core( const char* alias, const char* class_name ) {
6713 if( ! curr_query ) {
6714 osrfLogError( OSRF_LOG_MARK,
6715 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6717 } else if( curr_query->core.alias ) {
6718 osrfLogError( OSRF_LOG_MARK,
6719 "%s ERROR: Core class %s already populated as %s",
6720 modulename, curr_query->core.class_name, curr_query->core.alias );
6724 build_class_info( &curr_query->core, alias, class_name );
6725 if( curr_query->core.alias )
6728 osrfLogError( OSRF_LOG_MARK,
6729 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6735 @brief Search the current QueryFrame for a specified alias.
6736 @param target The alias for which to search.
6737 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6739 static inline ClassInfo* search_alias( const char* target ) {
6740 return search_alias_in_frame( curr_query, target );
6744 @brief Search all levels of query for a specified alias, starting with the current query.
6745 @param target The alias for which to search.
6746 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6748 static ClassInfo* search_all_alias( const char* target ) {
6749 ClassInfo* found_class = NULL;
6750 QueryFrame* curr_frame = curr_query;
6752 while( curr_frame ) {
6753 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6756 curr_frame = curr_frame->next;
6763 @brief Add a class to the list of classes joined to the current query.
6764 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6765 the class name as an alias.
6766 @param classname The name of the class to be added.
6767 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6769 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6771 if( ! classname || ! *classname ) { // sanity check
6772 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6779 const ClassInfo* conflict = search_alias( alias );
6781 osrfLogError( OSRF_LOG_MARK,
6782 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6783 modulename, alias, conflict->class_name );
6787 ClassInfo* info = allocate_class_info();
6789 if( build_class_info( info, alias, classname ) ) {
6790 free_class_info( info );
6794 // Add the new ClassInfo to the join list of the current QueryFrame
6795 info->next = curr_query->join_list;
6796 curr_query->join_list = info;
6802 @brief Destroy all nodes on the query stack.
6804 static void clear_query_stack( void ) {