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 int child_initialized = 0; /* boolean */
162 static dbi_conn writehandle; /* our MASTER db connection */
163 static dbi_conn dbhandle; /* our CURRENT db connection */
164 //static osrfHash * readHandles;
165 static jsonObject* const jsonNULL = NULL; //
166 static int max_flesh_depth = 100;
168 // The following points to the top of a stack of QueryFrames. It's a little
169 // confusing because the top level of the query is at the bottom of the stack.
170 static QueryFrame* curr_query = NULL;
172 //----------------------------------
174 int oilsExtendIDL( void );
176 static dbi_conn writehandle; /* our MASTER db connection */
177 static dbi_conn dbhandle; /* our CURRENT db connection */
178 //static osrfHash * readHandles;
180 static const int enforce_pcrud = ENFORCE_PCRUD; // Boolean
181 static const char modulename[] = MODULENAME;
185 @brief Disconnect from the database.
187 This function is called when the server drone is about to terminate.
189 void osrfAppChildExit() {
190 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
193 if (writehandle == dbhandle)
197 dbi_conn_query(writehandle, "ROLLBACK;");
198 dbi_conn_close(writehandle);
201 if (dbhandle && !same)
202 dbi_conn_close(dbhandle);
204 // XXX add cleanup of readHandles whenever that gets used
210 @brief Initialize the application.
211 @return Zero if successful, or non-zero if not.
213 Load the IDL file into an internal data structure for future reference. Each non-virtual
214 class in the IDL corresponds to a table or view in the database, or to a subquery defined
215 in the IDL. Ignore all virtual tables and virtual fields.
217 Register a number of methods, some of them general-purpose and others specific for
220 The name of the application is given by the MODULENAME macro, whose value depends on
221 conditional compilation. The method names also incorporate MODULENAME, followed by a
222 dot, as a prefix. Some methods are registered or not registered depending on whether
223 the PCRUD macro is defined, and on whether the IDL includes permacrud entries for the
226 The general-purpose methods are as follows (minus their MODULENAME prefixes):
228 - json_query (not registered for PCRUD)
231 - transaction.rollback
236 For each non-virtual class, create up to eight class-specific methods:
238 - create (not for readonly classes)
240 - update (not for readonly classes)
241 - delete (not for readonly classes
242 - search (atomic and non-atomic versions)
243 - id_list (atomic and non-atomic versions)
245 For PCRUD, the full method names follow the pattern "MODULENAME.method_type.classname".
246 Otherwise they follow the pattern "MODULENAME.direct.XXX.method_type", where XXX is the
247 fieldmapper name from the IDL, with every run of one or more consecutive colons replaced
248 by a period. In addition, the names of atomic methods have a suffix of ".atomic".
250 This function is called when the registering the application, and is executed by the
251 listener before spawning the drones.
253 int osrfAppInitialize() {
255 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
256 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
258 if (!oilsIDLInit( osrf_settings_host_value("/IDL") ))
259 return 1; /* return non-zero to indicate error */
261 growing_buffer* method_name = buffer_init(64);
263 if( ! enforce_pcrud ) {
264 // Generic search thingy (not for PCRUD)
265 buffer_add( method_name, modulename );
266 buffer_add( method_name, ".json_query" );
267 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
268 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
271 // first we register all the transaction and savepoint methods
272 buffer_reset(method_name);
273 OSRF_BUFFER_ADD(method_name, modulename );
274 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
275 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
276 "beginTransaction", "", 0, 0 );
278 buffer_reset(method_name);
279 OSRF_BUFFER_ADD(method_name, modulename );
280 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
281 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
282 "commitTransaction", "", 0, 0 );
284 buffer_reset(method_name);
285 OSRF_BUFFER_ADD(method_name, modulename );
286 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
287 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
288 "rollbackTransaction", "", 0, 0 );
290 buffer_reset(method_name);
291 OSRF_BUFFER_ADD(method_name, modulename );
292 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
293 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
294 "setSavepoint", "", 1, 0 );
296 buffer_reset(method_name);
297 OSRF_BUFFER_ADD(method_name, modulename );
298 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
299 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
300 "releaseSavepoint", "", 1, 0 );
302 buffer_reset(method_name);
303 OSRF_BUFFER_ADD(method_name, modulename );
304 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
305 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
306 "rollbackSavepoint", "", 1, 0 );
308 static const char* global_method[] = {
316 const int global_method_count
317 = sizeof( global_method ) / sizeof ( global_method[0] );
319 unsigned long class_count = osrfHashGetCount( oilsIDL() );
320 osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
321 osrfLogDebug(OSRF_LOG_MARK,
322 "At most %lu methods will be generated",
323 (unsigned long) (class_count * global_method_count) );
325 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
326 osrfHash* idlClass = NULL;
328 // For each class in the IDL...
329 while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
331 const char* classname = osrfHashIteratorKey( class_itr );
332 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
334 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), modulename )) {
335 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
336 modulename, classname);
340 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
341 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
345 // Look up some other attributes of the current class
346 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
347 if( !idlClass_fieldmapper ) {
348 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
353 osrfHash* idlClass_permacrud = NULL;
354 if( enforce_pcrud ) {
355 // For PCRUD, ignore classes with no permacrud section
356 idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
357 if (!idlClass_permacrud) {
358 osrfLogDebug( OSRF_LOG_MARK,
359 "Skipping class \"%s\"; no permacrud in IDL", classname );
364 const char* readonly = osrfHashGet(idlClass, "readonly");
367 for( i = 0; i < global_method_count; ++i ) { // for each global method
368 const char* method_type = global_method[ i ];
369 osrfLogDebug(OSRF_LOG_MARK,
370 "Using files to build %s class methods for %s", method_type, classname);
372 if( enforce_pcrud ) {
373 // Treat "id_list" or "search" as forms of "retrieve"
374 const char* tmp_method = method_type;
375 if ( *tmp_method == 'i' || *tmp_method == 's') { // "id_list" or "search"
376 tmp_method = "retrieve";
378 // Skip this method if there is no permacrud entry for it
379 if (!osrfHashGet( idlClass_permacrud, tmp_method ))
383 // No create, update, or delete methods for a readonly class
384 if ( str_is_true( readonly )
385 && ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd') )
388 buffer_reset( method_name );
390 // Build the method name
391 if( enforce_pcrud ) {
392 // For PCRUD: MODULENAME.method_type.classname
393 buffer_fadd(method_name, "%s.%s.%s", modulename, method_type, classname);
395 // For non-PCRUD: MODULENAME.MODULENAME.direct.XXX.method_type
396 // where XXX is the fieldmapper name from the IDL, with every run of
397 // one or more consecutive colons replaced by a period.
400 char* _fm = strdup( idlClass_fieldmapper );
401 part = strtok_r(_fm, ":", &st_tmp);
403 buffer_fadd(method_name, "%s.direct.%s", modulename, part);
405 while ((part = strtok_r(NULL, ":", &st_tmp))) {
406 OSRF_BUFFER_ADD_CHAR(method_name, '.');
407 OSRF_BUFFER_ADD(method_name, part);
409 OSRF_BUFFER_ADD_CHAR(method_name, '.');
410 OSRF_BUFFER_ADD(method_name, method_type);
414 // For an id_list or search method we specify the OSRF_METHOD_STREAMING option.
415 // The consequence is that we implicitly create an atomic method in addition to
416 // the usual non-atomic method.
418 if (*method_type == 'i' || *method_type == 's') { // id_list or search
419 flags = flags | OSRF_METHOD_STREAMING;
422 osrfHash* method_meta = osrfNewHash();
423 osrfHashSet( method_meta, idlClass, "class");
424 osrfHashSet( method_meta, buffer_data( method_name ), "methodname" );
425 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
427 // Register the method, with a pointer to an osrfHash to tell the method
428 // its name, type, and class.
429 osrfAppRegisterExtendedMethod(
431 OSRF_BUFFER_C_STR( method_name ),
432 "dispatchCRUDMethod",
439 } // end for each global method
440 } // end for each class in IDL
442 buffer_free( method_name );
443 osrfHashIteratorFree( class_itr );
449 @brief Get a table name, view name, or subquery for use in a FROM clause.
450 @param class Pointer to the IDL class entry.
451 @return A table name, a view name, or a subquery in parentheses.
453 In some cases the IDL defines a class, not with a table name or a view name, but with
454 a SELECT statement, which may be used as a subquery.
456 static char* getRelation( osrfHash* class ) {
458 char* source_def = NULL;
459 const char* tabledef = osrfHashGet(class, "tablename");
462 source_def = strdup( tabledef ); // Return the name of a table or view
464 tabledef = osrfHashGet( class, "source_definition" );
466 // Return a subquery, enclosed in parentheses
467 source_def = safe_malloc( strlen( tabledef ) + 3 );
468 source_def[ 0 ] = '(';
469 strcpy( source_def + 1, tabledef );
470 strcat( source_def, ")" );
472 // Not found: return an error
473 const char* classname = osrfHashGet( class, "classname" );
478 "%s ERROR No tablename or source_definition for class \"%s\"",
489 @brief Initialize a server drone.
490 @return Zero if successful, -1 if not.
492 Connect to the database. For each non-virtual class in the IDL, execute a dummy "SELECT * "
493 query to get the datatype of each column. Record the datatypes in the loaded IDL.
495 This function is called by a server drone shortly after it is spawned by the listener.
497 int osrfAppChildInit() {
499 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
500 dbi_initialize(NULL);
501 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
503 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", modulename );
504 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", modulename );
505 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", modulename );
506 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", modulename );
507 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", modulename );
508 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", modulename );
509 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion",
512 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
513 writehandle = dbi_conn_new(driver);
516 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
519 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
521 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
522 "port=%s, user=%s, db=%s", modulename, host, port, user, db );
524 if(host) dbi_conn_set_option(writehandle, "host", host );
525 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
526 if(user) dbi_conn_set_option(writehandle, "username", user);
527 if(pw) dbi_conn_set_option(writehandle, "password", pw );
528 if(db) dbi_conn_set_option(writehandle, "dbname", db );
530 if(md) max_flesh_depth = atoi(md);
531 if(max_flesh_depth < 0) max_flesh_depth = 1;
532 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
541 if (dbi_conn_connect(writehandle) < 0) {
543 if (dbi_conn_connect(writehandle) < 0) {
544 dbi_conn_error(writehandle, &err);
545 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
550 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", modulename );
552 // Add datatypes from database to the fields in the IDL
553 if( oilsExtendIDL() ) {
554 osrfLogError( OSRF_LOG_MARK, "Error extending the IDL" );
562 @brief Add datatypes from the database to the fields in the IDL.
563 @return Zero if successful, or 1 upon error.
565 For each relevant class in the IDL: ask the database for the datatype of every field.
566 In particular, determine which fields are text fields and which fields are numeric
567 fields, so that we know whether to enclose their values in quotes.
569 At this writing this function does not detect any errors, so it always returns zero.
571 int oilsExtendIDL( void ) {
572 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
573 osrfHash* class = NULL;
574 growing_buffer* query_buf = buffer_init( 64 );
576 // For each class in the IDL...
577 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
578 const char* classname = osrfHashIteratorKey( class_itr );
579 osrfHash* fields = osrfHashGet( class, "fields" );
581 // If the class is virtual, ignore it
582 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
583 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
587 char* tabledef = getRelation(class);
589 continue; // No such relation -- a query of it would be doomed to failure
591 buffer_reset( query_buf );
592 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
596 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
597 modulename, OSRF_BUFFER_C_STR( query_buf ) );
599 dbi_result result = dbi_conn_query( writehandle, OSRF_BUFFER_C_STR( query_buf ) );
603 const char* columnName;
605 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
607 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
610 /* fetch the fieldmapper index */
611 if( (_f = osrfHashGet(fields, columnName)) ) {
613 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
615 /* determine the field type and storage attributes */
617 switch( dbi_result_get_field_type_idx(result, columnIndex) ) {
619 case DBI_TYPE_INTEGER : {
621 if ( !osrfHashGet(_f, "primitive") )
622 osrfHashSet(_f, "number", "primitive");
624 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
625 if( attr & DBI_INTEGER_SIZE8 )
626 osrfHashSet(_f, "INT8", "datatype");
628 osrfHashSet(_f, "INT", "datatype");
631 case DBI_TYPE_DECIMAL :
632 if ( !osrfHashGet(_f, "primitive") )
633 osrfHashSet(_f, "number", "primitive");
635 osrfHashSet(_f,"NUMERIC", "datatype");
638 case DBI_TYPE_STRING :
639 if ( !osrfHashGet(_f, "primitive") )
640 osrfHashSet(_f,"string", "primitive");
642 osrfHashSet(_f,"TEXT", "datatype");
645 case DBI_TYPE_DATETIME :
646 if ( !osrfHashGet(_f, "primitive") )
647 osrfHashSet(_f,"string", "primitive");
649 osrfHashSet(_f,"TIMESTAMP", "datatype");
652 case DBI_TYPE_BINARY :
653 if ( !osrfHashGet(_f, "primitive") )
654 osrfHashSet(_f,"string", "primitive");
656 osrfHashSet(_f,"BYTEA", "datatype");
661 "Setting [%s] to primitive [%s] and datatype [%s]...",
663 osrfHashGet(_f, "primitive"),
664 osrfHashGet(_f, "datatype")
668 } // end while loop for traversing columns of result
669 dbi_result_free(result);
671 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", classname);
673 } // end for each class in IDL
675 buffer_free( query_buf );
676 osrfHashIteratorFree( class_itr );
677 child_initialized = 1;
682 @brief Install a database driver.
683 @param conn Pointer to a database driver.
685 The driver is used to process quoted strings correctly.
687 This function is a sleazy hack, intended @em only for testing and debugging without
688 actually connecting to a database. Any real server process should initialize the
689 database connection by calling osrfAppChildInit().
691 void set_cstore_dbi_conn( dbi_conn conn ) {
692 dbhandle = writehandle = conn;
696 @brief Free an osrfHash that stores a transaction ID.
697 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
699 This function is a callback, to be called by the application session when it ends.
700 The application session stores the osrfHash via an opaque pointer.
702 If the osrfHash contains an entry for the key "xact_id", it means that an
703 uncommitted transaction is pending. Roll it back.
705 void userDataFree( void* blob ) {
706 osrfHash* hash = (osrfHash*) blob;
707 if( osrfHashGet( hash, "xact_id" ) && writehandle )
708 dbi_conn_query( writehandle, "ROLLBACK;" );
710 osrfHashFree( hash );
714 @name Managing session data
715 @brief Maintain data stored via the userData pointer of the application session.
717 Currently, session-level data is stored in an osrfHash. Other arrangements are
718 possible, and some would be more efficient. The application session calls a
719 callback function to free userData before terminating.
721 Currently, the only data we store at the session level is the transaction id. By this
722 means we can ensure that any pending transactions are rolled back before the application
728 @brief Free an item in the application session's userData.
729 @param key The name of a key for an osrfHash.
730 @param item An opaque pointer to the item associated with the key.
732 We store an osrfHash as userData with the application session, and arrange (by
733 installing userDataFree() as a different callback) for the session to free that
734 osrfHash before terminating.
736 This function is a callback for freeing items in the osrfHash. Currently we store
738 - Transaction id of a pending transaction; a character string. Key: "xact_id".
739 - Authkey; a character string. Key: "authkey".
740 - User object from the authentication server; a jsonObject. Key: "user_login".
742 If we ever store anything else in userData, we will need to revisit this function so
743 that it will free whatever else needs freeing.
745 static void sessionDataFree( char* key, void* item ) {
746 if ( !strcmp( key, "xact_id" )
747 || !strcmp( key, "authkey" ) ) {
749 } else if( !strcmp( key, "user_login" ) )
750 jsonObjectFree( (jsonObject*) item );
754 @brief Save a transaction id.
755 @param ctx Pointer to the method context.
757 Save the session_id of the current application session as a transaction id.
759 static void setXactId( osrfMethodContext* ctx ) {
760 if( ctx && ctx->session ) {
761 osrfAppSession* session = ctx->session;
763 osrfHash* cache = session->userData;
765 // If the session doesn't already have a hash, create one. Make sure
766 // that the application session frees the hash when it terminates.
767 if( NULL == cache ) {
768 session->userData = cache = osrfNewHash();
769 osrfHashSetCallback( cache, &sessionDataFree );
770 ctx->session->userDataFree = &userDataFree;
773 // Save the transaction id in the hash, with the key "xact_id"
774 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
779 @brief Get the transaction ID for the current transaction, if any.
780 @param ctx Pointer to the method context.
781 @return Pointer to the transaction ID.
783 The return value points to an internal buffer, and will become invalid upon issuing
784 a commit or rollback.
786 static inline const char* getXactId( osrfMethodContext* ctx ) {
787 if( ctx && ctx->session && ctx->session->userData )
788 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
794 @brief Clear the current transaction id.
795 @param ctx Pointer to the method context.
797 static inline void clearXactId( osrfMethodContext* ctx ) {
798 if( ctx && ctx->session && ctx->session->userData )
799 osrfHashRemove( ctx->session->userData, "xact_id" );
804 @brief Save the user's login in the userData for the current application session.
805 @param ctx Pointer to the method context.
806 @param user_login Pointer to the user login object to be cached (we cache the original,
809 If @a user_login is NULL, remove the user login if one is already cached.
811 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
812 if( ctx && ctx->session ) {
813 osrfAppSession* session = ctx->session;
815 osrfHash* cache = session->userData;
817 // If the session doesn't already have a hash, create one. Make sure
818 // that the application session frees the hash when it terminates.
819 if( NULL == cache ) {
820 session->userData = cache = osrfNewHash();
821 osrfHashSetCallback( cache, &sessionDataFree );
822 ctx->session->userDataFree = &userDataFree;
826 osrfHashSet( cache, user_login, "user_login" );
828 osrfHashRemove( cache, "user_login" );
833 @brief Get the user login object for the current application session, if any.
834 @param ctx Pointer to the method context.
835 @return Pointer to the user login object if found; otherwise NULL.
837 The user login object was returned from the authentication server, and then cached so
838 we don't have to call the authentication server again for the same user.
840 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
841 if( ctx && ctx->session && ctx->session->userData )
842 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
848 @brief Save a copy of an authkey in the userData of the current application session.
849 @param ctx Pointer to the method context.
850 @param authkey The authkey to be saved.
852 If @a authkey is NULL, remove the authkey if one is already cached.
854 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
855 if( ctx && ctx->session && authkey ) {
856 osrfAppSession* session = ctx->session;
857 osrfHash* cache = session->userData;
859 // If the session doesn't already have a hash, create one. Make sure
860 // that the application session frees the hash when it terminates.
861 if( NULL == cache ) {
862 session->userData = cache = osrfNewHash();
863 osrfHashSetCallback( cache, &sessionDataFree );
864 ctx->session->userDataFree = &userDataFree;
867 // Save the transaction id in the hash, with the key "xact_id"
868 if( authkey && *authkey )
869 osrfHashSet( cache, strdup( authkey ), "authkey" );
871 osrfHashRemove( cache, "authkey" );
876 @brief Reset the login timeout.
877 @param authkey The authentication key for the current login session.
878 @param now The current time.
879 @return Zero if successful, or 1 if not.
881 Tell the authentication server to reset the timeout so that the login session won't
882 expire for a while longer.
884 We could dispense with the @a now parameter by calling time(). But we just called
885 time() in order to decide whether to reset the timeout, so we might as well reuse
886 the result instead of calling time() again.
888 static int reset_timeout( const char* authkey, time_t now ) {
889 jsonObject* auth_object = jsonNewObject( authkey );
891 // Ask the authentication server to reset the timeout. It returns an event
892 // indicating success or failure.
893 jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
894 "open-ils.auth.session.reset_timeout", auth_object );
895 jsonObjectFree(auth_object);
897 if( !result || result->type != JSON_HASH ) {
898 osrfLogError( OSRF_LOG_MARK,
899 "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
900 jsonObjectFree( result );
901 return 1; // Not the right sort of object returned
904 const jsonObject* ilsevent = jsonObjectGetKey( result, "ilsevent" );
905 if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
906 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
907 jsonObjectFree( result );
908 return 1; // Return code from method not available
911 if( jsonObjectGetNumber( ilsevent ) != 0.0 ){
912 const char* desc = jsonObjectGetString( jsonObjectGetKey( result, "desc" ) );
914 desc = "(No reason available)"; // failsafe; shouldn't happen
915 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
916 jsonObjectFree( result );
920 // Revise our local proxy for the timeout deadline
921 // by a smallish fraction of the timeout interval
922 const char* timeout = jsonObjectGetString( jsonObjectGetKey( result, "payload" ) );
924 timeout = "1"; // failsafe; shouldn't happen
925 time_next_reset = now + atoi( timeout ) / 15;
927 jsonObjectFree( result );
928 return 0; // Successfully reset timeout
932 @brief Get the authkey string for the current application session, if any.
933 @param ctx Pointer to the method context.
934 @return Pointer to the cached authkey if found; otherwise NULL.
936 If present, the authkey string was cached from a previous method call.
938 static const char* getAuthkey( osrfMethodContext* ctx ) {
939 if( ctx && ctx->session && ctx->session->userData ) {
940 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
942 // Possibly reset the authentication timeout to keep the login alive. We do so
943 // no more than once per method call, and not at all if it has been only a short
944 // time since the last reset.
946 // Here we reset explicitly, if at all. We also implicitly reset the timeout
947 // whenever we call the "open-ils.auth.session.retrieve" method.
948 if( timeout_needs_resetting ) {
949 time_t now = time( NULL );
950 if( now >= time_next_reset && reset_timeout( authkey, now ) )
951 authkey = NULL; // timeout has apparently expired already
954 timeout_needs_resetting = 0;
962 @brief Implement the transaction.begin method.
963 @param ctx Pointer to the method context.
964 @return Zero if successful, or -1 upon error.
966 Start a transaction. Save a transaction ID for future reference.
969 - authkey (PCRUD only)
971 Return to client: Transaction ID
973 int beginTransaction ( osrfMethodContext* ctx ) {
974 if(osrfMethodVerifyContext( ctx )) {
975 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
979 if( enforce_pcrud ) {
980 timeout_needs_resetting = 1;
981 const jsonObject* user = verifyUserPCRUD( ctx );
986 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
988 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", modulename );
989 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
990 "osrfMethodException", ctx->request, "Error starting transaction" );
994 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
995 osrfAppRespondComplete( ctx, ret );
1002 @brief Implement the savepoint.set method.
1003 @param ctx Pointer to the method context.
1004 @return Zero if successful, or -1 if not.
1006 Issue a SAVEPOINT to the database server.
1009 - authkey (PCRUD only)
1012 Return to client: Savepoint name
1014 int setSavepoint ( osrfMethodContext* ctx ) {
1015 if(osrfMethodVerifyContext( ctx )) {
1016 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1021 if( enforce_pcrud ) {
1023 timeout_needs_resetting = 1;
1024 const jsonObject* user = verifyUserPCRUD( ctx );
1029 // Verify that a transaction is pending
1030 const char* trans_id = getXactId( ctx );
1031 if( NULL == trans_id ) {
1032 osrfAppSessionStatus(
1034 OSRF_STATUS_INTERNALSERVERERROR,
1035 "osrfMethodException",
1037 "No active transaction -- required for savepoints"
1042 // Get the savepoint name from the method params
1043 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1045 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
1049 "%s: Error creating savepoint %s in transaction %s",
1054 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1055 "osrfMethodException", ctx->request, "Error creating savepoint" );
1058 jsonObject* ret = jsonNewObject(spName);
1059 osrfAppRespondComplete( ctx, ret );
1060 jsonObjectFree(ret);
1066 @brief Implement the savepoint.release method.
1067 @param ctx Pointer to the method context.
1068 @return Zero if successful, or -1 if not.
1070 Issue a RELEASE SAVEPOINT to the database server.
1073 - authkey (PCRUD only)
1076 Return to client: Savepoint name
1078 int releaseSavepoint ( osrfMethodContext* ctx ) {
1079 if(osrfMethodVerifyContext( ctx )) {
1080 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1085 if( enforce_pcrud ) {
1087 timeout_needs_resetting = 1;
1088 const jsonObject* user = verifyUserPCRUD( ctx );
1093 // Verify that a transaction is pending
1094 const char* trans_id = getXactId( ctx );
1095 if( NULL == trans_id ) {
1096 osrfAppSessionStatus(
1098 OSRF_STATUS_INTERNALSERVERERROR,
1099 "osrfMethodException",
1101 "No active transaction -- required for savepoints"
1106 // Get the savepoint name from the method params
1107 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1109 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
1113 "%s: Error releasing savepoint %s in transaction %s",
1118 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1119 "osrfMethodException", ctx->request, "Error releasing savepoint" );
1122 jsonObject* ret = jsonNewObject(spName);
1123 osrfAppRespondComplete( ctx, ret );
1124 jsonObjectFree(ret);
1130 @brief Implement the savepoint.rollback method.
1131 @param ctx Pointer to the method context.
1132 @return Zero if successful, or -1 if not.
1134 Issue a ROLLBACK TO SAVEPOINT to the database server.
1137 - authkey (PCRUD only)
1140 Return to client: Savepoint name
1142 int rollbackSavepoint ( osrfMethodContext* ctx ) {
1143 if(osrfMethodVerifyContext( ctx )) {
1144 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1149 if( enforce_pcrud ) {
1151 timeout_needs_resetting = 1;
1152 const jsonObject* user = verifyUserPCRUD( ctx );
1157 // Verify that a transaction is pending
1158 const char* trans_id = getXactId( ctx );
1159 if( NULL == trans_id ) {
1160 osrfAppSessionStatus(
1162 OSRF_STATUS_INTERNALSERVERERROR,
1163 "osrfMethodException",
1165 "No active transaction -- required for savepoints"
1170 // Get the savepoint name from the method params
1171 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1173 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
1177 "%s: Error rolling back savepoint %s in transaction %s",
1182 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1183 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1186 jsonObject* ret = jsonNewObject(spName);
1187 osrfAppRespondComplete( ctx, ret );
1188 jsonObjectFree(ret);
1194 @brief Implement the transaction.commit method.
1195 @param ctx Pointer to the method context.
1196 @return Zero if successful, or -1 if not.
1198 Issue a COMMIT to the database server.
1201 - authkey (PCRUD only)
1203 Return to client: Transaction ID.
1205 int commitTransaction ( osrfMethodContext* ctx ) {
1206 if(osrfMethodVerifyContext( ctx )) {
1207 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1211 if( enforce_pcrud ) {
1212 timeout_needs_resetting = 1;
1213 const jsonObject* user = verifyUserPCRUD( ctx );
1218 // Verify that a transaction is pending
1219 const char* trans_id = getXactId( ctx );
1220 if( NULL == trans_id ) {
1221 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1222 "osrfMethodException", ctx->request, "No active transaction to commit" );
1226 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
1228 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", modulename );
1229 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1230 "osrfMethodException", ctx->request, "Error committing transaction" );
1233 jsonObject* ret = jsonNewObject( trans_id );
1234 osrfAppRespondComplete( ctx, ret );
1235 jsonObjectFree(ret);
1242 @brief Implement the transaction.rollback method.
1243 @param ctx Pointer to the method context.
1244 @return Zero if successful, or -1 if not.
1246 Issue a ROLLBACK to the database server.
1249 - authkey (PCRUD only)
1251 Return to client: Transaction ID
1253 int rollbackTransaction ( osrfMethodContext* ctx ) {
1254 if(osrfMethodVerifyContext( ctx )) {
1255 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1259 if( enforce_pcrud ) {
1260 timeout_needs_resetting = 1;
1261 const jsonObject* user = verifyUserPCRUD( ctx );
1266 // Verify that a transaction is pending
1267 const char* trans_id = getXactId( ctx );
1268 if( NULL == trans_id ) {
1269 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1270 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1274 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
1276 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", modulename );
1277 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1278 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1281 jsonObject* ret = jsonNewObject( trans_id );
1282 osrfAppRespondComplete( ctx, ret );
1283 jsonObjectFree(ret);
1290 @brief Implement the class-specific methods.
1291 @param ctx Pointer to the method context.
1292 @return Zero if successful, or -1 if not.
1294 Branch on the method type: create, retrieve, update, delete, search, or id_list.
1296 The method parameters and the type of value returned to the client depend on the method
1297 type. However, for PCRUD methods, the first method parameter should always be an
1300 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
1302 // Get the method type, then can branch on it
1303 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1304 const char* methodtype = osrfHashGet( method_meta, "methodtype" );
1306 if( !strcmp( methodtype, "create" ))
1307 return doCreate( ctx );
1308 else if( !strcmp(methodtype, "retrieve" ))
1309 return doRetrieve( ctx );
1310 else if( !strcmp(methodtype, "update" ))
1311 return doUpdate( ctx );
1312 else if( !strcmp(methodtype, "delete" ))
1313 return doDelete( ctx );
1314 else if( !strcmp(methodtype, "search" ))
1315 return doSearch( ctx );
1316 else if( !strcmp(methodtype, "id_list" ))
1317 return doIdList( ctx );
1319 osrfAppRespondComplete( ctx, NULL ); // should be unreachable...
1325 @brief Implement the "search" method.
1326 @param ctx Pointer to the method context.
1327 @return Zero if successful, or -1 if not.
1330 - authkey (PCRUD only)
1331 - WHERE clause, as jsonObject
1332 - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1334 Return to client: rows of the specified class that satisfy a specified WHERE clause.
1335 Optionally flesh linked fields.
1337 static int doSearch( osrfMethodContext* ctx ) {
1338 if(osrfMethodVerifyContext( ctx )) {
1339 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1344 timeout_needs_resetting = 1;
1346 jsonObject* where_clause;
1347 jsonObject* rest_of_query;
1349 if( enforce_pcrud ) {
1350 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1351 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1353 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1354 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1357 // Get the class metadata
1358 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1359 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1363 jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1365 osrfAppRespondComplete( ctx, NULL );
1369 // Return each row to the client (except that some may be suppressed by PCRUD)
1370 jsonObject* cur = 0;
1371 unsigned long res_idx = 0;
1372 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1373 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1375 osrfAppRespond( ctx, cur );
1377 jsonObjectFree( obj );
1379 osrfAppRespondComplete( ctx, NULL );
1384 @brief Implement the "id_list" method.
1385 @param ctx Pointer to the method context.
1386 @param err Pointer through which to return an error code.
1387 @return Zero if successful, or -1 if not.
1390 - authkey (PCRUD only)
1391 - WHERE clause, as jsonObject
1392 - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1394 Return to client: The primary key values for all rows of the relevant class that
1395 satisfy a specified WHERE clause.
1397 This method relies on the assumption that every class has a primary key consisting of
1400 static int doIdList( osrfMethodContext* ctx ) {
1401 if(osrfMethodVerifyContext( ctx )) {
1402 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1407 timeout_needs_resetting = 1;
1409 jsonObject* where_clause;
1410 jsonObject* rest_of_query;
1412 // We use the where clause without change. But we need to massage the rest of the
1413 // query, so we work with a copy of it instead of modifying the original.
1415 if( enforce_pcrud ) {
1416 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1417 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1419 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1420 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1423 // Eliminate certain SQL clauses, if present.
1424 if ( rest_of_query ) {
1425 jsonObjectRemoveKey( rest_of_query, "select" );
1426 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1427 jsonObjectRemoveKey( rest_of_query, "flesh" );
1428 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
1430 rest_of_query = jsonNewObjectType( JSON_HASH );
1433 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1435 // Get the class metadata
1436 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1437 osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1439 // Build a SELECT list containing just the primary key,
1440 // i.e. like { "classname":["keyname"] }
1441 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1443 // Load array with name of primary key
1444 jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1445 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1446 jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1448 jsonObjectSetKey( rest_of_query, "select", select_clause );
1453 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1455 jsonObjectFree( rest_of_query );
1457 osrfAppRespondComplete( ctx, NULL );
1461 // Return each primary key value to the client
1463 unsigned long res_idx = 0;
1464 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1465 if( enforce_pcrud && !verifyObjectPCRUD( ctx, cur ))
1466 continue; // Suppress due to lack of permission
1468 osrfAppRespond( ctx,
1469 oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1472 jsonObjectFree( obj );
1473 osrfAppRespondComplete( ctx, NULL );
1478 @brief Verify that we have a valid class reference.
1479 @param ctx Pointer to the method context.
1480 @param param Pointer to the method parameters.
1481 @return 1 if the class reference is valid, or zero if it isn't.
1483 The class of the method params must match the class to which the method id devoted.
1484 For PCRUD there are additional restrictions.
1486 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1488 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1489 osrfHash* class = osrfHashGet( method_meta, "class" );
1491 // Compare the method's class to the parameters' class
1492 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1494 // Oops -- they don't match. Complain.
1495 growing_buffer* msg = buffer_init(128);
1498 "%s: %s method for type %s was passed a %s",
1500 osrfHashGet(method_meta, "methodtype"),
1501 osrfHashGet(class, "classname"),
1502 param->classname ? param->classname : "(null)"
1505 char* m = buffer_release(msg);
1506 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1514 return verifyObjectPCRUD( ctx, param );
1520 @brief (PCRUD only) Verify that the user is properly logged in.
1521 @param ctx Pointer to the method context.
1522 @return If the user is logged in, a pointer to the user object from the authentication
1523 server; otherwise NULL.
1525 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1527 // Get the authkey (the first method parameter)
1528 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1530 // See if we have the same authkey, and a user object,
1531 // locally cached from a previous call
1532 const char* cached_authkey = getAuthkey( ctx );
1533 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1534 const jsonObject* cached_user = getUserLogin( ctx );
1539 // We have no matching authentication data in the cache. Authenticate from scratch.
1540 jsonObject* auth_object = jsonNewObject(auth);
1542 // Fetch the user object from the authentication server
1543 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1545 jsonObjectFree(auth_object);
1547 if (!user->classname || strcmp(user->classname, "au")) {
1549 growing_buffer* msg = buffer_init(128);
1552 "%s: permacrud received a bad auth token: %s",
1557 char* m = buffer_release(msg);
1558 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1562 jsonObjectFree(user);
1566 setUserLogin( ctx, user );
1567 setAuthkey( ctx, auth );
1569 // Allow ourselves up to a second before we have to reset the login timeout.
1570 // It would be nice to use some fraction of the timeout interval enforced by the
1571 // authentication server, but that value is not readily available at this point.
1572 // Instead, we use a conservative default interval.
1573 time_next_reset = time( NULL ) + 1;
1578 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1580 dbhandle = writehandle;
1582 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1583 osrfHash* class = osrfHashGet( method_metadata, "class" );
1584 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1587 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
1588 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1589 } else if ( *method_type == 'u' || *method_type == 'd' ) {
1590 fetch = 1; // MUST go to the db for the object for update and delete
1593 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
1596 // No permacrud for this method type on this class
1598 growing_buffer* msg = buffer_init(128);
1601 "%s: %s on class %s has no permacrud IDL entry",
1603 osrfHashGet(method_metadata, "methodtype"),
1604 osrfHashGet(class, "classname")
1607 char* m = buffer_release(msg);
1608 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1609 "osrfMethodException", ctx->request, m );
1616 const jsonObject* user = verifyUserPCRUD( ctx );
1620 int userid = atoi( oilsFMGetString( user, "id" ) );
1622 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1623 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1624 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1626 osrfStringArray* context_org_array = osrfNewStringArray(1);
1629 char* pkey_value = NULL;
1630 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1631 osrfLogDebug( OSRF_LOG_MARK,
1632 "global-level permissions required, fetching top of the org tree" );
1634 // check for perm at top of org tree
1635 char* org_tree_root_id = org_tree_root( ctx );
1636 if( org_tree_root_id ) {
1637 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1638 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1640 osrfStringArrayFree( context_org_array );
1645 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1646 "fetching context org ids" );
1647 const char* pkey = osrfHashGet(class, "primarykey");
1648 jsonObject *param = NULL;
1650 if (obj->classname) {
1651 pkey_value = oilsFMGetString( obj, pkey );
1653 param = jsonObjectClone(obj);
1654 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1657 pkey_value = jsonObjectToSimpleString( obj );
1659 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1660 "of %s and retrieving from the database", pkey_value );
1664 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1665 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1666 jsonObjectFree(_tmp_params);
1668 param = jsonObjectExtractIndex(_list, 0);
1669 jsonObjectFree(_list);
1673 osrfLogDebug( OSRF_LOG_MARK,
1674 "Object not found in the database with primary key %s of %s",
1677 growing_buffer* msg = buffer_init(128);
1680 "%s: no object found with primary key %s of %s",
1686 char* m = buffer_release(msg);
1687 osrfAppSessionStatus(
1689 OSRF_STATUS_INTERNALSERVERERROR,
1690 "osrfMethodException",
1696 if (pkey_value) free(pkey_value);
1701 if (local_context->size > 0) {
1702 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1703 local_context->size);
1705 const char* lcontext = NULL;
1706 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1707 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1710 "adding class-local field %s (value: %s) to the context org list",
1712 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1717 if (foreign_context) {
1718 unsigned long class_count = osrfHashGetCount( foreign_context );
1719 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1721 if (class_count > 0) {
1723 osrfHash* fcontext = NULL;
1724 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1725 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1726 const char* class_name = osrfHashIteratorKey( class_itr );
1727 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1731 "%d foreign context fields(s) specified for class %s",
1732 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1736 char* foreign_pkey = osrfHashGet(fcontext, "field");
1737 char* foreign_pkey_value =
1738 oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1740 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1742 jsonObject* _list = doFieldmapperSearch(
1743 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1745 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1746 jsonObjectFree(_tmp_params);
1747 jsonObjectFree(_list);
1749 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1751 if (_fparam && jump_list) {
1752 const char* flink = NULL;
1754 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1755 free(foreign_pkey_value);
1757 osrfHash* foreign_link_hash =
1758 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1760 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1761 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1763 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1765 _list = doFieldmapperSearch(
1767 osrfHashGet( oilsIDL(),
1768 osrfHashGet( foreign_link_hash, "class" ) ),
1774 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1775 jsonObjectFree(_tmp_params);
1776 jsonObjectFree(_list);
1782 growing_buffer* msg = buffer_init(128);
1785 "%s: no object found with primary key %s of %s",
1791 char* m = buffer_release(msg);
1792 osrfAppSessionStatus(
1794 OSRF_STATUS_INTERNALSERVERERROR,
1795 "osrfMethodException",
1801 osrfHashIteratorFree(class_itr);
1802 free(foreign_pkey_value);
1803 jsonObjectFree(param);
1808 free(foreign_pkey_value);
1811 const char* foreign_field = NULL;
1812 while ( (foreign_field = osrfStringArrayGetString(
1813 osrfHashGet(fcontext,"context" ), j++)) ) {
1814 osrfStringArrayAdd( context_org_array,
1815 oilsFMGetString( _fparam, foreign_field ) );
1818 "adding foreign class %s field %s (value: %s) to the context org list",
1821 osrfStringArrayGetString(
1822 context_org_array, context_org_array->size - 1)
1826 jsonObjectFree(_fparam);
1829 osrfHashIteratorFree( class_itr );
1833 jsonObjectFree(param);
1836 const char* context_org = NULL;
1837 const char* perm = NULL;
1840 if (permission->size == 0) {
1841 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1846 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1848 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1854 "Checking object permission [%s] for user %d "
1855 "on object %s (class %s) at org %d",
1859 osrfHashGet(class, "classname"),
1863 result = dbi_conn_queryf(
1865 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1868 osrfHashGet(class, "classname"),
1876 "Received a result for object permission [%s] "
1877 "for user %d on object %s (class %s) at org %d",
1881 osrfHashGet(class, "classname"),
1885 if (dbi_result_first_row(result)) {
1886 jsonObject* return_val = oilsMakeJSONFromResult( result );
1887 const char* has_perm = jsonObjectGetString(
1888 jsonObjectGetKeyConst(return_val, "has_perm") );
1892 "Status of object permission [%s] for user %d "
1893 "on object %s (class %s) at org %d is %s",
1897 osrfHashGet(class, "classname"),
1902 if ( *has_perm == 't' ) OK = 1;
1903 jsonObjectFree(return_val);
1906 dbi_result_free(result);
1912 osrfLogDebug( OSRF_LOG_MARK,
1913 "Checking non-object permission [%s] for user %d at org %d",
1914 perm, userid, atoi(context_org) );
1915 result = dbi_conn_queryf(
1917 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1924 osrfLogDebug( OSRF_LOG_MARK,
1925 "Received a result for permission [%s] for user %d at org %d",
1926 perm, userid, atoi(context_org) );
1927 if ( dbi_result_first_row(result) ) {
1928 jsonObject* return_val = oilsMakeJSONFromResult( result );
1929 const char* has_perm = jsonObjectGetString(
1930 jsonObjectGetKeyConst(return_val, "has_perm") );
1931 osrfLogDebug( OSRF_LOG_MARK,
1932 "Status of permission [%s] for user %d at org %d is [%s]",
1933 perm, userid, atoi(context_org), has_perm );
1934 if ( *has_perm == 't' )
1936 jsonObjectFree(return_val);
1939 dbi_result_free(result);
1948 if (pkey_value) free(pkey_value);
1949 osrfStringArrayFree(context_org_array);
1955 @brief Look up the root of the org_unit tree.
1956 @param ctx Pointer to the method context.
1957 @return The id of the root org unit, as a character string.
1959 Query actor.org_unit where parent_ou is null, and return the id as a string.
1961 This function assumes that there is only one root org unit, i.e. that we
1962 have a single tree, not a forest.
1964 The calling code is responsible for freeing the returned string.
1966 static char* org_tree_root( osrfMethodContext* ctx ) {
1968 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1969 static time_t last_lookup_time = 0;
1970 time_t current_time = time( NULL );
1972 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1973 // We successfully looked this up less than an hour ago.
1974 // It's not likely to have changed since then.
1975 return strdup( cached_root_id );
1977 last_lookup_time = current_time;
1980 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1981 jsonObject* result = doFieldmapperSearch(
1982 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1983 jsonObjectFree( where_clause );
1985 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1988 jsonObjectFree( result );
1990 growing_buffer* msg = buffer_init(128);
1991 OSRF_BUFFER_ADD( msg, modulename );
1992 OSRF_BUFFER_ADD( msg,
1993 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1995 char* m = buffer_release(msg);
1996 osrfAppSessionStatus( ctx->session,
1997 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2000 cached_root_id[ 0 ] = '\0';
2004 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
2005 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2007 jsonObjectFree( result );
2009 strcpy( cached_root_id, root_org_unit_id );
2010 return root_org_unit_id;
2014 @brief Create a JSON_HASH with a single key/value pair.
2015 @param key The key of the key/value pair.
2016 @param value the value of the key/value pair.
2017 @return Pointer to a newly created jsonObject of type JSON_HASH.
2019 The value of the key/value is either a string or (if @a value is NULL) a null.
2021 static jsonObject* single_hash( const char* key, const char* value ) {
2023 if( ! key ) key = "";
2025 jsonObject* hash = jsonNewObjectType( JSON_HASH );
2026 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2031 static int doCreate( osrfMethodContext* ctx ) {
2032 if(osrfMethodVerifyContext( ctx )) {
2033 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2038 timeout_needs_resetting = 1;
2040 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2041 jsonObject* target = NULL;
2042 jsonObject* options = NULL;
2044 if( enforce_pcrud ) {
2045 target = jsonObjectGetIndex( ctx->params, 1 );
2046 options = jsonObjectGetIndex( ctx->params, 2 );
2048 target = jsonObjectGetIndex( ctx->params, 0 );
2049 options = jsonObjectGetIndex( ctx->params, 1 );
2052 if ( !verifyObjectClass( ctx, target )) {
2053 osrfAppRespondComplete( ctx, NULL );
2057 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2059 const char* trans_id = getXactId( ctx );
2061 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2063 osrfAppSessionStatus(
2065 OSRF_STATUS_BADREQUEST,
2066 "osrfMethodException",
2068 "No active transaction -- required for CREATE"
2070 osrfAppRespondComplete( ctx, NULL );
2074 // The following test is harmless but redundant. If a class is
2075 // readonly, we don't register a create method for it.
2076 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2077 osrfAppSessionStatus(
2079 OSRF_STATUS_BADREQUEST,
2080 "osrfMethodException",
2082 "Cannot INSERT readonly class"
2084 osrfAppRespondComplete( ctx, NULL );
2088 // Set the last_xact_id
2089 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2091 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2092 trans_id, target->classname, index);
2093 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
2096 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2098 dbhandle = writehandle;
2100 osrfHash* fields = osrfHashGet(meta, "fields");
2101 char* pkey = osrfHashGet(meta, "primarykey");
2102 char* seq = osrfHashGet(meta, "sequence");
2104 growing_buffer* table_buf = buffer_init(128);
2105 growing_buffer* col_buf = buffer_init(128);
2106 growing_buffer* val_buf = buffer_init(128);
2108 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
2109 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
2110 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2111 buffer_add(val_buf,"VALUES (");
2115 osrfHash* field = NULL;
2116 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2117 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2119 const char* field_name = osrfHashIteratorKey( field_itr );
2121 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2124 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2127 if (field_object && field_object->classname) {
2128 value = oilsFMGetString(
2130 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
2132 } else if( field_object && JSON_BOOL == field_object->type ) {
2133 if( jsonBoolIsTrue( field_object ) )
2134 value = strdup( "t" );
2136 value = strdup( "f" );
2138 value = jsonObjectToSimpleString( field_object );
2144 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2145 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2148 buffer_add(col_buf, field_name);
2150 if (!field_object || field_object->type == JSON_NULL) {
2151 buffer_add( val_buf, "DEFAULT" );
2153 } else if ( !strcmp(get_primitive( field ), "number") ) {
2154 const char* numtype = get_datatype( field );
2155 if ( !strcmp( numtype, "INT8") ) {
2156 buffer_fadd( val_buf, "%lld", atoll(value) );
2158 } else if ( !strcmp( numtype, "INT") ) {
2159 buffer_fadd( val_buf, "%d", atoi(value) );
2161 } else if ( !strcmp( numtype, "NUMERIC") ) {
2162 buffer_fadd( val_buf, "%f", atof(value) );
2165 if ( dbi_conn_quote_string(writehandle, &value) ) {
2166 OSRF_BUFFER_ADD( val_buf, value );
2169 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value);
2170 osrfAppSessionStatus(
2172 OSRF_STATUS_INTERNALSERVERERROR,
2173 "osrfMethodException",
2175 "Error quoting string -- please see the error log for more details"
2178 buffer_free(table_buf);
2179 buffer_free(col_buf);
2180 buffer_free(val_buf);
2181 osrfAppRespondComplete( ctx, NULL );
2190 osrfHashIteratorFree( field_itr );
2192 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2193 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2195 char* table_str = buffer_release(table_buf);
2196 char* col_str = buffer_release(col_buf);
2197 char* val_str = buffer_release(val_buf);
2198 growing_buffer* sql = buffer_init(128);
2199 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2204 char* query = buffer_release(sql);
2206 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query);
2208 jsonObject* obj = NULL;
2211 dbi_result result = dbi_conn_query( writehandle, query );
2213 obj = jsonNewObject(NULL);
2216 "%s ERROR inserting %s object using query [%s]",
2218 osrfHashGet(meta, "fieldmapper"),
2221 osrfAppSessionStatus(
2223 OSRF_STATUS_INTERNALSERVERERROR,
2224 "osrfMethodException",
2226 "INSERT error -- please see the error log for more details"
2231 char* id = oilsFMGetString(target, pkey);
2233 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
2234 growing_buffer* _id = buffer_init(10);
2235 buffer_fadd(_id, "%lld", new_id);
2236 id = buffer_release(_id);
2239 // Find quietness specification, if present
2240 const char* quiet_str = NULL;
2242 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2244 quiet_str = jsonObjectGetString( quiet_obj );
2247 if( str_is_true( quiet_str ) ) { // if quietness is specified
2248 obj = jsonNewObject(id);
2252 // Fetch the row that we just inserted, so that we can return it to the client
2253 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2254 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
2257 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2261 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2263 jsonObjectFree( list );
2264 jsonObjectFree( where_clause );
2271 osrfAppRespondComplete( ctx, obj );
2272 jsonObjectFree( obj );
2277 @brief Implement the retrieve method.
2278 @param ctx Pointer to the method context.
2279 @param err Pointer through which to return an error code.
2280 @return If successful, a pointer to the result to be returned to the client;
2283 From the method's class, fetch a row with a specified value in the primary key. This
2284 method relies on the database design convention that a primary key consists of a single
2288 - authkey (PCRUD only)
2289 - value of the primary key for the desired row, for building the WHERE clause
2290 - a JSON_HASH containing any other SQL clauses: select, join, etc.
2292 Return to client: One row from the query.
2294 static int doRetrieve(osrfMethodContext* ctx ) {
2295 if(osrfMethodVerifyContext( ctx )) {
2296 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2301 timeout_needs_resetting = 1;
2306 if( enforce_pcrud ) {
2311 // Get the class metadata
2312 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2314 // Get the value of the primary key, from a method parameter
2315 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos);
2319 "%s retrieving %s object with primary key value of %s",
2321 osrfHashGet( class_def, "fieldmapper" ),
2322 jsonObjectGetString( id_obj )
2325 // Build a WHERE clause based on the key value
2326 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2329 osrfHashGet( class_def, "primarykey" ), // name of key column
2330 jsonObjectClone( id_obj ) // value of key column
2333 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
2337 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2339 jsonObjectFree( where_clause );
2341 osrfAppRespondComplete( ctx, NULL );
2345 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2346 jsonObjectFree( list );
2348 if( enforce_pcrud ) {
2349 if(!verifyObjectPCRUD(ctx, obj)) {
2350 jsonObjectFree(obj);
2352 growing_buffer* msg = buffer_init(128);
2353 OSRF_BUFFER_ADD( msg, modulename );
2354 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2356 char* m = buffer_release(msg);
2357 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2361 osrfAppRespondComplete( ctx, NULL );
2366 osrfAppRespondComplete( ctx, obj );
2367 jsonObjectFree( obj );
2371 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
2372 growing_buffer* val_buf = buffer_init(32);
2373 const char* numtype = get_datatype( field );
2375 if ( !strncmp( numtype, "INT", 3 ) ) {
2376 if (value->type == JSON_NUMBER)
2377 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2378 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2380 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2383 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
2384 if (value->type == JSON_NUMBER)
2385 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2387 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2391 // Presumably this was really intended ot be a string, so quote it
2392 char* str = jsonObjectToSimpleString( value );
2393 if ( dbi_conn_quote_string(dbhandle, &str) ) {
2394 OSRF_BUFFER_ADD( val_buf, str );
2397 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str);
2399 buffer_free(val_buf);
2404 return buffer_release(val_buf);
2407 static char* searchINPredicate (const char* class_alias, osrfHash* field,
2408 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2409 growing_buffer* sql_buf = buffer_init(32);
2415 osrfHashGet(field, "name")
2419 buffer_add(sql_buf, "IN (");
2420 } else if (!(strcasecmp(op,"not in"))) {
2421 buffer_add(sql_buf, "NOT IN (");
2423 buffer_add(sql_buf, "IN (");
2426 if (node->type == JSON_HASH) {
2427 // subquery predicate
2428 char* subpred = buildQuery( ctx, node, SUBSELECT );
2430 buffer_free( sql_buf );
2434 buffer_add(sql_buf, subpred);
2437 } else if (node->type == JSON_ARRAY) {
2438 // literal value list
2439 int in_item_index = 0;
2440 int in_item_first = 1;
2441 const jsonObject* in_item;
2442 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
2447 buffer_add(sql_buf, ", ");
2450 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2451 osrfLogError( OSRF_LOG_MARK,
2452 "%s: Expected string or number within IN list; found %s",
2453 modulename, json_type( in_item->type ) );
2454 buffer_free(sql_buf);
2458 // Append the literal value -- quoted if not a number
2459 if ( JSON_NUMBER == in_item->type ) {
2460 char* val = jsonNumberToDBString( field, in_item );
2461 OSRF_BUFFER_ADD( sql_buf, val );
2464 } else if ( !strcmp( get_primitive( field ), "number") ) {
2465 char* val = jsonNumberToDBString( field, in_item );
2466 OSRF_BUFFER_ADD( sql_buf, val );
2470 char* key_string = jsonObjectToSimpleString(in_item);
2471 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
2472 OSRF_BUFFER_ADD( sql_buf, key_string );
2475 osrfLogError(OSRF_LOG_MARK,
2476 "%s: Error quoting key string [%s]", modulename, key_string);
2478 buffer_free(sql_buf);
2484 if( in_item_first ) {
2485 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2486 buffer_free( sql_buf );
2490 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2491 modulename, json_type( node->type ) );
2492 buffer_free(sql_buf);
2496 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2498 return buffer_release(sql_buf);
2501 // Receive a JSON_ARRAY representing a function call. The first
2502 // entry in the array is the function name. The rest are parameters.
2503 static char* searchValueTransform( const jsonObject* array ) {
2505 if( array->size < 1 ) {
2506 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2510 // Get the function name
2511 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2512 if( func_item->type != JSON_STRING ) {
2513 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2514 modulename, json_type( func_item->type ) );
2518 growing_buffer* sql_buf = buffer_init(32);
2520 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2521 OSRF_BUFFER_ADD( sql_buf, "( " );
2523 // Get the parameters
2524 int func_item_index = 1; // We already grabbed the zeroth entry
2525 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2527 // Add a separator comma, if we need one
2528 if( func_item_index > 2 )
2529 buffer_add( sql_buf, ", " );
2531 // Add the current parameter
2532 if (func_item->type == JSON_NULL) {
2533 buffer_add( sql_buf, "NULL" );
2535 char* val = jsonObjectToSimpleString(func_item);
2536 if ( dbi_conn_quote_string(dbhandle, &val) ) {
2537 OSRF_BUFFER_ADD( sql_buf, val );
2540 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, val);
2541 buffer_free(sql_buf);
2548 buffer_add( sql_buf, " )" );
2550 return buffer_release(sql_buf);
2553 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
2554 const jsonObject* node, const char* op) {
2556 if( ! is_good_operator( op ) ) {
2557 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2561 char* val = searchValueTransform(node);
2565 growing_buffer* sql_buf = buffer_init(32);
2570 osrfHashGet(field, "name"),
2577 return buffer_release(sql_buf);
2580 // class_alias is a class name or other table alias
2581 // field is a field definition as stored in the IDL
2582 // node comes from the method parameter, and may represent an entry in the SELECT list
2583 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
2584 growing_buffer* sql_buf = buffer_init(32);
2586 const char* field_transform = jsonObjectGetString(
2587 jsonObjectGetKeyConst( node, "transform" ) );
2588 const char* transform_subcolumn = jsonObjectGetString(
2589 jsonObjectGetKeyConst( node, "result_field" ) );
2591 if(transform_subcolumn) {
2592 if( ! is_identifier( transform_subcolumn ) ) {
2593 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2594 modulename, transform_subcolumn );
2595 buffer_free( sql_buf );
2598 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2601 if (field_transform) {
2603 if( ! is_identifier( field_transform ) ) {
2604 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2605 modulename, field_transform );
2606 buffer_free( sql_buf );
2610 if( obj_is_true( jsonObjectGetKeyConst( node, "distinct" ) ) ) {
2611 buffer_fadd( sql_buf, "%s(DISTINCT \"%s\".%s",
2612 field_transform, class_alias, osrfHashGet(field, "name"));
2614 buffer_fadd( sql_buf, "%s(\"%s\".%s",
2615 field_transform, class_alias, osrfHashGet(field, "name"));
2618 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2621 if( array->type != JSON_ARRAY ) {
2622 osrfLogError( OSRF_LOG_MARK,
2623 "%s: Expected JSON_ARRAY for function params; found %s",
2624 modulename, json_type( array->type ) );
2625 buffer_free( sql_buf );
2628 int func_item_index = 0;
2629 jsonObject* func_item;
2630 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2632 char* val = jsonObjectToSimpleString(func_item);
2635 buffer_add( sql_buf, ",NULL" );
2636 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
2637 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2638 OSRF_BUFFER_ADD( sql_buf, val );
2640 osrfLogError( OSRF_LOG_MARK,
2641 "%s: Error quoting key string [%s]", modulename, val);
2643 buffer_free(sql_buf);
2650 buffer_add( sql_buf, " )" );
2653 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2656 if (transform_subcolumn)
2657 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2659 return buffer_release(sql_buf);
2662 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2663 const jsonObject* node, const char* op ) {
2665 if( ! is_good_operator( op ) ) {
2666 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", modulename, op);
2670 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2671 if( ! field_transform )
2674 int extra_parens = 0; // boolean
2676 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2677 if ( ! value_obj ) {
2678 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2680 osrfLogError( OSRF_LOG_MARK, "%s: Error building condition for field transform",
2682 free(field_transform);
2686 } else if ( value_obj->type == JSON_ARRAY ) {
2687 value = searchValueTransform( value_obj );
2689 osrfLogError( OSRF_LOG_MARK,
2690 "%s: Error building value transform for field transform", modulename );
2691 free( field_transform );
2694 } else if ( value_obj->type == JSON_HASH ) {
2695 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2697 osrfLogError( OSRF_LOG_MARK, "%s: Error building predicate for field transform",
2699 free(field_transform);
2703 } else if ( value_obj->type == JSON_NUMBER ) {
2704 value = jsonNumberToDBString( field, value_obj );
2705 } else if ( value_obj->type == JSON_NULL ) {
2706 osrfLogError( OSRF_LOG_MARK,
2707 "%s: Error building predicate for field transform: null value", modulename );
2708 free(field_transform);
2710 } else if ( value_obj->type == JSON_BOOL ) {
2711 osrfLogError( OSRF_LOG_MARK,
2712 "%s: Error building predicate for field transform: boolean value", modulename );
2713 free(field_transform);
2716 if ( !strcmp( get_primitive( field ), "number") ) {
2717 value = jsonNumberToDBString( field, value_obj );
2719 value = jsonObjectToSimpleString( value_obj );
2720 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2721 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2722 modulename, value );
2724 free(field_transform);
2730 const char* left_parens = "";
2731 const char* right_parens = "";
2733 if( extra_parens ) {
2738 growing_buffer* sql_buf = buffer_init(32);
2742 "%s%s %s %s %s %s%s",
2753 free(field_transform);
2755 return buffer_release(sql_buf);
2758 static char* searchSimplePredicate (const char* op, const char* class_alias,
2759 osrfHash* field, const jsonObject* node) {
2761 if( ! is_good_operator( op ) ) {
2762 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", modulename, op );
2768 // Get the value to which we are comparing the specified column
2769 if (node->type != JSON_NULL) {
2770 if ( node->type == JSON_NUMBER ) {
2771 val = jsonNumberToDBString( field, node );
2772 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2773 val = jsonNumberToDBString( field, node );
2775 val = jsonObjectToSimpleString(node);
2780 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2781 // Value is not numeric; enclose it in quotes
2782 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2783 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]",
2790 // Compare to a null value
2791 val = strdup( "NULL" );
2792 if (strcmp( op, "=" ))
2798 growing_buffer* sql_buf = buffer_init(32);
2799 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2800 char* pred = buffer_release( sql_buf );
2807 static char* searchBETWEENPredicate (const char* class_alias,
2808 osrfHash* field, const jsonObject* node) {
2810 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2811 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2813 if( NULL == y_node ) {
2814 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", modulename );
2817 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2818 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", modulename );
2825 if ( !strcmp( get_primitive( field ), "number") ) {
2826 x_string = jsonNumberToDBString(field, x_node);
2827 y_string = jsonNumberToDBString(field, y_node);
2830 x_string = jsonObjectToSimpleString(x_node);
2831 y_string = jsonObjectToSimpleString(y_node);
2832 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2833 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2834 modulename, x_string, y_string );
2841 growing_buffer* sql_buf = buffer_init(32);
2842 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2843 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2847 return buffer_release(sql_buf);
2850 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2851 jsonObject* node, osrfMethodContext* ctx ) {
2854 if (node->type == JSON_ARRAY) { // equality IN search
2855 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2856 } else if (node->type == JSON_HASH) { // other search
2857 jsonIterator* pred_itr = jsonNewIterator( node );
2858 if( !jsonIteratorHasNext( pred_itr ) ) {
2859 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2860 modulename, osrfHashGet(field, "name" ));
2862 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2864 // Verify that there are no additional predicates
2865 if( jsonIteratorHasNext( pred_itr ) ) {
2866 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2867 modulename, osrfHashGet(field, "name" ));
2868 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2869 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2870 else if ( !(strcasecmp( pred_itr->key,"in" ))
2871 || !(strcasecmp( pred_itr->key,"not in" )) )
2872 pred = searchINPredicate(
2873 class_info->alias, field, pred_node, pred_itr->key, ctx );
2874 else if ( pred_node->type == JSON_ARRAY )
2875 pred = searchFunctionPredicate(
2876 class_info->alias, field, pred_node, pred_itr->key );
2877 else if ( pred_node->type == JSON_HASH )
2878 pred = searchFieldTransformPredicate(
2879 class_info, field, pred_node, pred_itr->key );
2881 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2883 jsonIteratorFree(pred_itr);
2885 } else if (node->type == JSON_NULL) { // IS NULL search
2886 growing_buffer* _p = buffer_init(64);
2889 "\"%s\".%s IS NULL",
2890 class_info->class_name,
2891 osrfHashGet(field, "name")
2893 pred = buffer_release(_p);
2894 } else { // equality search
2895 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2914 field : call_number,
2930 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2932 const jsonObject* working_hash;
2933 jsonObject* freeable_hash = NULL;
2935 if (join_hash->type == JSON_HASH) {
2936 working_hash = join_hash;
2937 } else if (join_hash->type == JSON_STRING) {
2938 // turn it into a JSON_HASH by creating a wrapper
2939 // around a copy of the original
2940 const char* _tmp = jsonObjectGetString( join_hash );
2941 freeable_hash = jsonNewObjectType(JSON_HASH);
2942 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2943 working_hash = freeable_hash;
2947 "%s: JOIN failed; expected JSON object type not found",
2953 growing_buffer* join_buf = buffer_init(128);
2954 const char* leftclass = left_info->class_name;
2956 jsonObject* snode = NULL;
2957 jsonIterator* search_itr = jsonNewIterator( working_hash );
2959 while ( (snode = jsonIteratorNext( search_itr )) ) {
2960 const char* right_alias = search_itr->key;
2962 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2964 class = right_alias;
2966 const ClassInfo* right_info = add_joined_class( right_alias, class );
2970 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2974 jsonIteratorFree( search_itr );
2975 buffer_free( join_buf );
2977 jsonObjectFree( freeable_hash );
2980 osrfHash* links = right_info->links;
2981 const char* table = right_info->source_def;
2983 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2984 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2986 if (field && !fkey) {
2987 // Look up the corresponding join column in the IDL.
2988 // The link must be defined in the child table,
2989 // and point to the right parent table.
2990 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
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, leftclass ) )
2997 fkey = 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 // Look up the corresponding join column in the IDL.
3016 // The link must be defined in the child table,
3017 // and point to the right parent table.
3018 osrfHash* left_links = left_info->links;
3019 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
3020 const char* reltype = NULL;
3021 const char* other_class = NULL;
3022 reltype = osrfHashGet( idl_link, "reltype" );
3023 if( reltype && strcmp( reltype, "has_many" ) )
3024 other_class = osrfHashGet( idl_link, "class" );
3025 if( other_class && !strcmp( other_class, class ) )
3026 field = osrfHashGet( idl_link, "key" );
3030 "%s: JOIN failed. No link defined from %s.%s to %s",
3036 buffer_free(join_buf);
3038 jsonObjectFree(freeable_hash);
3039 jsonIteratorFree(search_itr);
3043 } else if (!field && !fkey) {
3044 osrfHash* left_links = left_info->links;
3046 // For each link defined for the left class:
3047 // see if the link references the joined class
3048 osrfHashIterator* itr = osrfNewHashIterator( left_links );
3049 osrfHash* curr_link = NULL;
3050 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3051 const char* other_class = osrfHashGet( curr_link, "class" );
3052 if( other_class && !strcmp( other_class, class ) ) {
3054 // In the IDL, the parent class doesn't always know then names of the child
3055 // columns that are pointing to it, so don't use that end of the link
3056 const char* reltype = osrfHashGet( curr_link, "reltype" );
3057 if( reltype && strcmp( reltype, "has_many" ) ) {
3058 // Found a link between the classes
3059 fkey = osrfHashIteratorKey( itr );
3060 field = osrfHashGet( curr_link, "key" );
3065 osrfHashIteratorFree( itr );
3067 if (!field || !fkey) {
3068 // Do another such search, with the classes reversed
3070 // For each link defined for the joined class:
3071 // see if the link references the left class
3072 osrfHashIterator* itr = osrfNewHashIterator( links );
3073 osrfHash* curr_link = NULL;
3074 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
3075 const char* other_class = osrfHashGet( curr_link, "class" );
3076 if( other_class && !strcmp( other_class, leftclass ) ) {
3078 // In the IDL, the parent class doesn't know then names of the child
3079 // columns that are pointing to it, so don't use that end of the link
3080 const char* reltype = osrfHashGet( curr_link, "reltype" );
3081 if( reltype && strcmp( reltype, "has_many" ) ) {
3082 // Found a link between the classes
3083 field = osrfHashIteratorKey( itr );
3084 fkey = osrfHashGet( curr_link, "key" );
3089 osrfHashIteratorFree( itr );
3092 if (!field || !fkey) {
3095 "%s: JOIN failed. No link defined between %s and %s",
3100 buffer_free(join_buf);
3102 jsonObjectFree(freeable_hash);
3103 jsonIteratorFree(search_itr);
3108 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
3110 if ( !strcasecmp(type,"left") ) {
3111 buffer_add(join_buf, " LEFT JOIN");
3112 } else if ( !strcasecmp(type,"right") ) {
3113 buffer_add(join_buf, " RIGHT JOIN");
3114 } else if ( !strcasecmp(type,"full") ) {
3115 buffer_add(join_buf, " FULL JOIN");
3117 buffer_add(join_buf, " INNER JOIN");
3120 buffer_add(join_buf, " INNER JOIN");
3123 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
3124 table, right_alias, right_alias, field, left_info->alias, fkey);
3126 // Add any other join conditions as specified by "filter"
3127 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
3129 const char* filter_op = jsonObjectGetString(
3130 jsonObjectGetKeyConst( snode, "filter_op" ) );
3131 if ( filter_op && !strcasecmp("or",filter_op) ) {
3132 buffer_add( join_buf, " OR " );
3134 buffer_add( join_buf, " AND " );
3137 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
3139 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3140 OSRF_BUFFER_ADD( join_buf, jpred );
3145 "%s: JOIN failed. Invalid conditional expression.",
3148 jsonIteratorFree( search_itr );
3149 buffer_free( join_buf );
3151 jsonObjectFree( freeable_hash );
3156 buffer_add(join_buf, " ) ");
3158 // Recursively add a nested join, if one is present
3159 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
3161 char* jpred = searchJOIN( join_filter, right_info );
3163 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
3164 OSRF_BUFFER_ADD( join_buf, jpred );
3167 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", modulename );
3168 jsonIteratorFree( search_itr );
3169 buffer_free( join_buf );
3171 jsonObjectFree( freeable_hash );
3178 jsonObjectFree(freeable_hash);
3179 jsonIteratorFree(search_itr);
3181 return buffer_release(join_buf);
3186 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
3187 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
3188 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
3190 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
3192 search_hash is the JSON expression of the conditions.
3193 meta is the class definition from the IDL, for the relevant table.
3194 opjoin_type indicates whether multiple conditions, if present, should be
3195 connected by AND or OR.
3196 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
3197 to pass it to other functions -- and all they do with it is to use the session
3198 and request members to send error messages back to the client.
3202 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
3203 int opjoin_type, osrfMethodContext* ctx ) {
3207 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, "
3208 "opjoin_type = %d, ctx addr = %p",
3211 class_info->class_def,
3216 growing_buffer* sql_buf = buffer_init(128);
3218 jsonObject* node = NULL;
3221 if ( search_hash->type == JSON_ARRAY ) {
3222 osrfLogDebug( OSRF_LOG_MARK,
3223 "%s: In WHERE clause, condition type is JSON_ARRAY", modulename );
3224 if( 0 == search_hash->size ) {
3227 "%s: Invalid predicate structure: empty JSON array",
3230 buffer_free( sql_buf );
3234 unsigned long i = 0;
3235 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
3239 if (opjoin_type == OR_OP_JOIN)
3240 buffer_add(sql_buf, " OR ");
3242 buffer_add(sql_buf, " AND ");
3245 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
3247 buffer_free( sql_buf );
3251 buffer_fadd(sql_buf, "( %s )", subpred);
3255 } else if ( search_hash->type == JSON_HASH ) {
3256 osrfLogDebug( OSRF_LOG_MARK,
3257 "%s: In WHERE clause, condition type is JSON_HASH", modulename );
3258 jsonIterator* search_itr = jsonNewIterator( search_hash );
3259 if( !jsonIteratorHasNext( search_itr ) ) {
3262 "%s: Invalid predicate structure: empty JSON object",
3265 jsonIteratorFree( search_itr );
3266 buffer_free( sql_buf );
3270 while ( (node = jsonIteratorNext( search_itr )) ) {
3275 if (opjoin_type == OR_OP_JOIN)
3276 buffer_add(sql_buf, " OR ");
3278 buffer_add(sql_buf, " AND ");
3281 if ( '+' == search_itr->key[ 0 ] ) {
3283 // This plus sign prefixes a class name or other table alias;
3284 // make sure the table alias is in scope
3285 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
3286 if( ! alias_info ) {
3289 "%s: Invalid table alias \"%s\" in WHERE clause",
3293 jsonIteratorFree( search_itr );
3294 buffer_free( sql_buf );
3298 if ( node->type == JSON_STRING ) {
3299 // It's the name of a column; make sure it belongs to the class
3300 const char* fieldname = jsonObjectGetString( node );
3301 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
3304 "%s: Invalid column name \"%s\" in WHERE clause "
3305 "for table alias \"%s\"",
3310 jsonIteratorFree( search_itr );
3311 buffer_free( sql_buf );
3315 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3317 // It's something more complicated
3318 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3320 jsonIteratorFree( search_itr );
3321 buffer_free( sql_buf );
3325 buffer_fadd(sql_buf, "( %s )", subpred);
3328 } else if ( '-' == search_itr->key[ 0 ] ) {
3329 if ( !strcasecmp("-or",search_itr->key) ) {
3330 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3332 jsonIteratorFree( search_itr );
3333 buffer_free( sql_buf );
3337 buffer_fadd(sql_buf, "( %s )", subpred);
3339 } else if ( !strcasecmp("-and",search_itr->key) ) {
3340 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3342 jsonIteratorFree( search_itr );
3343 buffer_free( sql_buf );
3347 buffer_fadd(sql_buf, "( %s )", subpred);
3349 } else if ( !strcasecmp("-not",search_itr->key) ) {
3350 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3352 jsonIteratorFree( search_itr );
3353 buffer_free( sql_buf );
3357 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
3359 } else if ( !strcasecmp("-exists",search_itr->key) ) {
3360 char* subpred = buildQuery( ctx, node, SUBSELECT );
3362 jsonIteratorFree( search_itr );
3363 buffer_free( sql_buf );
3367 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
3369 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
3370 char* subpred = buildQuery( ctx, node, SUBSELECT );
3372 jsonIteratorFree( search_itr );
3373 buffer_free( sql_buf );
3377 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
3379 } else { // Invalid "minus" operator
3382 "%s: Invalid operator \"%s\" in WHERE clause",
3386 jsonIteratorFree( search_itr );
3387 buffer_free( sql_buf );
3393 const char* class = class_info->class_name;
3394 osrfHash* fields = class_info->fields;
3395 osrfHash* field = osrfHashGet( fields, search_itr->key );
3398 const char* table = class_info->source_def;
3401 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3404 table ? table : "?",
3407 jsonIteratorFree(search_itr);
3408 buffer_free(sql_buf);
3412 char* subpred = searchPredicate( class_info, field, node, ctx );
3414 buffer_free(sql_buf);
3415 jsonIteratorFree(search_itr);
3419 buffer_add( sql_buf, subpred );
3423 jsonIteratorFree(search_itr);
3426 // ERROR ... only hash and array allowed at this level
3427 char* predicate_string = jsonObjectToJSON( search_hash );
3430 "%s: Invalid predicate structure: %s",
3434 buffer_free(sql_buf);
3435 free(predicate_string);
3439 return buffer_release(sql_buf);
3442 /* Build a JSON_ARRAY of field names for a given table alias
3444 static jsonObject* defaultSelectList( const char* table_alias ) {
3449 ClassInfo* class_info = search_all_alias( table_alias );
3450 if( ! class_info ) {
3453 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3460 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3461 osrfHash* field_def = NULL;
3462 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3463 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3464 const char* field_name = osrfHashIteratorKey( field_itr );
3465 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3466 jsonObjectPush( array, jsonNewObject( field_name ) );
3469 osrfHashIteratorFree( field_itr );
3474 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3475 // The jsonObject must be a JSON_HASH with an single entry for "union",
3476 // "intersect", or "except". The data associated with this key must be an
3477 // array of hashes, each hash being a query.
3478 // Also allowed but currently ignored: entries for "order_by" and "alias".
3479 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3481 if( ! combo || combo->type != JSON_HASH )
3482 return NULL; // should be impossible; validated by caller
3484 const jsonObject* query_array = NULL; // array of subordinate queries
3485 const char* op = NULL; // name of operator, e.g. UNION
3486 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3487 int op_count = 0; // for detecting conflicting operators
3488 int excepting = 0; // boolean
3489 int all = 0; // boolean
3490 jsonObject* order_obj = NULL;
3492 // Identify the elements in the hash
3493 jsonIterator* query_itr = jsonNewIterator( combo );
3494 jsonObject* curr_obj = NULL;
3495 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3496 if( ! strcmp( "union", query_itr->key ) ) {
3499 query_array = curr_obj;
3500 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3503 query_array = curr_obj;
3504 } else if( ! strcmp( "except", query_itr->key ) ) {
3508 query_array = curr_obj;
3509 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3512 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3515 order_obj = curr_obj;
3516 } else if( ! strcmp( "alias", query_itr->key ) ) {
3517 if( curr_obj->type != JSON_STRING ) {
3518 jsonIteratorFree( query_itr );
3521 alias = jsonObjectGetString( curr_obj );
3522 } else if( ! strcmp( "all", query_itr->key ) ) {
3523 if( obj_is_true( curr_obj ) )
3527 osrfAppSessionStatus(
3529 OSRF_STATUS_INTERNALSERVERERROR,
3530 "osrfMethodException",
3532 "Malformed query; unexpected entry in query object"
3536 "%s: Unexpected entry for \"%s\" in%squery",
3541 jsonIteratorFree( query_itr );
3545 jsonIteratorFree( query_itr );
3547 // More sanity checks
3548 if( ! query_array ) {
3550 osrfAppSessionStatus(
3552 OSRF_STATUS_INTERNALSERVERERROR,
3553 "osrfMethodException",
3555 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3559 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3562 return NULL; // should be impossible...
3563 } else if( op_count > 1 ) {
3565 osrfAppSessionStatus(
3567 OSRF_STATUS_INTERNALSERVERERROR,
3568 "osrfMethodException",
3570 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3574 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3578 } if( query_array->type != JSON_ARRAY ) {
3580 osrfAppSessionStatus(
3582 OSRF_STATUS_INTERNALSERVERERROR,
3583 "osrfMethodException",
3585 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3589 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3592 json_type( query_array->type )
3595 } if( query_array->size < 2 ) {
3597 osrfAppSessionStatus(
3599 OSRF_STATUS_INTERNALSERVERERROR,
3600 "osrfMethodException",
3602 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3606 "%s:%srequires multiple queries as operands",
3611 } else if( excepting && query_array->size > 2 ) {
3613 osrfAppSessionStatus(
3615 OSRF_STATUS_INTERNALSERVERERROR,
3616 "osrfMethodException",
3618 "EXCEPT operator has too many queries as operands"
3622 "%s:EXCEPT operator has too many queries as operands",
3626 } else if( order_obj && ! alias ) {
3628 osrfAppSessionStatus(
3630 OSRF_STATUS_INTERNALSERVERERROR,
3631 "osrfMethodException",
3633 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3637 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3643 // So far so good. Now build the SQL.
3644 growing_buffer* sql = buffer_init( 256 );
3646 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3647 // Add a layer of parentheses
3648 if( flags & SUBCOMBO )
3649 OSRF_BUFFER_ADD( sql, "( " );
3651 // Traverse the query array. Each entry should be a hash.
3652 int first = 1; // boolean
3654 jsonObject* query = NULL;
3655 while((query = jsonObjectGetIndex( query_array, i++ ) )) {
3656 if( query->type != JSON_HASH ) {
3658 osrfAppSessionStatus(
3660 OSRF_STATUS_INTERNALSERVERERROR,
3661 "osrfMethodException",
3663 "Malformed query under UNION, INTERSECT or EXCEPT"
3667 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3670 json_type( query->type )
3679 OSRF_BUFFER_ADD( sql, op );
3681 OSRF_BUFFER_ADD( sql, "ALL " );
3684 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3688 "%s: Error building query under%s",
3696 OSRF_BUFFER_ADD( sql, query_str );
3699 if( flags & SUBCOMBO )
3700 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3702 if ( !(flags & SUBSELECT) )
3703 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3705 return buffer_release( sql );
3708 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3709 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3710 // or "except" to indicate the type of query.
3711 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3715 osrfAppSessionStatus(
3717 OSRF_STATUS_INTERNALSERVERERROR,
3718 "osrfMethodException",
3720 "Malformed query; no query object"
3722 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", modulename );
3724 } else if( query->type != JSON_HASH ) {
3726 osrfAppSessionStatus(
3728 OSRF_STATUS_INTERNALSERVERERROR,
3729 "osrfMethodException",
3731 "Malformed query object"
3735 "%s: Query object is %s instead of JSON_HASH",
3737 json_type( query->type )
3742 // Determine what kind of query it purports to be, and dispatch accordingly.
3743 if( jsonObjectGetKey( query, "union" ) ||
3744 jsonObjectGetKey( query, "intersect" ) ||
3745 jsonObjectGetKey( query, "except" ) ) {
3746 return doCombo( ctx, query, flags );
3748 // It is presumably a SELECT query
3750 // Push a node onto the stack for the current query. Every level of
3751 // subquery gets its own QueryFrame on the Stack.
3754 // Build an SQL SELECT statement
3757 jsonObjectGetKey( query, "select" ),
3758 jsonObjectGetKey( query, "from" ),
3759 jsonObjectGetKey( query, "where" ),
3760 jsonObjectGetKey( query, "having" ),
3761 jsonObjectGetKey( query, "order_by" ),
3762 jsonObjectGetKey( query, "limit" ),
3763 jsonObjectGetKey( query, "offset" ),
3772 /* method context */ osrfMethodContext* ctx,
3774 /* SELECT */ jsonObject* selhash,
3775 /* FROM */ jsonObject* join_hash,
3776 /* WHERE */ jsonObject* search_hash,
3777 /* HAVING */ jsonObject* having_hash,
3778 /* ORDER BY */ jsonObject* order_hash,
3779 /* LIMIT */ jsonObject* limit,
3780 /* OFFSET */ jsonObject* offset,
3781 /* flags */ int flags
3783 const char* locale = osrf_message_get_last_locale();
3785 // general tmp objects
3786 const jsonObject* tmp_const;
3787 jsonObject* selclass = NULL;
3788 jsonObject* snode = NULL;
3789 jsonObject* onode = NULL;
3791 char* string = NULL;
3792 int from_function = 0;
3797 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3799 // punt if there's no FROM clause
3800 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3803 "%s: FROM clause is missing or empty",
3807 osrfAppSessionStatus(
3809 OSRF_STATUS_INTERNALSERVERERROR,
3810 "osrfMethodException",
3812 "FROM clause is missing or empty in JSON query"
3817 // the core search class
3818 const char* core_class = NULL;
3820 // get the core class -- the only key of the top level FROM clause, or a string
3821 if (join_hash->type == JSON_HASH) {
3822 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3823 snode = jsonIteratorNext( tmp_itr );
3825 // Populate the current QueryFrame with information
3826 // about the core class
3827 if( add_query_core( NULL, tmp_itr->key ) ) {
3829 osrfAppSessionStatus(
3831 OSRF_STATUS_INTERNALSERVERERROR,
3832 "osrfMethodException",
3834 "Unable to look up core class"
3838 core_class = curr_query->core.class_name;
3841 jsonObject* extra = jsonIteratorNext( tmp_itr );
3843 jsonIteratorFree( tmp_itr );
3846 // There shouldn't be more than one entry in join_hash
3850 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3854 osrfAppSessionStatus(
3856 OSRF_STATUS_INTERNALSERVERERROR,
3857 "osrfMethodException",
3859 "Malformed FROM clause in JSON query"
3861 return NULL; // Malformed join_hash; extra entry
3863 } else if (join_hash->type == JSON_ARRAY) {
3864 // We're selecting from a function, not from a table
3866 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
3869 } else if (join_hash->type == JSON_STRING) {
3870 // Populate the current QueryFrame with information
3871 // about the core class
3872 core_class = jsonObjectGetString( join_hash );
3874 if( add_query_core( NULL, core_class ) ) {
3876 osrfAppSessionStatus(
3878 OSRF_STATUS_INTERNALSERVERERROR,
3879 "osrfMethodException",
3881 "Unable to look up core class"
3889 "%s: FROM clause is unexpected JSON type: %s",
3891 json_type( join_hash->type )
3894 osrfAppSessionStatus(
3896 OSRF_STATUS_INTERNALSERVERERROR,
3897 "osrfMethodException",
3899 "Ill-formed FROM clause in JSON query"
3904 // Build the join clause, if any, while filling out the list
3905 // of joined classes in the current QueryFrame.
3906 char* join_clause = NULL;
3907 if( join_hash && ! from_function ) {
3909 join_clause = searchJOIN( join_hash, &curr_query->core );
3910 if( ! join_clause ) {
3912 osrfAppSessionStatus(
3914 OSRF_STATUS_INTERNALSERVERERROR,
3915 "osrfMethodException",
3917 "Unable to construct JOIN clause(s)"
3923 // For in case we don't get a select list
3924 jsonObject* defaultselhash = NULL;
3926 // if there is no select list, build a default select list ...
3927 if (!selhash && !from_function) {
3928 jsonObject* default_list = defaultSelectList( core_class );
3929 if( ! default_list ) {
3931 osrfAppSessionStatus(
3933 OSRF_STATUS_INTERNALSERVERERROR,
3934 "osrfMethodException",
3936 "Unable to build default SELECT clause in JSON query"
3938 free( join_clause );
3943 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3944 jsonObjectSetKey( selhash, core_class, default_list );
3947 // The SELECT clause can be encoded only by a hash
3948 if( !from_function && selhash->type != JSON_HASH ) {
3951 "%s: Expected JSON_HASH for SELECT clause; found %s",
3953 json_type( selhash->type )
3957 osrfAppSessionStatus(
3959 OSRF_STATUS_INTERNALSERVERERROR,
3960 "osrfMethodException",
3962 "Malformed SELECT clause in JSON query"
3964 free( join_clause );
3968 // If you see a null or wild card specifier for the core class, or an
3969 // empty array, replace it with a default SELECT list
3970 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3972 int default_needed = 0; // boolean
3973 if( JSON_STRING == tmp_const->type
3974 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3976 else if( JSON_NULL == tmp_const->type )
3979 if( default_needed ) {
3980 // Build a default SELECT list
3981 jsonObject* default_list = defaultSelectList( core_class );
3982 if( ! default_list ) {
3984 osrfAppSessionStatus(
3986 OSRF_STATUS_INTERNALSERVERERROR,
3987 "osrfMethodException",
3989 "Can't build default SELECT clause in JSON query"
3991 free( join_clause );
3996 jsonObjectSetKey( selhash, core_class, default_list );
4000 // temp buffers for the SELECT list and GROUP BY clause
4001 growing_buffer* select_buf = buffer_init(128);
4002 growing_buffer* group_buf = buffer_init(128);
4004 int aggregate_found = 0; // boolean
4006 // Build a select list
4007 if(from_function) // From a function we select everything
4008 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
4011 // Build the SELECT list as SQL
4015 jsonIterator* selclass_itr = jsonNewIterator( selhash );
4016 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
4018 const char* cname = selclass_itr->key;
4020 // Make sure the target relation is in the FROM clause.
4022 // At this point join_hash is a step down from the join_hash we
4023 // received as a parameter. If the original was a JSON_STRING,
4024 // then json_hash is now NULL. If the original was a JSON_HASH,
4025 // then json_hash is now the first (and only) entry in it,
4026 // denoting the core class. We've already excluded the
4027 // possibility that the original was a JSON_ARRAY, because in
4028 // that case from_function would be non-NULL, and we wouldn't
4031 // If the current table alias isn't in scope, bail out
4032 ClassInfo* class_info = search_alias( cname );
4033 if( ! class_info ) {
4036 "%s: SELECT clause references class not in FROM clause: \"%s\"",
4041 osrfAppSessionStatus(
4043 OSRF_STATUS_INTERNALSERVERERROR,
4044 "osrfMethodException",
4046 "Selected class not in FROM clause in JSON query"
4048 jsonIteratorFree( selclass_itr );
4049 buffer_free( select_buf );
4050 buffer_free( group_buf );
4051 if( defaultselhash )
4052 jsonObjectFree( defaultselhash );
4053 free( join_clause );
4057 if( selclass->type != JSON_ARRAY ) {
4060 "%s: Malformed SELECT list for class \"%s\"; not an array",
4065 osrfAppSessionStatus(
4067 OSRF_STATUS_INTERNALSERVERERROR,
4068 "osrfMethodException",
4070 "Selected class not in FROM clause in JSON query"
4073 jsonIteratorFree( selclass_itr );
4074 buffer_free( select_buf );
4075 buffer_free( group_buf );
4076 if( defaultselhash )
4077 jsonObjectFree( defaultselhash );
4078 free( join_clause );
4082 // Look up some attributes of the current class
4083 osrfHash* idlClass = class_info->class_def;
4084 osrfHash* class_field_set = class_info->fields;
4085 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
4086 const char* class_tname = osrfHashGet( idlClass, "tablename" );
4088 if( 0 == selclass->size ) {
4091 "%s: No columns selected from \"%s\"",
4097 // stitch together the column list for the current table alias...
4098 unsigned long field_idx = 0;
4099 jsonObject* selfield = NULL;
4100 while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
4102 // If we need a separator comma, add one
4106 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
4109 // if the field specification is a string, add it to the list
4110 if (selfield->type == JSON_STRING) {
4112 // Look up the field in the IDL
4113 const char* col_name = jsonObjectGetString( selfield );
4114 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4116 // No such field in current class
4119 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
4125 osrfAppSessionStatus(
4127 OSRF_STATUS_INTERNALSERVERERROR,
4128 "osrfMethodException",
4130 "Selected column not defined in JSON query"
4132 jsonIteratorFree( selclass_itr );
4133 buffer_free( select_buf );
4134 buffer_free( group_buf );
4135 if( defaultselhash )
4136 jsonObjectFree( defaultselhash );
4137 free( join_clause );
4139 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4140 // Virtual field not allowed
4143 "%s: Selected column \"%s\" for class \"%s\" is virtual",
4149 osrfAppSessionStatus(
4151 OSRF_STATUS_INTERNALSERVERERROR,
4152 "osrfMethodException",
4154 "Selected column may not be virtual in JSON query"
4156 jsonIteratorFree( selclass_itr );
4157 buffer_free( select_buf );
4158 buffer_free( group_buf );
4159 if( defaultselhash )
4160 jsonObjectFree( defaultselhash );
4161 free( join_clause );
4167 if (flags & DISABLE_I18N)
4170 i18n = osrfHashGet(field_def, "i18n");
4172 if( str_is_true( i18n ) ) {
4173 buffer_fadd( select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
4174 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
4175 class_tname, cname, col_name, class_pkey,
4176 cname, class_pkey, locale, col_name );
4178 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"",
4179 cname, col_name, col_name );
4182 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"",
4183 cname, col_name, col_name );
4186 // ... but it could be an object, in which case we check for a Field Transform
4187 } else if (selfield->type == JSON_HASH) {
4189 const char* col_name = jsonObjectGetString(
4190 jsonObjectGetKeyConst( selfield, "column" ) );
4192 // Get the field definition from the IDL
4193 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
4195 // No such field in current class
4198 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
4204 osrfAppSessionStatus(
4206 OSRF_STATUS_INTERNALSERVERERROR,
4207 "osrfMethodException",
4209 "Selected column is not defined in JSON query"
4211 jsonIteratorFree( selclass_itr );
4212 buffer_free( select_buf );
4213 buffer_free( group_buf );
4214 if( defaultselhash )
4215 jsonObjectFree( defaultselhash );
4216 free( join_clause );
4218 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4219 // No such field in current class
4222 "%s: Selected column \"%s\" is virtual for class \"%s\"",
4228 osrfAppSessionStatus(
4230 OSRF_STATUS_INTERNALSERVERERROR,
4231 "osrfMethodException",
4233 "Selected column is virtual in JSON query"
4235 jsonIteratorFree( selclass_itr );
4236 buffer_free( select_buf );
4237 buffer_free( group_buf );
4238 if( defaultselhash )
4239 jsonObjectFree( defaultselhash );
4240 free( join_clause );
4244 // Decide what to use as a column alias
4246 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
4247 _alias = jsonObjectGetString( tmp_const );
4248 } else { // Use field name as the alias
4252 if (jsonObjectGetKeyConst( selfield, "transform" )) {
4253 char* transform_str = searchFieldTransform(
4254 class_info->alias, field_def, selfield );
4255 if( transform_str ) {
4256 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
4257 free(transform_str);
4260 osrfAppSessionStatus(
4262 OSRF_STATUS_INTERNALSERVERERROR,
4263 "osrfMethodException",
4265 "Unable to generate transform function in JSON query"
4267 jsonIteratorFree( selclass_itr );
4268 buffer_free( select_buf );
4269 buffer_free( group_buf );
4270 if( defaultselhash )
4271 jsonObjectFree( defaultselhash );
4272 free( join_clause );
4279 if (flags & DISABLE_I18N)
4282 i18n = osrfHashGet(field_def, "i18n");
4284 if( str_is_true( i18n ) ) {
4285 buffer_fadd( select_buf,
4286 " oils_i18n_xlate('%s', '%s', '%s', '%s', "
4287 "\"%s\".%s::TEXT, '%s') AS \"%s\"",
4288 class_tname, cname, col_name, class_pkey, cname,
4289 class_pkey, locale, _alias);
4291 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4292 cname, col_name, _alias );
4295 buffer_fadd( select_buf, " \"%s\".%s AS \"%s\"",
4296 cname, col_name, _alias);
4303 "%s: Selected item is unexpected JSON type: %s",
4305 json_type( selfield->type )
4308 osrfAppSessionStatus(
4310 OSRF_STATUS_INTERNALSERVERERROR,
4311 "osrfMethodException",
4313 "Ill-formed SELECT item in JSON query"
4315 jsonIteratorFree( selclass_itr );
4316 buffer_free( select_buf );
4317 buffer_free( group_buf );
4318 if( defaultselhash )
4319 jsonObjectFree( defaultselhash );
4320 free( join_clause );
4324 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
4325 if( obj_is_true( agg_obj ) )
4326 aggregate_found = 1;
4328 // Append a comma (except for the first one)
4329 // and add the column to a GROUP BY clause
4333 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4335 buffer_fadd(group_buf, " %d", sel_pos);
4339 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4341 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4342 if ( ! obj_is_true( aggregate_obj ) ) {
4346 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4349 buffer_fadd(group_buf, " %d", sel_pos);
4352 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4356 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4359 _column = searchFieldTransform(class_info->alias, field, selfield);
4360 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4361 OSRF_BUFFER_ADD(group_buf, _column);
4362 _column = searchFieldTransform(class_info->alias, field, selfield);
4369 } // end while -- iterating across SELECT columns
4371 } // end while -- iterating across classes
4373 jsonIteratorFree(selclass_itr);
4377 char* col_list = buffer_release(select_buf);
4379 // Make sure the SELECT list isn't empty. This can happen, for example,
4380 // if we try to build a default SELECT clause from a non-core table.
4383 osrfLogError( OSRF_LOG_MARK, "%s: SELECT clause is empty", modulename );
4385 osrfAppSessionStatus(
4387 OSRF_STATUS_INTERNALSERVERERROR,
4388 "osrfMethodException",
4390 "SELECT list is empty"
4393 buffer_free( group_buf );
4394 if( defaultselhash )
4395 jsonObjectFree( defaultselhash );
4396 free( join_clause );
4401 if (from_function) table = searchValueTransform(join_hash);
4402 else table = strdup( curr_query->core.source_def );
4406 osrfAppSessionStatus(
4408 OSRF_STATUS_INTERNALSERVERERROR,
4409 "osrfMethodException",
4411 "Unable to identify table for core class"
4414 buffer_free( group_buf );
4415 if( defaultselhash )
4416 jsonObjectFree( defaultselhash );
4417 free( join_clause );
4421 // Put it all together
4422 growing_buffer* sql_buf = buffer_init(128);
4423 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4427 // Append the join clause, if any
4429 buffer_add(sql_buf, join_clause);
4433 char* order_by_list = NULL;
4434 char* having_buf = NULL;
4436 if (!from_function) {
4438 // Build a WHERE clause, if there is one
4439 if ( search_hash ) {
4440 buffer_add(sql_buf, " WHERE ");
4442 // and it's on the WHERE clause
4443 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4446 osrfAppSessionStatus(
4448 OSRF_STATUS_INTERNALSERVERERROR,
4449 "osrfMethodException",
4451 "Severe query error in WHERE predicate -- see error log for more details"
4454 buffer_free(group_buf);
4455 buffer_free(sql_buf);
4457 jsonObjectFree(defaultselhash);
4461 buffer_add(sql_buf, pred);
4465 // Build a HAVING clause, if there is one
4466 if ( having_hash ) {
4468 // and it's on the the WHERE clause
4469 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4471 if( ! having_buf ) {
4473 osrfAppSessionStatus(
4475 OSRF_STATUS_INTERNALSERVERERROR,
4476 "osrfMethodException",
4478 "Severe query error in HAVING predicate -- see error log for more details"
4481 buffer_free(group_buf);
4482 buffer_free(sql_buf);
4484 jsonObjectFree(defaultselhash);
4489 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4491 // Build an ORDER BY clause, if there is one
4492 if( NULL == order_hash )
4493 ; // No ORDER BY? do nothing
4494 else if( JSON_ARRAY == order_hash->type ) {
4495 // Array of field specifications, each specification being a
4496 // hash to define the class, field, and other details
4498 jsonObject* order_spec;
4499 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4501 if( JSON_HASH != order_spec->type ) {
4502 osrfLogError(OSRF_LOG_MARK,
4503 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4504 modulename, json_type( order_spec->type ) );
4506 osrfAppSessionStatus(
4508 OSRF_STATUS_INTERNALSERVERERROR,
4509 "osrfMethodException",
4511 "Malformed ORDER BY clause -- see error log for more details"
4513 buffer_free( order_buf );
4515 buffer_free(group_buf);
4516 buffer_free(sql_buf);
4518 jsonObjectFree(defaultselhash);
4522 const char* class_alias =
4523 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4525 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4528 OSRF_BUFFER_ADD(order_buf, ", ");
4530 order_buf = buffer_init(128);
4532 if( !field || !class_alias ) {
4533 osrfLogError( OSRF_LOG_MARK,
4534 "%s: Missing class or field name in field specification "
4535 "of ORDER BY clause",
4538 osrfAppSessionStatus(
4540 OSRF_STATUS_INTERNALSERVERERROR,
4541 "osrfMethodException",
4543 "Malformed ORDER BY clause -- see error log for more details"
4545 buffer_free( order_buf );
4547 buffer_free(group_buf);
4548 buffer_free(sql_buf);
4550 jsonObjectFree(defaultselhash);
4554 ClassInfo* order_class_info = search_alias( class_alias );
4555 if( ! order_class_info ) {
4556 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4557 "not in FROM clause", modulename, class_alias );
4559 osrfAppSessionStatus(
4561 OSRF_STATUS_INTERNALSERVERERROR,
4562 "osrfMethodException",
4564 "Invalid class referenced in ORDER BY clause -- "
4565 "see error log for more details"
4568 buffer_free(group_buf);
4569 buffer_free(sql_buf);
4571 jsonObjectFree(defaultselhash);
4575 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4577 osrfLogError( OSRF_LOG_MARK,
4578 "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4579 modulename, class_alias, field );
4581 osrfAppSessionStatus(
4583 OSRF_STATUS_INTERNALSERVERERROR,
4584 "osrfMethodException",
4586 "Invalid field referenced in ORDER BY clause -- "
4587 "see error log for more details"
4590 buffer_free(group_buf);
4591 buffer_free(sql_buf);
4593 jsonObjectFree(defaultselhash);
4595 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4596 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4597 modulename, field );
4599 osrfAppSessionStatus(
4601 OSRF_STATUS_INTERNALSERVERERROR,
4602 "osrfMethodException",
4604 "Virtual field in ORDER BY clause -- see error log for more details"
4606 buffer_free( order_buf );
4608 buffer_free(group_buf);
4609 buffer_free(sql_buf);
4611 jsonObjectFree(defaultselhash);
4615 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4616 char* transform_str = searchFieldTransform(
4617 class_alias, field_def, order_spec );
4618 if( ! transform_str ) {
4620 osrfAppSessionStatus(
4622 OSRF_STATUS_INTERNALSERVERERROR,
4623 "osrfMethodException",
4625 "Severe query error in ORDER BY clause -- "
4626 "see error log for more details"
4628 buffer_free( order_buf );
4630 buffer_free(group_buf);
4631 buffer_free(sql_buf);
4633 jsonObjectFree(defaultselhash);
4637 OSRF_BUFFER_ADD( order_buf, transform_str );
4638 free( transform_str );
4641 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4643 const char* direction =
4644 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4646 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4647 OSRF_BUFFER_ADD( order_buf, " DESC" );
4649 OSRF_BUFFER_ADD( order_buf, " ASC" );
4652 } else if( JSON_HASH == order_hash->type ) {
4653 // This hash is keyed on class alias. Each class has either
4654 // an array of field names or a hash keyed on field name.
4655 jsonIterator* class_itr = jsonNewIterator( order_hash );
4656 while ( (snode = jsonIteratorNext( class_itr )) ) {
4658 ClassInfo* order_class_info = search_alias( class_itr->key );
4659 if( ! order_class_info ) {
4660 osrfLogError(OSRF_LOG_MARK,
4661 "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4662 modulename, class_itr->key );
4664 osrfAppSessionStatus(
4666 OSRF_STATUS_INTERNALSERVERERROR,
4667 "osrfMethodException",
4669 "Invalid class referenced in ORDER BY clause -- "
4670 "see error log for more details"
4672 jsonIteratorFree( class_itr );
4673 buffer_free( order_buf );
4675 buffer_free(group_buf);
4676 buffer_free(sql_buf);
4678 jsonObjectFree(defaultselhash);
4682 osrfHash* field_list_def = order_class_info->fields;
4684 if ( snode->type == JSON_HASH ) {
4686 // Hash is keyed on field names from the current class. For each field
4687 // there is another layer of hash to define the sorting details, if any,
4688 // or a string to indicate direction of sorting.
4689 jsonIterator* order_itr = jsonNewIterator( snode );
4690 while ( (onode = jsonIteratorNext( order_itr )) ) {
4692 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4694 osrfLogError( OSRF_LOG_MARK,
4695 "%s: Invalid field \"%s\" in ORDER BY clause",
4696 modulename, order_itr->key );
4698 osrfAppSessionStatus(
4700 OSRF_STATUS_INTERNALSERVERERROR,
4701 "osrfMethodException",
4703 "Invalid field in ORDER BY clause -- "
4704 "see error log for more details"
4706 jsonIteratorFree( order_itr );
4707 jsonIteratorFree( class_itr );
4708 buffer_free( order_buf );
4710 buffer_free(group_buf);
4711 buffer_free(sql_buf);
4713 jsonObjectFree(defaultselhash);
4715 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4716 osrfLogError( OSRF_LOG_MARK,
4717 "%s: Virtual field \"%s\" in ORDER BY clause",
4718 modulename, order_itr->key );
4720 osrfAppSessionStatus(
4722 OSRF_STATUS_INTERNALSERVERERROR,
4723 "osrfMethodException",
4725 "Virtual field in ORDER BY clause -- "
4726 "see error log for more details"
4728 jsonIteratorFree( order_itr );
4729 jsonIteratorFree( class_itr );
4730 buffer_free( order_buf );
4732 buffer_free(group_buf);
4733 buffer_free(sql_buf);
4735 jsonObjectFree(defaultselhash);
4739 const char* direction = NULL;
4740 if ( onode->type == JSON_HASH ) {
4741 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4742 string = searchFieldTransform(
4744 osrfHashGet( field_list_def, order_itr->key ),
4748 if( ctx ) osrfAppSessionStatus(
4750 OSRF_STATUS_INTERNALSERVERERROR,
4751 "osrfMethodException",
4753 "Severe query error in ORDER BY clause -- "
4754 "see error log for more details"
4756 jsonIteratorFree( order_itr );
4757 jsonIteratorFree( class_itr );
4759 buffer_free(group_buf);
4760 buffer_free(order_buf);
4761 buffer_free(sql_buf);
4763 jsonObjectFree(defaultselhash);
4767 growing_buffer* field_buf = buffer_init(16);
4768 buffer_fadd( field_buf, "\"%s\".%s",
4769 class_itr->key, order_itr->key );
4770 string = buffer_release(field_buf);
4773 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4774 const char* dir = jsonObjectGetString(tmp_const);
4775 if (!strncasecmp(dir, "d", 1)) {
4776 direction = " DESC";
4782 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4783 osrfLogError( OSRF_LOG_MARK,
4784 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4785 modulename, json_type( onode->type ) );
4787 osrfAppSessionStatus(
4789 OSRF_STATUS_INTERNALSERVERERROR,
4790 "osrfMethodException",
4792 "Malformed ORDER BY clause -- see error log for more details"
4794 jsonIteratorFree( order_itr );
4795 jsonIteratorFree( class_itr );
4797 buffer_free(group_buf);
4798 buffer_free(order_buf);
4799 buffer_free(sql_buf);
4801 jsonObjectFree(defaultselhash);
4805 string = strdup(order_itr->key);
4806 const char* dir = jsonObjectGetString(onode);
4807 if (!strncasecmp(dir, "d", 1)) {
4808 direction = " DESC";
4815 OSRF_BUFFER_ADD(order_buf, ", ");
4817 order_buf = buffer_init(128);
4819 OSRF_BUFFER_ADD(order_buf, string);
4823 OSRF_BUFFER_ADD(order_buf, direction);
4827 jsonIteratorFree(order_itr);
4829 } else if ( snode->type == JSON_ARRAY ) {
4831 // Array is a list of fields from the current class
4832 unsigned long order_idx = 0;
4833 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4835 const char* _f = jsonObjectGetString( onode );
4837 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4839 osrfLogError( OSRF_LOG_MARK,
4840 "%s: Invalid field \"%s\" in ORDER BY clause",
4843 osrfAppSessionStatus(
4845 OSRF_STATUS_INTERNALSERVERERROR,
4846 "osrfMethodException",
4848 "Invalid field in ORDER BY clause -- "
4849 "see error log for more details"
4851 jsonIteratorFree( class_itr );
4852 buffer_free( order_buf );
4854 buffer_free(group_buf);
4855 buffer_free(sql_buf);
4857 jsonObjectFree(defaultselhash);
4859 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4860 osrfLogError( OSRF_LOG_MARK,
4861 "%s: Virtual field \"%s\" in ORDER BY clause",
4864 osrfAppSessionStatus(
4866 OSRF_STATUS_INTERNALSERVERERROR,
4867 "osrfMethodException",
4869 "Virtual field in ORDER BY clause -- "
4870 "see error log for more details"
4872 jsonIteratorFree( class_itr );
4873 buffer_free( order_buf );
4875 buffer_free(group_buf);
4876 buffer_free(sql_buf);
4878 jsonObjectFree(defaultselhash);
4883 OSRF_BUFFER_ADD(order_buf, ", ");
4885 order_buf = buffer_init(128);
4887 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
4891 // IT'S THE OOOOOOOOOOOLD STYLE!
4893 osrfLogError( OSRF_LOG_MARK,
4894 "%s: Possible SQL injection attempt; direct order by is not allowed",
4897 osrfAppSessionStatus(
4899 OSRF_STATUS_INTERNALSERVERERROR,
4900 "osrfMethodException",
4902 "Severe query error -- see error log for more details"
4907 buffer_free(group_buf);
4908 buffer_free(order_buf);
4909 buffer_free(sql_buf);
4911 jsonObjectFree(defaultselhash);
4912 jsonIteratorFree(class_itr);
4916 jsonIteratorFree( class_itr );
4918 osrfLogError(OSRF_LOG_MARK,
4919 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4920 modulename, json_type( order_hash->type ) );
4922 osrfAppSessionStatus(
4924 OSRF_STATUS_INTERNALSERVERERROR,
4925 "osrfMethodException",
4927 "Malformed ORDER BY clause -- see error log for more details"
4929 buffer_free( order_buf );
4931 buffer_free(group_buf);
4932 buffer_free(sql_buf);
4934 jsonObjectFree(defaultselhash);
4939 order_by_list = buffer_release( order_buf );
4943 string = buffer_release(group_buf);
4945 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4946 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4947 OSRF_BUFFER_ADD( sql_buf, string );
4952 if( having_buf && *having_buf ) {
4953 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4954 OSRF_BUFFER_ADD( sql_buf, having_buf );
4958 if( order_by_list ) {
4960 if ( *order_by_list ) {
4961 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4962 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4965 free( order_by_list );
4969 const char* str = jsonObjectGetString(limit);
4970 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
4974 const char* str = jsonObjectGetString(offset);
4975 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
4978 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4981 jsonObjectFree(defaultselhash);
4983 return buffer_release(sql_buf);
4985 } // end of SELECT()
4987 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4989 const char* locale = osrf_message_get_last_locale();
4991 osrfHash* fields = osrfHashGet(meta, "fields");
4992 char* core_class = osrfHashGet(meta, "classname");
4994 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4996 jsonObject* node = NULL;
4997 jsonObject* snode = NULL;
4998 jsonObject* onode = NULL;
4999 const jsonObject* _tmp = NULL;
5000 jsonObject* selhash = NULL;
5001 jsonObject* defaultselhash = NULL;
5003 growing_buffer* sql_buf = buffer_init(128);
5004 growing_buffer* select_buf = buffer_init(128);
5006 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
5007 defaultselhash = jsonNewObjectType(JSON_HASH);
5008 selhash = defaultselhash;
5011 // If there's no SELECT list for the core class, build one
5012 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
5013 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
5015 // Add every non-virtual field to the field list
5016 osrfHash* field_def = NULL;
5017 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5018 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
5019 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
5020 const char* field = osrfHashIteratorKey( field_itr );
5021 jsonObjectPush( field_list, jsonNewObject( field ) );
5024 osrfHashIteratorFree( field_itr );
5025 jsonObjectSetKey( selhash, core_class, field_list );
5029 jsonIterator* class_itr = jsonNewIterator( selhash );
5030 while ( (snode = jsonIteratorNext( class_itr )) ) {
5032 const char* cname = class_itr->key;
5033 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
5037 if (strcmp(core_class,class_itr->key)) {
5041 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
5043 jsonObjectFree(found);
5047 jsonObjectFree(found);
5050 jsonIterator* select_itr = jsonNewIterator( snode );
5051 while ( (node = jsonIteratorNext( select_itr )) ) {
5052 const char* item_str = jsonObjectGetString( node );
5053 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
5054 char* fname = osrfHashGet(field, "name");
5062 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
5067 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
5068 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
5071 i18n = osrfHashGet(field, "i18n");
5073 if( str_is_true( i18n ) ) {
5074 char* pkey = osrfHashGet(idlClass, "primarykey");
5075 char* tname = osrfHashGet(idlClass, "tablename");
5077 buffer_fadd(select_buf, " oils_i18n_xlate('%s', '%s', '%s', "
5078 "'%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
5079 tname, cname, fname, pkey, cname, pkey, locale, fname);
5081 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
5084 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
5088 jsonIteratorFree(select_itr);
5091 jsonIteratorFree(class_itr);
5093 char* col_list = buffer_release(select_buf);
5094 char* table = getRelation(meta);
5096 table = strdup( "(null)" );
5098 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
5102 // Clear the query stack (as a fail-safe precaution against possible
5103 // leftover garbage); then push the first query frame onto the stack.
5104 clear_query_stack();
5106 if( add_query_core( NULL, core_class ) ) {
5108 osrfAppSessionStatus(
5110 OSRF_STATUS_INTERNALSERVERERROR,
5111 "osrfMethodException",
5113 "Unable to build query frame for core class"
5119 char* join_clause = searchJOIN( join_hash, &curr_query->core );
5120 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
5121 OSRF_BUFFER_ADD(sql_buf, join_clause);
5125 osrfLogDebug( OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
5126 modulename, OSRF_BUFFER_C_STR( sql_buf ));
5128 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
5130 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
5132 osrfAppSessionStatus(
5134 OSRF_STATUS_INTERNALSERVERERROR,
5135 "osrfMethodException",
5137 "Severe query error -- see error log for more details"
5139 buffer_free(sql_buf);
5141 jsonObjectFree(defaultselhash);
5142 clear_query_stack();
5145 buffer_add(sql_buf, pred);
5150 char* string = NULL;
5151 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
5153 growing_buffer* order_buf = buffer_init(128);
5156 jsonIterator* class_itr = jsonNewIterator( _tmp );
5157 while ( (snode = jsonIteratorNext( class_itr )) ) {
5159 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
5162 if ( snode->type == JSON_HASH ) {
5164 jsonIterator* order_itr = jsonNewIterator( snode );
5165 while ( (onode = jsonIteratorNext( order_itr )) ) {
5167 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
5168 class_itr->key, order_itr->key );
5172 char* direction = NULL;
5173 if ( onode->type == JSON_HASH ) {
5174 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
5175 string = searchFieldTransform( class_itr->key, field_def, onode );
5177 osrfAppSessionStatus(
5179 OSRF_STATUS_INTERNALSERVERERROR,
5180 "osrfMethodException",
5182 "Severe query error in ORDER BY clause -- "
5183 "see error log for more details"
5185 jsonIteratorFree( order_itr );
5186 jsonIteratorFree( class_itr );
5187 buffer_free( order_buf );
5188 buffer_free( sql_buf );
5189 if( defaultselhash )
5190 jsonObjectFree( defaultselhash );
5191 clear_query_stack();
5195 growing_buffer* field_buf = buffer_init(16);
5196 buffer_fadd( field_buf, "\"%s\".%s",
5197 class_itr->key, order_itr->key );
5198 string = buffer_release(field_buf);
5201 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
5202 const char* dir = jsonObjectGetString(_tmp);
5203 if (!strncasecmp(dir, "d", 1)) {
5204 direction = " DESC";
5210 string = strdup(order_itr->key);
5211 const char* dir = jsonObjectGetString(onode);
5212 if (!strncasecmp(dir, "d", 1)) {
5213 direction = " DESC";
5222 buffer_add(order_buf, ", ");
5225 buffer_add(order_buf, string);
5229 buffer_add(order_buf, direction);
5233 jsonIteratorFree(order_itr);
5236 const char* str = jsonObjectGetString(snode);
5237 buffer_add(order_buf, str);
5243 jsonIteratorFree(class_itr);
5245 string = buffer_release(order_buf);
5248 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
5249 OSRF_BUFFER_ADD( sql_buf, string );
5255 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
5256 const char* str = jsonObjectGetString(_tmp);
5264 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
5266 const char* str = jsonObjectGetString(_tmp);
5276 jsonObjectFree(defaultselhash);
5277 clear_query_stack();
5279 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
5280 return buffer_release(sql_buf);
5283 int doJSONSearch ( osrfMethodContext* ctx ) {
5284 if(osrfMethodVerifyContext( ctx )) {
5285 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5289 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
5294 dbhandle = writehandle;
5296 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
5300 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
5301 flags |= SELECT_DISTINCT;
5303 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
5304 flags |= DISABLE_I18N;
5306 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
5307 clear_query_stack(); // a possibly needless precaution
5308 char* sql = buildQuery( ctx, hash, flags );
5309 clear_query_stack();
5316 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", modulename, sql);
5317 dbi_result result = dbi_conn_query(dbhandle, sql);
5320 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
5322 if (dbi_result_first_row(result)) {
5323 /* JSONify the result */
5324 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
5327 jsonObject* return_val = oilsMakeJSONFromResult( result );
5328 osrfAppRespond( ctx, return_val );
5329 jsonObjectFree( return_val );
5330 } while (dbi_result_next_row(result));
5333 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", modulename, sql);
5336 osrfAppRespondComplete( ctx, NULL );
5338 /* clean up the query */
5339 dbi_result_free(result);
5343 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", modulename, sql);
5344 osrfAppSessionStatus(
5346 OSRF_STATUS_INTERNALSERVERERROR,
5347 "osrfMethodException",
5349 "Severe query error -- see error log for more details"
5357 // The last parameter, err, is used to report an error condition by updating an int owned by
5358 // the calling code.
5360 // In case of an error, we set *err to -1. If there is no error, *err is left unchanged.
5361 // It is the responsibility of the calling code to initialize *err before the
5362 // call, so that it will be able to make sense of the result.
5364 // Note also that we return NULL if and only if we set *err to -1. So the err parameter is
5365 // redundant anyway.
5366 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
5367 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
5370 dbhandle = writehandle;
5372 char* core_class = osrfHashGet( class_meta, "classname" );
5373 char* pkey = osrfHashGet( class_meta, "primarykey" );
5375 const jsonObject* _tmp;
5377 char* sql = buildSELECT( where_hash, query_hash, class_meta, ctx );
5379 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
5384 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", modulename, sql);
5386 dbi_result result = dbi_conn_query(dbhandle, sql);
5387 if( NULL == result ) {
5388 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
5389 modulename, osrfHashGet( class_meta, "fieldmapper" ), sql);
5390 osrfAppSessionStatus(
5392 OSRF_STATUS_INTERNALSERVERERROR,
5393 "osrfMethodException",
5395 "Severe query error -- see error log for more details"
5402 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
5405 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
5406 jsonObject* row_obj = NULL;
5408 if (dbi_result_first_row(result)) {
5410 // Convert each row to a JSON_ARRAY of column values, and enclose those objects
5411 // in a JSON_ARRAY of rows. If two or more rows have the same key value, then
5412 // eliminate the duplicates.
5413 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
5414 osrfHash* dedup = osrfNewHash();
5416 row_obj = oilsMakeFieldmapperFromResult( result, class_meta );
5417 char* pkey_val = oilsFMGetString( row_obj, pkey );
5418 if ( osrfHashGet( dedup, pkey_val ) ) {
5419 jsonObjectFree( row_obj );
5422 osrfHashSet( dedup, pkey_val, pkey_val );
5423 jsonObjectPush( res_list, row_obj );
5425 } while (dbi_result_next_row(result));
5426 osrfHashFree(dedup);
5429 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
5433 /* clean up the query */
5434 dbi_result_free(result);
5437 // If we're asked to flesh, and there's anything to flesh, then flesh.
5438 if (res_list->size && query_hash) {
5439 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5441 // Get the flesh depth
5442 int flesh_depth = (int) jsonObjectGetNumber( _tmp );
5443 if ( flesh_depth == -1 || flesh_depth > max_flesh_depth )
5444 flesh_depth = max_flesh_depth;
5446 // We need a non-zero flesh depth, and a list of fields to flesh
5447 const jsonObject* temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" );
5448 if ( temp_blob && flesh_depth > 0 ) {
5450 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5451 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5453 osrfStringArray* link_fields = NULL;
5454 osrfHash* links = osrfHashGet( class_meta, "links" );
5456 // Make an osrfStringArray of the names of fields to be fleshed
5458 if (flesh_fields->size == 1) {
5459 const char* _t = jsonObjectGetString(
5460 jsonObjectGetIndex( flesh_fields, 0 ) );
5461 if (!strcmp(_t,"*"))
5462 link_fields = osrfHashKeys( links );
5467 link_fields = osrfNewStringArray(1);
5468 jsonIterator* _i = jsonNewIterator( flesh_fields );
5469 while ((_f = jsonIteratorNext( _i ))) {
5470 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5472 jsonIteratorFree(_i);
5476 osrfHash* fields = osrfHashGet( class_meta, "fields" );
5478 // Iterate over the JSON_ARRAY of rows
5480 unsigned long res_idx = 0;
5481 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5484 const char* link_field;
5486 // Iterate over the list of fleshable fields
5487 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5489 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
5491 osrfHash* kid_link = osrfHashGet(links, link_field);
5493 continue; // Not a link field; skip it
5495 osrfHash* field = osrfHashGet(fields, link_field);
5497 continue; // Not a field at all; skip it (IDL is ill-formed)
5499 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
5501 continue; // The class it links to doesn't exist; skip it
5503 const char* reltype = osrfHashGet( kid_link, "reltype" );
5505 continue; // No reltype; skip it (IDL is ill-formed)
5507 osrfHash* value_field = field;
5509 if ( !strcmp( reltype, "has_many" )
5510 || !strcmp( reltype, "might_have" ) ) { // has_many or might_have
5511 value_field = osrfHashGet(
5512 fields, osrfHashGet( class_meta, "primarykey" ) );
5515 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5517 if (link_map->size > 0) {
5518 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
5521 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5526 osrfHashGet(kid_link, "class"),
5533 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5534 osrfHashGet(kid_link, "field"),
5535 osrfHashGet(kid_link, "class"),
5536 osrfHashGet(kid_link, "key"),
5537 osrfHashGet(kid_link, "reltype")
5540 const char* search_key = jsonObjectGetString(
5541 jsonObjectGetIndex( cur,
5542 atoi( osrfHashGet(value_field, "array_position") )
5547 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
5551 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
5553 // construct WHERE clause
5554 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
5557 osrfHashGet(kid_link, "key"),
5558 jsonNewObject( search_key )
5561 // construct the rest of the query, mostly
5562 // by copying pieces of the previous level of query
5563 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
5564 jsonObjectSetKey( rest_of_query, "flesh",
5565 jsonNewNumberObject( flesh_depth - 1 + link_map->size )
5569 jsonObjectSetKey( rest_of_query, "flesh_fields",
5570 jsonObjectClone(flesh_blob) );
5572 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
5573 jsonObjectSetKey( rest_of_query, "order_by",
5574 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
5578 if (jsonObjectGetKeyConst(query_hash, "select")) {
5579 jsonObjectSetKey( rest_of_query, "select",
5580 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
5584 // do the query, recursively, to expand the fleshable field
5585 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5586 where_clause, rest_of_query, err);
5588 jsonObjectFree( where_clause );
5589 jsonObjectFree( rest_of_query );
5592 osrfStringArrayFree(link_fields);
5593 jsonObjectFree(res_list);
5594 jsonObjectFree(flesh_blob);
5598 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects",
5599 osrfHashGet(kid_link, "class"), kids->size);
5601 // Traverse the result set
5602 jsonObject* X = NULL;
5603 if ( link_map->size > 0 && kids->size > 0 ) {
5605 kids = jsonNewObjectType(JSON_ARRAY);
5607 jsonObject* _k_node;
5608 unsigned long res_idx = 0;
5609 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5615 (unsigned long)atoi(
5621 osrfHashGet(kid_link, "class")
5625 osrfStringArrayGetString( link_map, 0 )
5633 } // end while loop traversing X
5636 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" ))
5637 || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
5638 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s",
5639 osrfHashGet(kid_link, "field"));
5642 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5643 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
5647 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) {
5649 osrfLogDebug( OSRF_LOG_MARK, "Storing fleshed objects in %s",
5650 osrfHashGet( kid_link, "field" ) );
5653 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5654 jsonObjectClone( kids )
5659 jsonObjectFree(kids);
5663 jsonObjectFree( kids );
5665 osrfLogDebug( OSRF_LOG_MARK, "Fleshing of %s complete",
5666 osrfHashGet( kid_link, "field" ) );
5667 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
5669 } // end while loop traversing list of fleshable fields
5670 } // end while loop traversing res_list
5671 jsonObjectFree( flesh_blob );
5672 osrfStringArrayFree(link_fields);
5681 static int doUpdate(osrfMethodContext* ctx ) {
5682 if(osrfMethodVerifyContext( ctx )) {
5683 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5688 timeout_needs_resetting = 1;
5690 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5692 jsonObject* target = NULL;
5694 target = jsonObjectGetIndex( ctx->params, 1 );
5696 target = jsonObjectGetIndex( ctx->params, 0 );
5698 if (!verifyObjectClass(ctx, target)) {
5699 osrfAppRespondComplete( ctx, NULL );
5703 if( getXactId( ctx ) == NULL ) {
5704 osrfAppSessionStatus(
5706 OSRF_STATUS_BADREQUEST,
5707 "osrfMethodException",
5709 "No active transaction -- required for UPDATE"
5711 osrfAppRespondComplete( ctx, NULL );
5715 // The following test is harmless but redundant. If a class is
5716 // readonly, we don't register an update method for it.
5717 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5718 osrfAppSessionStatus(
5720 OSRF_STATUS_BADREQUEST,
5721 "osrfMethodException",
5723 "Cannot UPDATE readonly class"
5725 osrfAppRespondComplete( ctx, NULL );
5729 dbhandle = writehandle;
5730 const char* trans_id = getXactId( ctx );
5732 // Set the last_xact_id
5733 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5735 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5736 trans_id, target->classname, index);
5737 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
5740 char* pkey = osrfHashGet(meta, "primarykey");
5741 osrfHash* fields = osrfHashGet(meta, "fields");
5743 char* id = oilsFMGetString( target, pkey );
5747 "%s updating %s object with %s = %s",
5749 osrfHashGet(meta, "fieldmapper"),
5754 growing_buffer* sql = buffer_init(128);
5755 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
5758 osrfHash* field_def = NULL;
5759 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5760 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
5762 // Skip virtual fields, and the primary key
5763 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5766 const char* field_name = osrfHashIteratorKey( field_itr );
5767 if( ! strcmp( field_name, pkey ) )
5770 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5772 int value_is_numeric = 0; // boolean
5774 if (field_object && field_object->classname) {
5775 value = oilsFMGetString(
5777 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
5779 } else if( field_object && JSON_BOOL == field_object->type ) {
5780 if( jsonBoolIsTrue( field_object ) )
5781 value = strdup( "t" );
5783 value = strdup( "f" );
5785 value = jsonObjectToSimpleString( field_object );
5786 if( field_object && JSON_NUMBER == field_object->type )
5787 value_is_numeric = 1;
5790 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5791 osrfHashGet(meta, "fieldmapper"), field_name, value);
5793 if (!field_object || field_object->type == JSON_NULL) {
5794 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
5795 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5799 OSRF_BUFFER_ADD_CHAR(sql, ',');
5800 buffer_fadd( sql, " %s = NULL", field_name );
5803 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5807 OSRF_BUFFER_ADD_CHAR(sql, ',');
5809 const char* numtype = get_datatype( field_def );
5810 if ( !strncmp( numtype, "INT", 3 ) ) {
5811 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
5812 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
5813 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
5815 // Must really be intended as a string, so quote it
5816 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5817 buffer_fadd( sql, " %s = %s", field_name, value );
5819 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]",
5821 osrfAppSessionStatus(
5823 OSRF_STATUS_INTERNALSERVERERROR,
5824 "osrfMethodException",
5826 "Error quoting string -- please see the error log for more details"
5830 osrfHashIteratorFree( field_itr );
5832 osrfAppRespondComplete( ctx, NULL );
5837 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5840 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5844 OSRF_BUFFER_ADD_CHAR( sql, ',' );
5845 buffer_fadd( sql, " %s = %s", field_name, value );
5847 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value);
5848 osrfAppSessionStatus(
5850 OSRF_STATUS_INTERNALSERVERERROR,
5851 "osrfMethodException",
5853 "Error quoting string -- please see the error log for more details"
5857 osrfHashIteratorFree( field_itr );
5859 osrfAppRespondComplete( ctx, NULL );
5868 osrfHashIteratorFree( field_itr );
5870 jsonObject* obj = jsonNewObject(id);
5872 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5873 dbi_conn_quote_string(dbhandle, &id);
5875 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5877 char* query = buffer_release(sql);
5878 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", modulename, query);
5880 dbi_result result = dbi_conn_query(dbhandle, query);
5884 jsonObjectFree(obj);
5885 obj = jsonNewObject(NULL);
5888 "%s ERROR updating %s object with %s = %s",
5890 osrfHashGet(meta, "fieldmapper"),
5897 osrfAppRespondComplete( ctx, obj );
5898 jsonObjectFree( obj );
5902 static int doDelete( osrfMethodContext* ctx ) {
5903 if(osrfMethodVerifyContext( ctx )) {
5904 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
5909 timeout_needs_resetting = 1;
5911 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5913 if( getXactId( ctx ) == NULL ) {
5914 osrfAppSessionStatus(
5916 OSRF_STATUS_BADREQUEST,
5917 "osrfMethodException",
5919 "No active transaction -- required for DELETE"
5921 osrfAppRespondComplete( ctx, NULL );
5925 // The following test is harmless but redundant. If a class is
5926 // readonly, we don't register a delete method for it.
5927 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5928 osrfAppSessionStatus(
5930 OSRF_STATUS_BADREQUEST,
5931 "osrfMethodException",
5933 "Cannot DELETE readonly class"
5935 osrfAppRespondComplete( ctx, NULL );
5939 dbhandle = writehandle;
5941 char* pkey = osrfHashGet(meta, "primarykey");
5948 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
5949 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5950 osrfAppRespondComplete( ctx, NULL );
5954 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5956 if( enforce_pcrud && !verifyObjectPCRUD( ctx, NULL )) {
5957 osrfAppRespondComplete( ctx, NULL );
5960 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
5965 "%s deleting %s object with %s = %s",
5967 osrfHashGet(meta, "fieldmapper"),
5972 jsonObject* obj = jsonNewObject(id);
5974 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5975 dbi_conn_quote_string(writehandle, &id);
5977 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;",
5978 osrfHashGet(meta, "tablename"), pkey, id);
5981 jsonObjectFree(obj);
5982 obj = jsonNewObject(NULL);
5985 "%s ERROR deleting %s object with %s = %s",
5987 osrfHashGet(meta, "fieldmapper"),
5995 osrfAppRespondComplete( ctx, obj );
5996 jsonObjectFree( obj );
6001 @brief Translate a row returned from the database into a jsonObject of type JSON_ARRAY.
6002 @param result An iterator for a result set; we only look at the current row.
6003 @param @meta Pointer to the class metadata for the core class.
6004 @return Pointer to the resulting jsonObject if successful; otherwise NULL.
6006 If a column is not defined in the IDL, or if it has no array_position defined for it in
6007 the IDL, or if it is defined as virtual, ignore it.
6009 Otherwise, translate the column value into a jsonObject of type JSON_NULL, JSON_NUMBER,
6010 or JSON_STRING. Then insert this jsonObject into the JSON_ARRAY according to its
6011 array_position in the IDL.
6013 A field defined in the IDL but not represented in the returned row will leave a hole
6014 in the JSON_ARRAY. In effect it will be treated as a null value.
6016 In the resulting JSON_ARRAY, the field values appear in the sequence defined by the IDL,
6017 regardless of their sequence in the SELECT statement. The JSON_ARRAY is assigned the
6018 classname corresponding to the @a meta argument.
6020 The calling code is responsible for freeing the the resulting jsonObject by calling
6023 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
6024 if(!(result && meta)) return jsonNULL;
6026 jsonObject* object = jsonNewObjectType( JSON_ARRAY );
6027 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
6028 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
6030 osrfHash* fields = osrfHashGet(meta, "fields");
6032 int columnIndex = 1;
6033 const char* columnName;
6035 /* cycle through the columns in the row returned from the database */
6036 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
6038 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
6040 int fmIndex = -1; // Will be set to the IDL's sequence number for this field
6042 /* determine the field type and storage attributes */
6043 unsigned short type = dbi_result_get_field_type_idx(result, columnIndex);
6044 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
6046 // Fetch the IDL's sequence number for the field. If the field isn't in the IDL,
6047 // or if it has no sequence number there, or if it's virtual, skip it.
6048 osrfHash* _f = osrfHashGet( fields, (char*) columnName );
6051 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
6052 continue; // skip this column: IDL says it's virtual
6054 const char* pos = (char*)osrfHashGet(_f, "array_position");
6055 if ( !pos ) // IDL has no sequence number for it. This shouldn't happen,
6056 continue; // since we assign sequence numbers dynamically as we load the IDL.
6058 fmIndex = atoi( pos );
6059 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
6061 continue; // This field is not defined in the IDL
6064 // Stuff the column value into a slot in the JSON_ARRAY, indexed according to the
6065 // sequence number from the IDL (which is likely to be different from the sequence
6066 // of columns in the SELECT clause).
6067 if (dbi_result_field_is_null_idx(result, columnIndex)) {
6068 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
6073 case DBI_TYPE_INTEGER :
6075 if( attr & DBI_INTEGER_SIZE8 )
6076 jsonObjectSetIndex( object, fmIndex,
6077 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
6079 jsonObjectSetIndex( object, fmIndex,
6080 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
6084 case DBI_TYPE_DECIMAL :
6085 jsonObjectSetIndex( object, fmIndex,
6086 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
6089 case DBI_TYPE_STRING :
6094 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
6099 case DBI_TYPE_DATETIME : {
6101 char dt_string[256] = "";
6104 // Fetch the date column as a time_t
6105 time_t _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
6107 // Translate the time_t to a human-readable string
6108 if (!(attr & DBI_DATETIME_DATE)) {
6109 gmtime_r( &_tmp_dt, &gmdt );
6110 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
6111 } else if (!(attr & DBI_DATETIME_TIME)) {
6112 localtime_r( &_tmp_dt, &gmdt );
6113 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
6115 localtime_r( &_tmp_dt, &gmdt );
6116 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
6119 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
6123 case DBI_TYPE_BINARY :
6124 osrfLogError( OSRF_LOG_MARK,
6125 "Can't do binary at column %s : index %d", columnName, columnIndex);
6134 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
6135 if(!result) return jsonNULL;
6137 jsonObject* object = jsonNewObject(NULL);
6140 char dt_string[256];
6144 int columnIndex = 1;
6146 unsigned short type;
6147 const char* columnName;
6149 /* cycle through the column list */
6150 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
6152 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
6154 fmIndex = -1; // reset the position
6156 /* determine the field type and storage attributes */
6157 type = dbi_result_get_field_type_idx(result, columnIndex);
6158 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
6160 if (dbi_result_field_is_null_idx(result, columnIndex)) {
6161 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
6166 case DBI_TYPE_INTEGER :
6168 if( attr & DBI_INTEGER_SIZE8 )
6169 jsonObjectSetKey( object, columnName,
6170 jsonNewNumberObject(dbi_result_get_longlong_idx(
6171 result, columnIndex)) );
6173 jsonObjectSetKey( object, columnName,
6174 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
6177 case DBI_TYPE_DECIMAL :
6178 jsonObjectSetKey( object, columnName,
6179 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
6182 case DBI_TYPE_STRING :
6183 jsonObjectSetKey( object, columnName,
6184 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
6187 case DBI_TYPE_DATETIME :
6189 memset(dt_string, '\0', sizeof(dt_string));
6190 memset(&gmdt, '\0', sizeof(gmdt));
6192 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
6195 if (!(attr & DBI_DATETIME_DATE)) {
6196 gmtime_r( &_tmp_dt, &gmdt );
6197 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
6198 } else if (!(attr & DBI_DATETIME_TIME)) {
6199 localtime_r( &_tmp_dt, &gmdt );
6200 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
6202 localtime_r( &_tmp_dt, &gmdt );
6203 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
6206 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
6209 case DBI_TYPE_BINARY :
6210 osrfLogError( OSRF_LOG_MARK,
6211 "Can't do binary at column %s : index %d", columnName, columnIndex );
6215 } // end while loop traversing result
6220 // Interpret a string as true or false
6221 static int str_is_true( const char* str ) {
6222 if( NULL == str || strcasecmp( str, "true" ) )
6228 // Interpret a jsonObject as true or false
6229 static int obj_is_true( const jsonObject* obj ) {
6232 else switch( obj->type )
6240 if( strcasecmp( obj->value.s, "true" ) )
6244 case JSON_NUMBER : // Support 1/0 for perl's sake
6245 if( jsonObjectGetNumber( obj ) == 1.0 )
6254 // Translate a numeric code into a text string identifying a type of
6255 // jsonObject. To be used for building error messages.
6256 static const char* json_type( int code ) {
6262 return "JSON_ARRAY";
6264 return "JSON_STRING";
6266 return "JSON_NUMBER";
6272 return "(unrecognized)";
6276 // Extract the "primitive" attribute from an IDL field definition.
6277 // If we haven't initialized the app, then we must be running in
6278 // some kind of testbed. In that case, default to "string".
6279 static const char* get_primitive( osrfHash* field ) {
6280 const char* s = osrfHashGet( field, "primitive" );
6282 if( child_initialized )
6285 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6287 osrfHashGet( field, "name" )
6295 // Extract the "datatype" attribute from an IDL field definition.
6296 // If we haven't initialized the app, then we must be running in
6297 // some kind of testbed. In that case, default to to NUMERIC,
6298 // since we look at the datatype only for numbers.
6299 static const char* get_datatype( osrfHash* field ) {
6300 const char* s = osrfHashGet( field, "datatype" );
6302 if( child_initialized )
6305 "%s ERROR No \"datatype\" attribute for field \"%s\"",
6307 osrfHashGet( field, "name" )
6316 @brief Determine whether a string is potentially a valid SQL identifier.
6317 @param s The identifier to be tested.
6318 @return 1 if the input string is potentially a valid SQL identifier, or 0 if not.
6320 Purpose: to prevent certain kinds of SQL injection. To that end we don't necessarily
6321 need to follow all the rules exactly, such as requiring that the first character not
6324 We allow leading and trailing white space. In between, we do not allow punctuation
6325 (except for underscores and dollar signs), control characters, or embedded white space.
6327 More pedantically we should allow quoted identifiers containing arbitrary characters, but
6328 for the foreseeable future such quoted identifiers are not likely to be an issue.
6330 static int is_identifier( const char* s) {
6334 // Skip leading white space
6335 while( isspace( (unsigned char) *s ) )
6339 return 0; // Nothing but white space? Not okay.
6341 // Check each character until we reach white space or
6342 // end-of-string. Letters, digits, underscores, and
6343 // dollar signs are okay. With the exception of periods
6344 // (as in schema.identifier), control characters and other
6345 // punctuation characters are not okay. Anything else
6346 // is okay -- it could for example be part of a multibyte
6347 // UTF8 character such as a letter with diacritical marks,
6348 // and those are allowed.
6350 if( isalnum( (unsigned char) *s )
6354 ; // Fine; keep going
6355 else if( ispunct( (unsigned char) *s )
6356 || iscntrl( (unsigned char) *s ) )
6359 } while( *s && ! isspace( (unsigned char) *s ) );
6361 // If we found any white space in the above loop,
6362 // the rest had better be all white space.
6364 while( isspace( (unsigned char) *s ) )
6368 return 0; // White space was embedded within non-white space
6374 @brief Determine whether to accept a character string as a comparison operator.
6375 @param op The candidate comparison operator.
6376 @return 1 if the string is acceptable as a comparison operator, or 0 if not.
6378 We don't validate the operator for real. We just make sure that it doesn't contain
6379 any semicolons or white space (with special exceptions for a few specific operators).
6380 The idea is to block certain kinds of SQL injection. If it has no semicolons or white
6381 space but it's still not a valid operator, then the database will complain.
6383 Another approach would be to compare the string against a short list of approved operators.
6384 We don't do that because we want to allow custom operators like ">100*", which at this
6385 writing would be difficult or impossible to express otherwise in a JSON query.
6387 static int is_good_operator( const char* op ) {
6388 if( !op ) return 0; // Sanity check
6392 if( isspace( (unsigned char) *s ) ) {
6393 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
6394 // and IS NOT DISTINCT FROM.
6395 if( !strcasecmp( op, "similar to" ) )
6397 else if( !strcasecmp( op, "is distinct from" ) )
6399 else if( !strcasecmp( op, "is not distinct from" ) )
6404 else if( ';' == *s )
6412 @name Query Frame Management
6414 The following machinery supports a stack of query frames for use by SELECT().
6416 A query frame caches information about one level of a SELECT query. When we enter
6417 a subquery, we push another query frame onto the stack, and pop it off when we leave.
6419 The query frame stores information about the core class, and about any joined classes
6422 The main purpose is to map table aliases to classes and tables, so that a query can
6423 join to the same table more than once. A secondary goal is to reduce the number of
6424 lookups in the IDL by caching the results.
6428 #define STATIC_CLASS_INFO_COUNT 3
6430 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
6433 @brief Allocate a ClassInfo as raw memory.
6434 @return Pointer to the newly allocated ClassInfo.
6436 Except for the in_use flag, which is used only by the allocation and deallocation
6437 logic, we don't initialize the ClassInfo here.
6439 static ClassInfo* allocate_class_info( void ) {
6440 // In order to reduce the number of mallocs and frees, we return a static
6441 // instance of ClassInfo, if we can find one that we're not already using.
6442 // We rely on the fact that the compiler will implicitly initialize the
6443 // static instances so that in_use == 0.
6446 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6447 if( ! static_class_info[ i ].in_use ) {
6448 static_class_info[ i ].in_use = 1;
6449 return static_class_info + i;
6453 // The static ones are all in use. Malloc one.
6455 return safe_malloc( sizeof( ClassInfo ) );
6459 @brief Free any malloc'd memory owned by a ClassInfo, returning it to a pristine state.
6460 @param info Pointer to the ClassInfo to be cleared.
6462 static void clear_class_info( ClassInfo* info ) {
6467 // Free any malloc'd strings
6469 if( info->alias != info->alias_store )
6470 free( info->alias );
6472 if( info->class_name != info->class_name_store )
6473 free( info->class_name );
6475 free( info->source_def );
6477 info->alias = info->class_name = info->source_def = NULL;
6482 @brief Free a ClassInfo and everything it owns.
6483 @param info Pointer to the ClassInfo to be freed.
6485 static void free_class_info( ClassInfo* info ) {
6490 clear_class_info( info );
6492 // If it's one of the static instances, just mark it as not in use
6495 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6496 if( info == static_class_info + i ) {
6497 static_class_info[ i ].in_use = 0;
6502 // Otherwise it must have been malloc'd, so free it
6508 @brief Populate an already-allocated ClassInfo.
6509 @param info Pointer to the ClassInfo to be populated.
6510 @param alias Alias for the class. If it is NULL, or an empty string, use the class
6512 @param class Name of the class.
6513 @return Zero if successful, or 1 if not.
6515 Populate the ClassInfo with copies of the alias and class name, and with pointers to
6516 the relevant portions of the IDL for the specified class.
6518 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6521 osrfLogError( OSRF_LOG_MARK,
6522 "%s ERROR: No ClassInfo available to populate", modulename );
6523 info->alias = info->class_name = info->source_def = NULL;
6524 info->class_def = info->fields = info->links = NULL;
6529 osrfLogError( OSRF_LOG_MARK,
6530 "%s ERROR: No class name provided for lookup", modulename );
6531 info->alias = info->class_name = info->source_def = NULL;
6532 info->class_def = info->fields = info->links = NULL;
6536 // Alias defaults to class name if not supplied
6537 if( ! alias || ! alias[ 0 ] )
6540 // Look up class info in the IDL
6541 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6543 osrfLogError( OSRF_LOG_MARK,
6544 "%s ERROR: Class %s not defined in IDL", modulename, class );
6545 info->alias = info->class_name = info->source_def = NULL;
6546 info->class_def = info->fields = info->links = NULL;
6548 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6549 osrfLogError( OSRF_LOG_MARK,
6550 "%s ERROR: Class %s is defined as virtual", modulename, class );
6551 info->alias = info->class_name = info->source_def = NULL;
6552 info->class_def = info->fields = info->links = NULL;
6556 osrfHash* links = osrfHashGet( class_def, "links" );
6558 osrfLogError( OSRF_LOG_MARK,
6559 "%s ERROR: No links defined in IDL for class %s", modulename, class );
6560 info->alias = info->class_name = info->source_def = NULL;
6561 info->class_def = info->fields = info->links = NULL;
6565 osrfHash* fields = osrfHashGet( class_def, "fields" );
6567 osrfLogError( OSRF_LOG_MARK,
6568 "%s ERROR: No fields defined in IDL for class %s", modulename, class );
6569 info->alias = info->class_name = info->source_def = NULL;
6570 info->class_def = info->fields = info->links = NULL;
6574 char* source_def = getRelation( class_def );
6578 // We got everything we need, so populate the ClassInfo
6579 if( strlen( alias ) > ALIAS_STORE_SIZE )
6580 info->alias = strdup( alias );
6582 strcpy( info->alias_store, alias );
6583 info->alias = info->alias_store;
6586 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6587 info->class_name = strdup( class );
6589 strcpy( info->class_name_store, class );
6590 info->class_name = info->class_name_store;
6593 info->source_def = source_def;
6595 info->class_def = class_def;
6596 info->links = links;
6597 info->fields = fields;
6602 #define STATIC_FRAME_COUNT 3
6604 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6607 @brief Allocate a QueryFrame as raw memory.
6608 @return Pointer to the newly allocated QueryFrame.
6610 Except for the in_use flag, which is used only by the allocation and deallocation
6611 logic, we don't initialize the QueryFrame here.
6613 static QueryFrame* allocate_frame( void ) {
6614 // In order to reduce the number of mallocs and frees, we return a static
6615 // instance of QueryFrame, if we can find one that we're not already using.
6616 // We rely on the fact that the compiler will implicitly initialize the
6617 // static instances so that in_use == 0.
6620 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6621 if( ! static_frame[ i ].in_use ) {
6622 static_frame[ i ].in_use = 1;
6623 return static_frame + i;
6627 // The static ones are all in use. Malloc one.
6629 return safe_malloc( sizeof( QueryFrame ) );
6633 @brief Free a QueryFrame, and all the memory it owns.
6634 @param frame Pointer to the QueryFrame to be freed.
6636 static void free_query_frame( QueryFrame* frame ) {
6641 clear_class_info( &frame->core );
6643 // Free the join list
6645 ClassInfo* info = frame->join_list;
6648 free_class_info( info );
6652 frame->join_list = NULL;
6655 // If the frame is a static instance, just mark it as unused
6657 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6658 if( frame == static_frame + i ) {
6659 static_frame[ i ].in_use = 0;
6664 // Otherwise it must have been malloc'd, so free it
6670 @brief Search a given QueryFrame for a specified alias.
6671 @param frame Pointer to the QueryFrame to be searched.
6672 @param target The alias for which to search.
6673 @return Pointer to the ClassInfo for the specified alias, if found; otherwise NULL.
6675 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6676 if( ! frame || ! target ) {
6680 ClassInfo* found_class = NULL;
6682 if( !strcmp( target, frame->core.alias ) )
6683 return &(frame->core);
6685 ClassInfo* curr_class = frame->join_list;
6686 while( curr_class ) {
6687 if( strcmp( target, curr_class->alias ) )
6688 curr_class = curr_class->next;
6690 found_class = curr_class;
6700 @brief Push a new (blank) QueryFrame onto the stack.
6702 static void push_query_frame( void ) {
6703 QueryFrame* frame = allocate_frame();
6704 frame->join_list = NULL;
6705 frame->next = curr_query;
6707 // Initialize the ClassInfo for the core class
6708 ClassInfo* core = &frame->core;
6709 core->alias = core->class_name = core->source_def = NULL;
6710 core->class_def = core->fields = core->links = NULL;
6716 @brief Pop a QueryFrame off the stack and destroy it.
6718 static void pop_query_frame( void ) {
6723 QueryFrame* popped = curr_query;
6724 curr_query = popped->next;
6726 free_query_frame( popped );
6730 @brief Populate the ClassInfo for the core class.
6731 @param alias Alias for the core class. If it is NULL or an empty string, we use the
6732 class name as an alias.
6733 @param class_name Name of the core class.
6734 @return Zero if successful, or 1 if not.
6736 Populate the ClassInfo of the core class with copies of the alias and class name, and
6737 with pointers to the relevant portions of the IDL for the core class.
6739 static int add_query_core( const char* alias, const char* class_name ) {
6742 if( ! curr_query ) {
6743 osrfLogError( OSRF_LOG_MARK,
6744 "%s ERROR: No QueryFrame available for class %s", modulename, class_name );
6746 } else if( curr_query->core.alias ) {
6747 osrfLogError( OSRF_LOG_MARK,
6748 "%s ERROR: Core class %s already populated as %s",
6749 modulename, curr_query->core.class_name, curr_query->core.alias );
6753 build_class_info( &curr_query->core, alias, class_name );
6754 if( curr_query->core.alias )
6757 osrfLogError( OSRF_LOG_MARK,
6758 "%s ERROR: Unable to look up core class %s", modulename, class_name );
6764 @brief Search the current QueryFrame for a specified alias.
6765 @param target The alias for which to search.
6766 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6768 static inline ClassInfo* search_alias( const char* target ) {
6769 return search_alias_in_frame( curr_query, target );
6773 @brief Search all levels of query for a specified alias, starting with the current query.
6774 @param target The alias for which to search.
6775 @return A pointer to the corresponding ClassInfo, if found; otherwise NULL.
6777 static ClassInfo* search_all_alias( const char* target ) {
6778 ClassInfo* found_class = NULL;
6779 QueryFrame* curr_frame = curr_query;
6781 while( curr_frame ) {
6782 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6785 curr_frame = curr_frame->next;
6792 @brief Add a class to the list of classes joined to the current query.
6793 @param alias Alias of the class to be added. If it is NULL or an empty string, we use
6794 the class name as an alias.
6795 @param classname The name of the class to be added.
6796 @return A pointer to the ClassInfo for the added class, if successful; otherwise NULL.
6798 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6800 if( ! classname || ! *classname ) { // sanity check
6801 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6808 const ClassInfo* conflict = search_alias( alias );
6810 osrfLogError( OSRF_LOG_MARK,
6811 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6812 modulename, alias, conflict->class_name );
6816 ClassInfo* info = allocate_class_info();
6818 if( build_class_info( info, alias, classname ) ) {
6819 free_class_info( info );
6823 // Add the new ClassInfo to the join list of the current QueryFrame
6824 info->next = curr_query->join_list;
6825 curr_query->join_list = info;
6831 @brief Destroy all nodes on the query stack.
6833 static void clear_query_stack( void ) {