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"
22 # define MODULENAME "open-ils.reporter-store"
25 # define MODULENAME "open-ils.pcrud"
27 # define MODULENAME "open-ils.cstore"
31 // The next four macros are OR'd together as needed to form a set
32 // of bitflags. SUBCOMBO enables an extra pair of parentheses when
33 // nesting one UNION, INTERSECT or EXCEPT inside another.
34 // SUBSELECT tells us we're in a subquery, so don't add the
35 // terminal semicolon yet.
38 #define DISABLE_I18N 2
39 #define SELECT_DISTINCT 1
44 struct ClassInfoStruct;
45 typedef struct ClassInfoStruct ClassInfo;
47 #define ALIAS_STORE_SIZE 16
48 #define CLASS_NAME_STORE_SIZE 16
50 struct ClassInfoStruct {
54 osrfHash* class_def; // Points into IDL
55 osrfHash* fields; // Points into IDL
56 osrfHash* links; // Points into IDL
58 // The remaining members are private and internal. Client code should not
59 // access them directly.
61 ClassInfo* next; // Supports linked list of joined classes
62 int in_use; // boolean
64 // We usually store the alias and class name in the following arrays, and
65 // point the corresponding pointers at them. When the string is too big
66 // for the array (which will probably never happen in practice), we strdup it.
68 char alias_store[ ALIAS_STORE_SIZE + 1 ];
69 char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
72 struct QueryFrameStruct;
73 typedef struct QueryFrameStruct QueryFrame;
75 struct QueryFrameStruct {
77 ClassInfo* join_list; // linked list of classes joined to the core class
78 QueryFrame* next; // implements stack as linked list
79 int in_use; // boolean
82 int osrfAppChildInit();
83 int osrfAppInitialize();
84 void osrfAppChildExit();
86 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
88 static void setXactId( osrfMethodContext* ctx );
89 static inline const char* getXactId( osrfMethodContext* ctx );
90 static inline void clearXactId( osrfMethodContext* ctx );
92 int beginTransaction ( osrfMethodContext* );
93 int commitTransaction ( osrfMethodContext* );
94 int rollbackTransaction ( osrfMethodContext* );
96 int setSavepoint ( osrfMethodContext* );
97 int releaseSavepoint ( osrfMethodContext* );
98 int rollbackSavepoint ( osrfMethodContext* );
100 int doJSONSearch ( osrfMethodContext* );
102 int dispatchCRUDMethod ( osrfMethodContext* );
103 static jsonObject* doCreate ( osrfMethodContext*, int* );
104 static jsonObject* doRetrieve ( osrfMethodContext*, int* );
105 static jsonObject* doUpdate ( osrfMethodContext*, int* );
106 static jsonObject* doDelete ( osrfMethodContext*, int* );
107 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
108 jsonObject* where_hash, jsonObject* query_hash, int* err );
109 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
110 static jsonObject* oilsMakeJSONFromResult( dbi_result );
112 static char* searchSimplePredicate ( const char* op, const char* class_alias,
113 osrfHash* field, const jsonObject* node );
114 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
115 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject*);
116 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*, const char* );
117 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
118 static char* searchINPredicate ( const char*, osrfHash*,
119 jsonObject*, const char*, osrfMethodContext* );
120 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
121 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
122 static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
123 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
124 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
126 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
128 void userDataFree( void* );
129 static void sessionDataFree( char*, void* );
130 static char* getRelation( osrfHash* );
131 static int str_is_true( const char* str );
132 static int obj_is_true( const jsonObject* obj );
133 static const char* json_type( int code );
134 static const char* get_primitive( osrfHash* field );
135 static const char* get_datatype( osrfHash* field );
136 static int is_identifier( const char* s);
137 static int is_good_operator( const char* op );
138 static void pop_query_frame( void );
139 static void push_query_frame( void );
140 static int add_query_core( const char* alias, const char* class_name );
141 static ClassInfo* search_alias( const char* target );
142 static ClassInfo* search_all_alias( const char* target );
143 static ClassInfo* add_joined_class( const char* alias, const char* classname );
144 static void clear_query_stack( void );
147 static jsonObject* verifyUserPCRUD( osrfMethodContext* );
148 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
149 static char* org_tree_root( osrfMethodContext* ctx );
150 static jsonObject* single_hash( const char* key, const char* value );
153 static int child_initialized = 0; /* boolean */
155 static dbi_conn writehandle; /* our MASTER db connection */
156 static dbi_conn dbhandle; /* our CURRENT db connection */
157 //static osrfHash * readHandles;
158 static jsonObject* const jsonNULL = NULL; //
159 static int max_flesh_depth = 100;
161 // The following points to the top of a stack of QueryFrames. It's a little
162 // confusing because the top level of the query is at the bottom of the stack.
163 static QueryFrame* curr_query = NULL;
166 @brief Disconnect from the database.
168 This function is called when the server drone is about to terminate.
170 void osrfAppChildExit() {
171 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
174 if (writehandle == dbhandle) same = 1;
176 dbi_conn_query(writehandle, "ROLLBACK;");
177 dbi_conn_close(writehandle);
180 if (dbhandle && !same)
181 dbi_conn_close(dbhandle);
183 // XXX add cleanup of readHandles whenever that gets used
189 @brief Initialize the application.
190 @return Zero if successful, or non-zero if not.
192 Load the IDL file into an internal data structure for future reference. Each non-virtual
193 class in the IDL corresponds to a table or view in the database, or to a subquery defined
194 in the IDL. Ignore all virtual tables and virtual fields.
196 Register a number of methods, some of them general-purpose and others specific for
199 The name of the application is given by the MODULENAME macro, whose value depends on
200 conditional compilation. The method names also incorporate MODULENAME, followed by a
201 dot, as a prefix. Some methods are registered or not registered depending on whether
202 the PCRUD macro is defined, and on whether the IDL includes permacrud entries for the
205 The general-purpose methods are as follows (minus their MODULENAME prefixes):
207 - json_query (not registered for PCRUD)
210 - transaction.rollback
215 For each non-virtual class, create up to eight class-specific methods:
217 - create (not for readonly classes)
219 - update (not for readonly classes)
220 - delete (not for readonly classes
221 - search (atomic and non-atomic versions)
222 - id_list (atomic and non-atomic versions)
224 For PCRUD, the full method names follow the pattern "MODULENAME.method_type.classname".
225 Otherwise they follow the pattern "MODULENAME.direct.XXX.method_type", where XXX is the
226 fieldmapper name from the IDL, with every run of one or more consecutive colons replaced
227 by a period. In addition, the names of atomic methods have a suffix of ".atomic".
229 This function is called when the registering the application, and is executed by the
230 listener before spawning the drones.
232 int osrfAppInitialize() {
234 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
235 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
237 if (!oilsIDLInit( osrf_settings_host_value("/IDL") ))
238 return 1; /* return non-zero to indicate error */
240 growing_buffer* method_name = buffer_init(64);
242 // Generic search thingy
243 buffer_add(method_name, MODULENAME);
244 buffer_add(method_name, ".json_query");
245 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
246 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
249 // first we register all the transaction and savepoint methods
250 buffer_reset(method_name);
251 OSRF_BUFFER_ADD(method_name, MODULENAME);
252 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
253 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
254 "beginTransaction", "", 0, 0 );
256 buffer_reset(method_name);
257 OSRF_BUFFER_ADD(method_name, MODULENAME);
258 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
259 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
260 "commitTransaction", "", 0, 0 );
262 buffer_reset(method_name);
263 OSRF_BUFFER_ADD(method_name, MODULENAME);
264 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
265 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
266 "rollbackTransaction", "", 0, 0 );
268 buffer_reset(method_name);
269 OSRF_BUFFER_ADD(method_name, MODULENAME);
270 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
271 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
272 "setSavepoint", "", 1, 0 );
274 buffer_reset(method_name);
275 OSRF_BUFFER_ADD(method_name, MODULENAME);
276 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
277 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
278 "releaseSavepoint", "", 1, 0 );
280 buffer_reset(method_name);
281 OSRF_BUFFER_ADD(method_name, MODULENAME);
282 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
283 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
284 "rollbackSavepoint", "", 1, 0 );
286 static const char* global_method[] = {
294 const int global_method_count
295 = sizeof( global_method ) / sizeof ( global_method[0] );
297 unsigned long class_count = osrfHashGetCount( oilsIDL() );
298 osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
299 osrfLogDebug(OSRF_LOG_MARK,
300 "At most %lu methods will be generated",
301 (unsigned long) (class_count * global_method_count) );
303 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
304 osrfHash* idlClass = NULL;
306 // For each class in the IDL...
307 while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
309 const char* classname = osrfHashIteratorKey( class_itr );
310 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
312 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
313 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
314 MODULENAME, classname);
318 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
319 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
323 // Look up some other attributes of the current class
324 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
325 if( !idlClass_fieldmapper ) {
326 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
332 // For PCRUD, ignore classes with no permacrud attribute
333 osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
334 if (!idlClass_permacrud) {
335 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no permacrud in IDL", classname );
339 const char* readonly = osrfHashGet(idlClass, "readonly");
342 for( i = 0; i < global_method_count; ++i ) { // for each global method
343 const char* method_type = global_method[ i ];
344 osrfLogDebug(OSRF_LOG_MARK,
345 "Using files to build %s class methods for %s", method_type, classname);
348 // Treat "id_list" or "search" as forms of "retrieve"
349 const char* tmp_method = method_type;
350 if ( *tmp_method == 'i' || *tmp_method == 's') { // "id_list" or "search"
351 tmp_method = "retrieve";
353 // Skip this method if there is no permacrud entry for it
354 if (!osrfHashGet( idlClass_permacrud, tmp_method ))
358 // No create, update, or delete methods for a readonly class
359 if ( str_is_true( readonly ) &&
360 ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd')
363 buffer_reset( method_name );
365 // Build the method name
367 // For PCRUD: MODULENAME.method_type.classname
368 buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
370 // For non-PCRUD: MODULENAME.MODULENAME.direct.XXX.method_type
371 // where XXX is the fieldmapper name from the IDL, with every run of
372 // one or more consecutive colons replaced by a period.
375 char* _fm = strdup( idlClass_fieldmapper );
376 part = strtok_r(_fm, ":", &st_tmp);
378 buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
380 while ((part = strtok_r(NULL, ":", &st_tmp))) {
381 OSRF_BUFFER_ADD_CHAR(method_name, '.');
382 OSRF_BUFFER_ADD(method_name, part);
384 OSRF_BUFFER_ADD_CHAR(method_name, '.');
385 OSRF_BUFFER_ADD(method_name, method_type);
389 // For an id_list or search method we specify the OSRF_METHOD_STREAMING option.
390 // The consequence is that we implicitly create an atomic method in addition to
391 // the usual non-atomic method.
393 if (*method_type == 'i' || *method_type == 's') { // id_list or search
394 flags = flags | OSRF_METHOD_STREAMING;
397 osrfHash* method_meta = osrfNewHash();
398 osrfHashSet( method_meta, idlClass, "class");
399 osrfHashSet( method_meta, buffer_data( method_name ), "methodname" );
400 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
402 // Register the method, with a pointer to an osrfHash to tell the method
403 // its name, type, and class.
404 osrfAppRegisterExtendedMethod(
406 OSRF_BUFFER_C_STR( method_name ),
407 "dispatchCRUDMethod",
414 } // end for each global method
415 } // end for each class in IDL
417 buffer_free( method_name );
418 osrfHashIteratorFree( class_itr );
424 @brief Get a table name, view name, or subquery for use in a FROM clause.
425 @param class Pointer to the IDL class entry.
426 @return A table name, a view name, or a subquery in parentheses.
428 In some cases the IDL defines a class, not with a table name or a view name, but with
429 a SELECT statement, which may be used as a subquery.
431 static char* getRelation( osrfHash* class ) {
433 char* source_def = NULL;
434 const char* tabledef = osrfHashGet(class, "tablename");
437 source_def = strdup( tabledef ); // Return the name of a table or view
439 tabledef = osrfHashGet( class, "source_definition" );
441 // Return a subquery, enclosed in parentheses
442 source_def = safe_malloc( strlen( tabledef ) + 3 );
443 source_def[ 0 ] = '(';
444 strcpy( source_def + 1, tabledef );
445 strcat( source_def, ")" );
447 // Not found: return an error
448 const char* classname = osrfHashGet( class, "classname" );
453 "%s ERROR No tablename or source_definition for class \"%s\"",
464 @brief Initialize a server drone.
465 @return Zero if successful, -1 if not.
467 Connect to the database. For each non-virtual class in the IDL, execute a dummy "SELECT * "
468 query to get the datatype of each column. Record the datatypes in the loaded IDL.
470 This function is called by a server drone shortly after it is spawned by the listener.
472 int osrfAppChildInit() {
474 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
475 dbi_initialize(NULL);
476 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
478 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
479 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
480 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
481 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
482 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
483 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
484 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion",
487 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
488 writehandle = dbi_conn_new(driver);
491 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
494 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
496 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
497 "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
499 if(host) dbi_conn_set_option(writehandle, "host", host );
500 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
501 if(user) dbi_conn_set_option(writehandle, "username", user);
502 if(pw) dbi_conn_set_option(writehandle, "password", pw );
503 if(db) dbi_conn_set_option(writehandle, "dbname", db );
505 if(md) max_flesh_depth = atoi(md);
506 if(max_flesh_depth < 0) max_flesh_depth = 1;
507 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
516 if (dbi_conn_connect(writehandle) < 0) {
518 if (dbi_conn_connect(writehandle) < 0) {
519 dbi_conn_error(writehandle, &err);
520 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
525 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
527 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
528 osrfHash* class = NULL;
529 growing_buffer* query_buf = buffer_init( 64 );
531 // For each class in the IDL...
532 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
533 const char* classname = osrfHashIteratorKey( class_itr );
534 osrfHash* fields = osrfHashGet( class, "fields" );
536 // If the class is virtual, ignore it
537 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
538 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
542 char* tabledef = getRelation(class);
544 continue; // No such relation -- a query of it would be doomed to failure
546 buffer_reset( query_buf );
547 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
551 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
552 MODULENAME, OSRF_BUFFER_C_STR( query_buf ) );
554 dbi_result result = dbi_conn_query( writehandle, OSRF_BUFFER_C_STR( query_buf ) );
558 const char* columnName;
560 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
562 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
565 /* fetch the fieldmapper index */
566 if( (_f = osrfHashGet(fields, columnName)) ) {
568 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
570 /* determine the field type and storage attributes */
572 switch( dbi_result_get_field_type_idx(result, columnIndex) ) {
574 case DBI_TYPE_INTEGER : {
576 if ( !osrfHashGet(_f, "primitive") )
577 osrfHashSet(_f, "number", "primitive");
579 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
580 if( attr & DBI_INTEGER_SIZE8 )
581 osrfHashSet(_f, "INT8", "datatype");
583 osrfHashSet(_f, "INT", "datatype");
586 case DBI_TYPE_DECIMAL :
587 if ( !osrfHashGet(_f, "primitive") )
588 osrfHashSet(_f, "number", "primitive");
590 osrfHashSet(_f,"NUMERIC", "datatype");
593 case DBI_TYPE_STRING :
594 if ( !osrfHashGet(_f, "primitive") )
595 osrfHashSet(_f,"string", "primitive");
597 osrfHashSet(_f,"TEXT", "datatype");
600 case DBI_TYPE_DATETIME :
601 if ( !osrfHashGet(_f, "primitive") )
602 osrfHashSet(_f,"string", "primitive");
604 osrfHashSet(_f,"TIMESTAMP", "datatype");
607 case DBI_TYPE_BINARY :
608 if ( !osrfHashGet(_f, "primitive") )
609 osrfHashSet(_f,"string", "primitive");
611 osrfHashSet(_f,"BYTEA", "datatype");
616 "Setting [%s] to primitive [%s] and datatype [%s]...",
618 osrfHashGet(_f, "primitive"),
619 osrfHashGet(_f, "datatype")
623 } // end while loop for traversing columns of result
624 dbi_result_free(result);
626 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", classname);
628 } // end for each class in IDL
630 buffer_free( query_buf );
631 osrfHashIteratorFree( class_itr );
632 child_initialized = 1;
637 @brief Install a database driver.
638 @param conn Pointer to a database driver.
640 The driver is used to process quoted strings correctly.
642 This function is a sleazy hack intended @em only for testing and debugging without
643 actually connecting to a database. Any real server process should initialize the
644 database connection by calling osrfAppChildInit().
646 void set_cstore_dbi_conn( dbi_conn conn ) {
647 dbhandle = writehandle = conn;
651 @brief Free an osrfHash that stores a transaction ID.
652 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
654 This function is a callback, to be called by the application session when it ends.
655 The application session stores the osrfHash via an opaque pointer.
657 If the osrfHash contains an entry for the key "xact_id", it means that an
658 uncommitted transaction is pending. Roll it back.
660 void userDataFree( void* blob ) {
661 osrfHash* hash = (osrfHash*) blob;
662 if( osrfHashGet( hash, "xact_id" ) && writehandle )
663 dbi_conn_query( writehandle, "ROLLBACK;" );
665 osrfHashFree( hash );
669 @name Managing session data
670 @brief Maintain data stored via the userData pointer of the application session.
672 Currently, session-level data is stored in an osrfHash. Other arrangements are
673 possible, and some would be more efficient. The application session calls a
674 callback function to free userData before terminating.
676 Currently, the only data we store at the session level is the transaction id. By this
677 means we can ensure that any pending transactions are rolled back before the application
683 @brief Free an item in the application session's userData.
684 @param key The name of a key for an osrfHash.
685 @param item An opaque pointer to the item associated with the key.
687 We store an osrfHash as userData with the application session, and arrange (by
688 installing userDataFree() as a different callback) for the session to free that
689 osrfHash before terminating.
691 This function is a callback for freeing items in the osrfHash. If the item has a key
692 of "xact_id", the item is a transaction id for a transaction that is still pending.
693 It is just a character string, so we free it.
695 Currently the transaction ID is the only thing that we store in this osrfHash. If we
696 ever store anything else in it, we will need to revisit this function so that it will
697 free whatever else needs freeing (which may or may not be just a character string).
699 static void sessionDataFree( char* key, void* item ) {
700 if ( !strcmp(key,"xact_id") ) {
706 @brief Save a transaction id.
707 @param ctx Pointer to the method context.
709 Save the session_id of the current application session as a transaction id.
711 static void setXactId( osrfMethodContext* ctx ) {
712 if( ctx && ctx->session ) {
713 osrfAppSession* session = ctx->session;
715 // If the session doesn't already have a hash, create one. Make sure
716 // that the application session frees the hash when it terminates.
717 if( NULL == session->userData ) {
718 session->userData = osrfNewHash();
719 osrfHashSetCallback( (osrfHash*) session->userData, &sessionDataFree );
720 ctx->session->userDataFree = &userDataFree;
723 // Save the transaction id in the hash, with the key "xact_id"
724 osrfHashSet( (osrfHash*) session->userData, strdup( session->session_id ),
730 @brief Get the transaction ID for the current transaction, if any.
731 @param ctx Pointer to the method context.
732 @return Pointer to the transaction ID.
734 The return value points to an internal buffer, and will become invalid upon issuing
735 a commit or rollback.
737 static inline const char* getXactId( osrfMethodContext* ctx ) {
738 if( ctx && ctx->session && ctx->session->userData )
739 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
745 @brief Clear the current transaction id.
746 @param ctx Pointer to the method context.
748 static inline void clearXactId( osrfMethodContext* ctx ) {
749 if( ctx && ctx->session && ctx->session->userData )
750 osrfHashRemove( ctx->session->userData, "xact_id" );
755 @brief Implement the transaction.begin method.
756 @param ctx Pointer to the method context.
757 @return Zero if successful, or -1 upon error.
759 Start a transaction. Return a transaction ID (actually the session_id of the
760 application session) to the client, and save it for future reference.
762 int beginTransaction ( osrfMethodContext* ctx ) {
763 if(osrfMethodVerifyContext( ctx )) {
764 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
769 jsonObject* user = verifyUserPCRUD( ctx );
772 jsonObjectFree(user);
775 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
777 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
778 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
779 "osrfMethodException", ctx->request, "Error starting transaction" );
783 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
784 osrfAppRespondComplete( ctx, ret );
790 int setSavepoint ( osrfMethodContext* ctx ) {
791 if(osrfMethodVerifyContext( ctx )) {
792 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
799 jsonObject* user = verifyUserPCRUD( ctx );
802 jsonObjectFree(user);
805 if( getXactId( ctx ) == NULL ) {
806 osrfAppSessionStatus(
808 OSRF_STATUS_INTERNALSERVERERROR,
809 "osrfMethodException",
811 "No active transaction -- required for savepoints"
816 const char* spName = jsonObjectGetString(jsonObjectGetIndex(ctx->params, spNamePos));
818 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
822 "%s: Error creating savepoint %s in transaction %s",
827 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
828 "osrfMethodException", ctx->request, "Error creating savepoint" );
831 jsonObject* ret = jsonNewObject(spName);
832 osrfAppRespondComplete( ctx, ret );
838 int releaseSavepoint ( osrfMethodContext* ctx ) {
839 if(osrfMethodVerifyContext( ctx )) {
840 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
847 jsonObject* user = verifyUserPCRUD( ctx );
850 jsonObjectFree(user);
853 if( getXactId( ctx ) == NULL ) {
854 osrfAppSessionStatus(
856 OSRF_STATUS_INTERNALSERVERERROR,
857 "osrfMethodException",
859 "No active transaction -- required for savepoints"
864 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
866 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
870 "%s: Error releasing savepoint %s in transaction %s",
875 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
876 "osrfMethodException", ctx->request, "Error releasing savepoint" );
879 jsonObject* ret = jsonNewObject(spName);
880 osrfAppRespondComplete( ctx, ret );
886 int rollbackSavepoint ( osrfMethodContext* ctx ) {
887 if(osrfMethodVerifyContext( ctx )) {
888 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
895 jsonObject* user = verifyUserPCRUD( ctx );
898 jsonObjectFree(user);
901 if( getXactId( ctx ) == NULL ) {
902 osrfAppSessionStatus(
904 OSRF_STATUS_INTERNALSERVERERROR,
905 "osrfMethodException",
907 "No active transaction -- required for savepoints"
912 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
914 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
918 "%s: Error rolling back savepoint %s in transaction %s",
923 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
924 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
927 jsonObject* ret = jsonNewObject(spName);
928 osrfAppRespondComplete( ctx, ret );
934 int commitTransaction ( osrfMethodContext* ctx ) {
935 if(osrfMethodVerifyContext( ctx )) {
936 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
941 jsonObject* user = verifyUserPCRUD( ctx );
944 jsonObjectFree(user);
947 if( getXactId( ctx ) == NULL ) {
948 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
949 "osrfMethodException", ctx->request, "No active transaction to commit" );
953 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
955 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
956 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
957 "osrfMethodException", ctx->request, "Error committing transaction" );
961 jsonObject* ret = jsonNewObject(ctx->session->session_id);
962 osrfAppRespondComplete( ctx, ret );
968 int rollbackTransaction ( osrfMethodContext* ctx ) {
969 if(osrfMethodVerifyContext( ctx )) {
970 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
975 jsonObject* user = verifyUserPCRUD( ctx );
978 jsonObjectFree(user);
981 if( getXactId( ctx ) == NULL ) {
982 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
983 "osrfMethodException", ctx->request, "No active transaction to roll back" );
987 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
989 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
990 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
991 "osrfMethodException", ctx->request, "Error rolling back transaction" );
995 jsonObject* ret = jsonNewObject(ctx->session->session_id);
996 osrfAppRespondComplete( ctx, ret );
1002 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
1003 if(osrfMethodVerifyContext( ctx )) {
1004 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1008 osrfHash* meta = (osrfHash*) ctx->method->userData;
1009 osrfHash* class_obj = osrfHashGet( meta, "class" );
1013 const char* methodtype = osrfHashGet(meta, "methodtype");
1014 jsonObject * obj = NULL;
1016 if (!strcmp(methodtype, "create")) {
1017 obj = doCreate(ctx, &err);
1018 osrfAppRespondComplete( ctx, obj );
1020 else if (!strcmp(methodtype, "retrieve")) {
1021 obj = doRetrieve(ctx, &err);
1022 osrfAppRespondComplete( ctx, obj );
1024 else if (!strcmp(methodtype, "update")) {
1025 obj = doUpdate(ctx, &err);
1026 osrfAppRespondComplete( ctx, obj );
1028 else if (!strcmp(methodtype, "delete")) {
1029 obj = doDelete(ctx, &err);
1030 osrfAppRespondComplete( ctx, obj );
1032 else if (!strcmp(methodtype, "search")) {
1034 jsonObject* where_clause;
1035 jsonObject* rest_of_query;
1038 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1039 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1041 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1042 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1045 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
1049 jsonObject* cur = 0;
1050 unsigned long res_idx = 0;
1051 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1053 if(!verifyObjectPCRUD(ctx, cur)) continue;
1055 osrfAppRespond( ctx, cur );
1057 osrfAppRespondComplete( ctx, NULL );
1059 } else if (!strcmp(methodtype, "id_list")) {
1061 jsonObject* where_clause;
1062 jsonObject* rest_of_query;
1064 // We use the where clause without change. But we need
1065 // to massage the rest of the query, so we work with a copy
1066 // of it instead of modifying the original.
1068 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1069 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1071 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1072 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1075 if ( rest_of_query ) {
1076 jsonObjectRemoveKey( rest_of_query, "select" );
1077 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1078 jsonObjectRemoveKey( rest_of_query, "flesh" );
1079 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
1081 rest_of_query = jsonNewObjectType( JSON_HASH );
1084 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1086 // Build a SELECT list containing just the primary key,
1087 // i.e. like { "classname":["keyname"] }
1088 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1089 jsonObjectPush( col_list_obj, // Load array with name of primary key
1090 jsonNewObject( osrfHashGet( class_obj, "primarykey" ) ) );
1091 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1092 jsonObjectSetKey( select_clause, osrfHashGet( class_obj, "classname" ), col_list_obj );
1094 jsonObjectSetKey( rest_of_query, "select", select_clause );
1096 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
1098 jsonObjectFree( rest_of_query );
1102 unsigned long res_idx = 0;
1103 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1105 if(!verifyObjectPCRUD(ctx, cur)) continue;
1109 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
1112 osrfAppRespondComplete( ctx, NULL );
1115 osrfAppRespondComplete( ctx, obj );
1118 jsonObjectFree(obj);
1123 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1126 osrfHash* meta = (osrfHash*) ctx->method->userData;
1127 osrfHash* class = osrfHashGet( meta, "class" );
1129 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1131 const char* temp_classname = param->classname;
1132 if( ! temp_classname )
1133 temp_classname = "(null)";
1135 growing_buffer* msg = buffer_init(128);
1138 "%s: %s method for type %s was passed a %s",
1140 osrfHashGet(meta, "methodtype"),
1141 osrfHashGet(class, "classname"),
1145 char* m = buffer_release(msg);
1146 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1155 ret = verifyObjectPCRUD( ctx, param );
1163 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1164 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1165 jsonObject* auth_object = jsonNewObject(auth);
1166 jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve",
1168 jsonObjectFree(auth_object);
1170 if (!user->classname || strcmp(user->classname, "au")) {
1172 growing_buffer* msg = buffer_init(128);
1175 "%s: permacrud received a bad auth token: %s",
1180 char* m = buffer_release(msg);
1181 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1185 jsonObjectFree(user);
1193 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1195 dbhandle = writehandle;
1197 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1198 osrfHash* class = osrfHashGet( method_metadata, "class" );
1199 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1202 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
1203 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1204 } else if ( *method_type == 'u' || *method_type == 'd' ) {
1205 fetch = 1; // MUST go to the db for the object for update and delete
1208 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
1211 // No permacrud for this method type on this class
1213 growing_buffer* msg = buffer_init(128);
1216 "%s: %s on class %s has no permacrud IDL entry",
1218 osrfHashGet(method_metadata, "methodtype"),
1219 osrfHashGet(class, "classname")
1222 char* m = buffer_release(msg);
1223 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN, "osrfMethodException", ctx->request, m );
1230 jsonObject* user = verifyUserPCRUD( ctx );
1234 int userid = atoi( oilsFMGetString( user, "id" ) );
1235 jsonObjectFree(user);
1237 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1238 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1239 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1241 osrfStringArray* context_org_array = osrfNewStringArray(1);
1244 char* pkey_value = NULL;
1245 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1246 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions required, fetching top of the org tree" );
1248 // check for perm at top of org tree
1249 char* org_tree_root_id = org_tree_root( ctx );
1250 if( org_tree_root_id ) {
1251 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1252 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1254 osrfStringArrayFree( context_org_array );
1259 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, fetching context org ids" );
1260 const char* pkey = osrfHashGet(class, "primarykey");
1261 jsonObject *param = NULL;
1263 if (obj->classname) {
1264 pkey_value = oilsFMGetString( obj, pkey );
1265 if (!fetch) param = jsonObjectClone(obj);
1266 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s", pkey_value );
1268 pkey_value = jsonObjectToSimpleString( obj );
1270 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value of %s and retrieving from the database", pkey_value );
1274 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1275 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1276 jsonObjectFree(_tmp_params);
1278 param = jsonObjectExtractIndex(_list, 0);
1279 jsonObjectFree(_list);
1283 osrfLogDebug( OSRF_LOG_MARK, "Object not found in the database with primary key %s of %s", pkey, pkey_value );
1285 growing_buffer* msg = buffer_init(128);
1288 "%s: no object found with primary key %s of %s",
1294 char* m = buffer_release(msg);
1295 osrfAppSessionStatus(
1297 OSRF_STATUS_INTERNALSERVERERROR,
1298 "osrfMethodException",
1304 if (pkey_value) free(pkey_value);
1309 if (local_context->size > 0) {
1310 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified", local_context->size);
1312 const char* lcontext = NULL;
1313 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1314 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1317 "adding class-local field %s (value: %s) to the context org list",
1319 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1325 if (foreign_context) {
1326 unsigned long class_count = osrfHashGetCount( foreign_context );
1327 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1329 if (class_count > 0) {
1331 osrfHash* fcontext = NULL;
1332 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1333 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1334 const char* class_name = osrfHashIteratorKey( class_itr );
1335 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1339 "%d foreign context fields(s) specified for class %s",
1340 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1344 char* foreign_pkey = osrfHashGet(fcontext, "field");
1345 char* foreign_pkey_value = oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1347 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1349 jsonObject* _list = doFieldmapperSearch(
1350 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1352 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1353 jsonObjectFree(_tmp_params);
1354 jsonObjectFree(_list);
1356 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1358 if (_fparam && jump_list) {
1359 const char* flink = NULL;
1361 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1362 free(foreign_pkey_value);
1364 osrfHash* foreign_link_hash = oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1366 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1367 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1369 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1371 _list = doFieldmapperSearch(
1373 osrfHashGet( oilsIDL(), osrfHashGet( foreign_link_hash, "class" ) ),
1379 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1380 jsonObjectFree(_tmp_params);
1381 jsonObjectFree(_list);
1387 growing_buffer* msg = buffer_init(128);
1390 "%s: no object found with primary key %s of %s",
1396 char* m = buffer_release(msg);
1397 osrfAppSessionStatus(
1399 OSRF_STATUS_INTERNALSERVERERROR,
1400 "osrfMethodException",
1406 osrfHashIteratorFree(class_itr);
1407 free(foreign_pkey_value);
1408 jsonObjectFree(param);
1413 free(foreign_pkey_value);
1416 const char* foreign_field = NULL;
1417 while ( (foreign_field = osrfStringArrayGetString(osrfHashGet(fcontext,"context"), j++)) ) {
1418 osrfStringArrayAdd( context_org_array, oilsFMGetString( _fparam, foreign_field ) );
1421 "adding foreign class %s field %s (value: %s) to the context org list",
1424 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1428 jsonObjectFree(_fparam);
1431 osrfHashIteratorFree( class_itr );
1435 jsonObjectFree(param);
1438 const char* context_org = NULL;
1439 const char* perm = NULL;
1442 if (permission->size == 0) {
1443 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1448 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1450 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1456 "Checking object permission [%s] for user %d on object %s (class %s) at org %d",
1460 osrfHashGet(class, "classname"),
1464 result = dbi_conn_queryf(
1466 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1469 osrfHashGet(class, "classname"),
1477 "Received a result for object permission [%s] for user %d on object %s (class %s) at org %d",
1481 osrfHashGet(class, "classname"),
1485 if (dbi_result_first_row(result)) {
1486 jsonObject* return_val = oilsMakeJSONFromResult( result );
1487 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1491 "Status of object permission [%s] for user %d on object %s (class %s) at org %d is %s",
1495 osrfHashGet(class, "classname"),
1500 if ( *has_perm == 't' ) OK = 1;
1501 jsonObjectFree(return_val);
1504 dbi_result_free(result);
1509 osrfLogDebug( OSRF_LOG_MARK, "Checking non-object permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1510 result = dbi_conn_queryf(
1512 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1519 osrfLogDebug( OSRF_LOG_MARK, "Received a result for permission [%s] for user %d at org %d",
1520 perm, userid, atoi(context_org) );
1521 if ( dbi_result_first_row(result) ) {
1522 jsonObject* return_val = oilsMakeJSONFromResult( result );
1523 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1524 osrfLogDebug( OSRF_LOG_MARK, "Status of permission [%s] for user %d at org %d is [%s]",
1525 perm, userid, atoi(context_org), has_perm );
1526 if ( *has_perm == 't' ) OK = 1;
1527 jsonObjectFree(return_val);
1530 dbi_result_free(result);
1538 if (pkey_value) free(pkey_value);
1539 osrfStringArrayFree(context_org_array);
1545 * Look up the root of the org_unit tree. If you find it, return
1546 * a string containing the id, which the caller is responsible for freeing.
1547 * Otherwise return NULL.
1549 static char* org_tree_root( osrfMethodContext* ctx ) {
1551 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1552 static time_t last_lookup_time = 0;
1553 time_t current_time = time( NULL );
1555 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1556 // We successfully looked this up less than an hour ago.
1557 // It's not likely to have changed since then.
1558 return strdup( cached_root_id );
1560 last_lookup_time = current_time;
1563 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1564 jsonObject* result = doFieldmapperSearch(
1565 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1566 jsonObjectFree( where_clause );
1568 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1571 jsonObjectFree( result );
1573 growing_buffer* msg = buffer_init(128);
1574 OSRF_BUFFER_ADD( msg, MODULENAME );
1575 OSRF_BUFFER_ADD( msg,
1576 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1578 char* m = buffer_release(msg);
1579 osrfAppSessionStatus( ctx->session,
1580 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1583 cached_root_id[ 0 ] = '\0';
1587 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1588 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1590 jsonObjectFree( result );
1592 strcpy( cached_root_id, root_org_unit_id );
1593 return root_org_unit_id;
1597 @brief Create a JSON_HASH with a single key/value pair.
1598 @param key The key of the key/value pair.
1599 @param value the value of the key/value pair.
1600 @return Pointer to a newly created jsonObject of type JSON_HASH.
1602 The value of the key/value is either a string or (if @a value is NULL) a null.
1604 static jsonObject* single_hash( const char* key, const char* value ) {
1606 if( ! key ) key = "";
1608 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1609 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1615 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1617 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1619 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1620 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1622 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1623 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1626 if (!verifyObjectClass(ctx, target)) {
1631 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1633 const char* trans_id = getXactId( ctx );
1635 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1637 osrfAppSessionStatus(
1639 OSRF_STATUS_BADREQUEST,
1640 "osrfMethodException",
1642 "No active transaction -- required for CREATE"
1648 // The following test is harmless but redundant. If a class is
1649 // readonly, we don't register a create method for it.
1650 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1651 osrfAppSessionStatus(
1653 OSRF_STATUS_BADREQUEST,
1654 "osrfMethodException",
1656 "Cannot INSERT readonly class"
1662 // Set the last_xact_id
1663 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1665 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1666 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1669 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1671 dbhandle = writehandle;
1673 osrfHash* fields = osrfHashGet(meta, "fields");
1674 char* pkey = osrfHashGet(meta, "primarykey");
1675 char* seq = osrfHashGet(meta, "sequence");
1677 growing_buffer* table_buf = buffer_init(128);
1678 growing_buffer* col_buf = buffer_init(128);
1679 growing_buffer* val_buf = buffer_init(128);
1681 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1682 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1683 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1684 buffer_add(val_buf,"VALUES (");
1688 osrfHash* field = NULL;
1689 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1690 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1692 const char* field_name = osrfHashIteratorKey( field_itr );
1694 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1697 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1700 if (field_object && field_object->classname) {
1701 value = oilsFMGetString(
1703 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1705 } else if( field_object && JSON_BOOL == field_object->type ) {
1706 if( jsonBoolIsTrue( field_object ) )
1707 value = strdup( "t" );
1709 value = strdup( "f" );
1711 value = jsonObjectToSimpleString( field_object );
1717 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1718 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1721 buffer_add(col_buf, field_name);
1723 if (!field_object || field_object->type == JSON_NULL) {
1724 buffer_add( val_buf, "DEFAULT" );
1726 } else if ( !strcmp(get_primitive( field ), "number") ) {
1727 const char* numtype = get_datatype( field );
1728 if ( !strcmp( numtype, "INT8") ) {
1729 buffer_fadd( val_buf, "%lld", atoll(value) );
1731 } else if ( !strcmp( numtype, "INT") ) {
1732 buffer_fadd( val_buf, "%d", atoi(value) );
1734 } else if ( !strcmp( numtype, "NUMERIC") ) {
1735 buffer_fadd( val_buf, "%f", atof(value) );
1738 if ( dbi_conn_quote_string(writehandle, &value) ) {
1739 OSRF_BUFFER_ADD( val_buf, value );
1742 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1743 osrfAppSessionStatus(
1745 OSRF_STATUS_INTERNALSERVERERROR,
1746 "osrfMethodException",
1748 "Error quoting string -- please see the error log for more details"
1751 buffer_free(table_buf);
1752 buffer_free(col_buf);
1753 buffer_free(val_buf);
1763 osrfHashIteratorFree( field_itr );
1765 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1766 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1768 char* table_str = buffer_release(table_buf);
1769 char* col_str = buffer_release(col_buf);
1770 char* val_str = buffer_release(val_buf);
1771 growing_buffer* sql = buffer_init(128);
1772 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1777 char* query = buffer_release(sql);
1779 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1782 dbi_result result = dbi_conn_query(writehandle, query);
1784 jsonObject* obj = NULL;
1787 obj = jsonNewObject(NULL);
1790 "%s ERROR inserting %s object using query [%s]",
1792 osrfHashGet(meta, "fieldmapper"),
1795 osrfAppSessionStatus(
1797 OSRF_STATUS_INTERNALSERVERERROR,
1798 "osrfMethodException",
1800 "INSERT error -- please see the error log for more details"
1805 char* id = oilsFMGetString(target, pkey);
1807 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1808 growing_buffer* _id = buffer_init(10);
1809 buffer_fadd(_id, "%lld", new_id);
1810 id = buffer_release(_id);
1813 // Find quietness specification, if present
1814 const char* quiet_str = NULL;
1816 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1818 quiet_str = jsonObjectGetString( quiet_obj );
1821 if( str_is_true( quiet_str ) ) { // if quietness is specified
1822 obj = jsonNewObject(id);
1826 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1827 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1829 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1831 jsonObjectFree( where_clause );
1836 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1839 jsonObjectFree( list );
1852 * Fetch one row from a specified table, using a specified value
1853 * for the primary key
1855 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1865 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1867 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos); // key value
1871 "%s retrieving %s object with primary key value of %s",
1873 osrfHashGet( class_def, "fieldmapper" ),
1874 jsonObjectGetString( id_obj )
1877 // Build a WHERE clause based on the key value
1878 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1881 osrfHashGet( class_def, "primarykey" ),
1882 jsonObjectClone( id_obj )
1885 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
1887 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
1889 jsonObjectFree( where_clause );
1893 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
1894 jsonObjectFree( list );
1897 if(!verifyObjectPCRUD(ctx, obj)) {
1898 jsonObjectFree(obj);
1901 growing_buffer* msg = buffer_init(128);
1902 OSRF_BUFFER_ADD( msg, MODULENAME );
1903 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1905 char* m = buffer_release(msg);
1906 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1917 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1918 growing_buffer* val_buf = buffer_init(32);
1919 const char* numtype = get_datatype( field );
1921 if ( !strncmp( numtype, "INT", 3 ) ) {
1922 if (value->type == JSON_NUMBER)
1923 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1924 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1926 //const char* val_str = jsonObjectGetString( value );
1927 //buffer_fadd( val_buf, "%ld", atol(val_str) );
1928 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1931 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1932 if (value->type == JSON_NUMBER)
1933 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
1934 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1936 //const char* val_str = jsonObjectGetString( value );
1937 //buffer_fadd( val_buf, "%f", atof(val_str) );
1938 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1942 // Presumably this was really intended ot be a string, so quote it
1943 char* str = jsonObjectToSimpleString( value );
1944 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1945 OSRF_BUFFER_ADD( val_buf, str );
1948 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1950 buffer_free(val_buf);
1955 return buffer_release(val_buf);
1958 static char* searchINPredicate (const char* class_alias, osrfHash* field,
1959 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1960 growing_buffer* sql_buf = buffer_init(32);
1966 osrfHashGet(field, "name")
1970 buffer_add(sql_buf, "IN (");
1971 } else if (!(strcasecmp(op,"not in"))) {
1972 buffer_add(sql_buf, "NOT IN (");
1974 buffer_add(sql_buf, "IN (");
1977 if (node->type == JSON_HASH) {
1978 // subquery predicate
1979 char* subpred = buildQuery( ctx, node, SUBSELECT );
1981 buffer_free( sql_buf );
1985 buffer_add(sql_buf, subpred);
1988 } else if (node->type == JSON_ARRAY) {
1989 // literal value list
1990 int in_item_index = 0;
1991 int in_item_first = 1;
1992 const jsonObject* in_item;
1993 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1998 buffer_add(sql_buf, ", ");
2001 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2002 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
2003 MODULENAME, json_type( in_item->type ) );
2004 buffer_free(sql_buf);
2008 // Append the literal value -- quoted if not a number
2009 if ( JSON_NUMBER == in_item->type ) {
2010 char* val = jsonNumberToDBString( field, in_item );
2011 OSRF_BUFFER_ADD( sql_buf, val );
2014 } else if ( !strcmp( get_primitive( field ), "number") ) {
2015 char* val = jsonNumberToDBString( field, in_item );
2016 OSRF_BUFFER_ADD( sql_buf, val );
2020 char* key_string = jsonObjectToSimpleString(in_item);
2021 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
2022 OSRF_BUFFER_ADD( sql_buf, key_string );
2025 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
2027 buffer_free(sql_buf);
2033 if( in_item_first ) {
2034 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
2035 buffer_free( sql_buf );
2039 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2040 MODULENAME, json_type( node->type ) );
2041 buffer_free(sql_buf);
2045 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2047 return buffer_release(sql_buf);
2050 // Receive a JSON_ARRAY representing a function call. The first
2051 // entry in the array is the function name. The rest are parameters.
2052 static char* searchValueTransform( const jsonObject* array ) {
2054 if( array->size < 1 ) {
2055 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
2059 // Get the function name
2060 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2061 if( func_item->type != JSON_STRING ) {
2062 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2063 MODULENAME, json_type( func_item->type ) );
2067 growing_buffer* sql_buf = buffer_init(32);
2069 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2070 OSRF_BUFFER_ADD( sql_buf, "( " );
2072 // Get the parameters
2073 int func_item_index = 1; // We already grabbed the zeroth entry
2074 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2076 // Add a separator comma, if we need one
2077 if( func_item_index > 2 )
2078 buffer_add( sql_buf, ", " );
2080 // Add the current parameter
2081 if (func_item->type == JSON_NULL) {
2082 buffer_add( sql_buf, "NULL" );
2084 char* val = jsonObjectToSimpleString(func_item);
2085 if ( dbi_conn_quote_string(dbhandle, &val) ) {
2086 OSRF_BUFFER_ADD( sql_buf, val );
2089 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2090 buffer_free(sql_buf);
2097 buffer_add( sql_buf, " )" );
2099 return buffer_release(sql_buf);
2102 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
2103 const jsonObject* node, const char* op) {
2105 if( ! is_good_operator( op ) ) {
2106 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2110 char* val = searchValueTransform(node);
2114 growing_buffer* sql_buf = buffer_init(32);
2119 osrfHashGet(field, "name"),
2126 return buffer_release(sql_buf);
2129 // class_alias is a class name or other table alias
2130 // field is a field definition as stored in the IDL
2131 // node comes from the method parameter, and may represent an entry in the SELECT list
2132 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
2133 growing_buffer* sql_buf = buffer_init(32);
2135 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
2136 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
2138 if(transform_subcolumn) {
2139 if( ! is_identifier( transform_subcolumn ) ) {
2140 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2141 MODULENAME, transform_subcolumn );
2142 buffer_free( sql_buf );
2145 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2148 if (field_transform) {
2150 if( ! is_identifier( field_transform ) ) {
2151 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2152 MODULENAME, field_transform );
2153 buffer_free( sql_buf );
2157 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class_alias, osrfHashGet(field, "name"));
2158 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2161 if( array->type != JSON_ARRAY ) {
2162 osrfLogError( OSRF_LOG_MARK,
2163 "%s: Expected JSON_ARRAY for function params; found %s",
2164 MODULENAME, json_type( array->type ) );
2165 buffer_free( sql_buf );
2168 int func_item_index = 0;
2169 jsonObject* func_item;
2170 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2172 char* val = jsonObjectToSimpleString(func_item);
2175 buffer_add( sql_buf, ",NULL" );
2176 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
2177 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2178 OSRF_BUFFER_ADD( sql_buf, val );
2180 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2182 buffer_free(sql_buf);
2189 buffer_add( sql_buf, " )" );
2192 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2195 if (transform_subcolumn)
2196 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2198 return buffer_release(sql_buf);
2201 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2202 const jsonObject* node, const char* op ) {
2204 if( ! is_good_operator( op ) ) {
2205 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2209 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2210 if( ! field_transform )
2213 int extra_parens = 0; // boolean
2215 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2216 if ( ! value_obj ) {
2217 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2219 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
2220 free(field_transform);
2224 } else if ( value_obj->type == JSON_ARRAY ) {
2225 value = searchValueTransform( value_obj );
2227 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
2228 free( field_transform );
2231 } else if ( value_obj->type == JSON_HASH ) {
2232 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2234 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
2235 free(field_transform);
2239 } else if ( value_obj->type == JSON_NUMBER ) {
2240 value = jsonNumberToDBString( field, value_obj );
2241 } else if ( value_obj->type == JSON_NULL ) {
2242 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
2243 free(field_transform);
2245 } else if ( value_obj->type == JSON_BOOL ) {
2246 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
2247 free(field_transform);
2250 if ( !strcmp( get_primitive( field ), "number") ) {
2251 value = jsonNumberToDBString( field, value_obj );
2253 value = jsonObjectToSimpleString( value_obj );
2254 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2255 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
2257 free(field_transform);
2263 const char* left_parens = "";
2264 const char* right_parens = "";
2266 if( extra_parens ) {
2271 growing_buffer* sql_buf = buffer_init(32);
2275 "%s%s %s %s %s %s%s",
2286 free(field_transform);
2288 return buffer_release(sql_buf);
2291 static char* searchSimplePredicate (const char* op, const char* class_alias,
2292 osrfHash* field, const jsonObject* node) {
2294 if( ! is_good_operator( op ) ) {
2295 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2301 // Get the value to which we are comparing the specified column
2302 if (node->type != JSON_NULL) {
2303 if ( node->type == JSON_NUMBER ) {
2304 val = jsonNumberToDBString( field, node );
2305 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2306 val = jsonNumberToDBString( field, node );
2308 val = jsonObjectToSimpleString(node);
2313 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2314 // Value is not numeric; enclose it in quotes
2315 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2316 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2322 // Compare to a null value
2323 val = strdup( "NULL" );
2324 if (strcmp( op, "=" ))
2330 growing_buffer* sql_buf = buffer_init(32);
2331 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2332 char* pred = buffer_release( sql_buf );
2339 static char* searchBETWEENPredicate (const char* class_alias,
2340 osrfHash* field, const jsonObject* node) {
2342 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2343 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2345 if( NULL == y_node ) {
2346 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2349 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2350 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2357 if ( !strcmp( get_primitive( field ), "number") ) {
2358 x_string = jsonNumberToDBString(field, x_node);
2359 y_string = jsonNumberToDBString(field, y_node);
2362 x_string = jsonObjectToSimpleString(x_node);
2363 y_string = jsonObjectToSimpleString(y_node);
2364 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2365 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2366 MODULENAME, x_string, y_string);
2373 growing_buffer* sql_buf = buffer_init(32);
2374 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2375 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2379 return buffer_release(sql_buf);
2382 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2383 jsonObject* node, osrfMethodContext* ctx ) {
2386 if (node->type == JSON_ARRAY) { // equality IN search
2387 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2388 } else if (node->type == JSON_HASH) { // other search
2389 jsonIterator* pred_itr = jsonNewIterator( node );
2390 if( !jsonIteratorHasNext( pred_itr ) ) {
2391 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2392 MODULENAME, osrfHashGet(field, "name") );
2394 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2396 // Verify that there are no additional predicates
2397 if( jsonIteratorHasNext( pred_itr ) ) {
2398 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2399 MODULENAME, osrfHashGet(field, "name") );
2400 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2401 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2402 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2403 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
2404 else if ( pred_node->type == JSON_ARRAY )
2405 pred = searchFunctionPredicate( class_info->alias, field, pred_node, pred_itr->key );
2406 else if ( pred_node->type == JSON_HASH )
2407 pred = searchFieldTransformPredicate( class_info, field, pred_node, pred_itr->key );
2409 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2411 jsonIteratorFree(pred_itr);
2413 } else if (node->type == JSON_NULL) { // IS NULL search
2414 growing_buffer* _p = buffer_init(64);
2417 "\"%s\".%s IS NULL",
2418 class_info->class_name,
2419 osrfHashGet(field, "name")
2421 pred = buffer_release(_p);
2422 } else { // equality search
2423 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2442 field : call_number,
2458 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2460 const jsonObject* working_hash;
2461 jsonObject* freeable_hash = NULL;
2463 if (join_hash->type == JSON_HASH) {
2464 working_hash = join_hash;
2465 } else if (join_hash->type == JSON_STRING) {
2466 // turn it into a JSON_HASH by creating a wrapper
2467 // around a copy of the original
2468 const char* _tmp = jsonObjectGetString( join_hash );
2469 freeable_hash = jsonNewObjectType(JSON_HASH);
2470 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2471 working_hash = freeable_hash;
2475 "%s: JOIN failed; expected JSON object type not found",
2481 growing_buffer* join_buf = buffer_init(128);
2482 const char* leftclass = left_info->class_name;
2484 jsonObject* snode = NULL;
2485 jsonIterator* search_itr = jsonNewIterator( working_hash );
2487 while ( (snode = jsonIteratorNext( search_itr )) ) {
2488 const char* right_alias = search_itr->key;
2490 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2492 class = right_alias;
2494 const ClassInfo* right_info = add_joined_class( right_alias, class );
2498 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2502 jsonIteratorFree( search_itr );
2503 buffer_free( join_buf );
2505 jsonObjectFree( freeable_hash );
2508 osrfHash* links = right_info->links;
2509 const char* table = right_info->source_def;
2511 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2512 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2514 if (field && !fkey) {
2515 // Look up the corresponding join column in the IDL.
2516 // The link must be defined in the child table,
2517 // and point to the right parent table.
2518 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2519 const char* reltype = NULL;
2520 const char* other_class = NULL;
2521 reltype = osrfHashGet( idl_link, "reltype" );
2522 if( reltype && strcmp( reltype, "has_many" ) )
2523 other_class = osrfHashGet( idl_link, "class" );
2524 if( other_class && !strcmp( other_class, leftclass ) )
2525 fkey = osrfHashGet( idl_link, "key" );
2529 "%s: JOIN failed. No link defined from %s.%s to %s",
2535 buffer_free(join_buf);
2537 jsonObjectFree(freeable_hash);
2538 jsonIteratorFree(search_itr);
2542 } else if (!field && fkey) {
2543 // Look up the corresponding join column in the IDL.
2544 // The link must be defined in the child table,
2545 // and point to the right parent table.
2546 osrfHash* left_links = left_info->links;
2547 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2548 const char* reltype = NULL;
2549 const char* other_class = NULL;
2550 reltype = osrfHashGet( idl_link, "reltype" );
2551 if( reltype && strcmp( reltype, "has_many" ) )
2552 other_class = osrfHashGet( idl_link, "class" );
2553 if( other_class && !strcmp( other_class, class ) )
2554 field = osrfHashGet( idl_link, "key" );
2558 "%s: JOIN failed. No link defined from %s.%s to %s",
2564 buffer_free(join_buf);
2566 jsonObjectFree(freeable_hash);
2567 jsonIteratorFree(search_itr);
2571 } else if (!field && !fkey) {
2572 osrfHash* left_links = left_info->links;
2574 // For each link defined for the left class:
2575 // see if the link references the joined class
2576 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2577 osrfHash* curr_link = NULL;
2578 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2579 const char* other_class = osrfHashGet( curr_link, "class" );
2580 if( other_class && !strcmp( other_class, class ) ) {
2582 // In the IDL, the parent class doesn't know then names of the child
2583 // columns that are pointing to it, so don't use that end of the link
2584 const char* reltype = osrfHashGet( curr_link, "reltype" );
2585 if( reltype && strcmp( reltype, "has_many" ) ) {
2586 // Found a link between the classes
2587 fkey = osrfHashIteratorKey( itr );
2588 field = osrfHashGet( curr_link, "key" );
2593 osrfHashIteratorFree( itr );
2595 if (!field || !fkey) {
2596 // Do another such search, with the classes reversed
2598 // For each link defined for the joined class:
2599 // see if the link references the left class
2600 osrfHashIterator* itr = osrfNewHashIterator( links );
2601 osrfHash* curr_link = NULL;
2602 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2603 const char* other_class = osrfHashGet( curr_link, "class" );
2604 if( other_class && !strcmp( other_class, leftclass ) ) {
2606 // In the IDL, the parent class doesn't know then names of the child
2607 // columns that are pointing to it, so don't use that end of the link
2608 const char* reltype = osrfHashGet( curr_link, "reltype" );
2609 if( reltype && strcmp( reltype, "has_many" ) ) {
2610 // Found a link between the classes
2611 field = osrfHashIteratorKey( itr );
2612 fkey = osrfHashGet( curr_link, "key" );
2617 osrfHashIteratorFree( itr );
2620 if (!field || !fkey) {
2623 "%s: JOIN failed. No link defined between %s and %s",
2628 buffer_free(join_buf);
2630 jsonObjectFree(freeable_hash);
2631 jsonIteratorFree(search_itr);
2637 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2639 if ( !strcasecmp(type,"left") ) {
2640 buffer_add(join_buf, " LEFT JOIN");
2641 } else if ( !strcasecmp(type,"right") ) {
2642 buffer_add(join_buf, " RIGHT JOIN");
2643 } else if ( !strcasecmp(type,"full") ) {
2644 buffer_add(join_buf, " FULL JOIN");
2646 buffer_add(join_buf, " INNER JOIN");
2649 buffer_add(join_buf, " INNER JOIN");
2652 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2653 table, right_alias, right_alias, field, left_info->alias, fkey);
2655 // Add any other join conditions as specified by "filter"
2656 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2658 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2659 if ( filter_op && !strcasecmp("or",filter_op) ) {
2660 buffer_add( join_buf, " OR " );
2662 buffer_add( join_buf, " AND " );
2665 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2667 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2668 OSRF_BUFFER_ADD( join_buf, jpred );
2673 "%s: JOIN failed. Invalid conditional expression.",
2676 jsonIteratorFree( search_itr );
2677 buffer_free( join_buf );
2679 jsonObjectFree( freeable_hash );
2684 buffer_add(join_buf, " ) ");
2686 // Recursively add a nested join, if one is present
2687 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2689 char* jpred = searchJOIN( join_filter, right_info );
2691 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2692 OSRF_BUFFER_ADD( join_buf, jpred );
2695 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2696 jsonIteratorFree( search_itr );
2697 buffer_free( join_buf );
2699 jsonObjectFree( freeable_hash );
2706 jsonObjectFree(freeable_hash);
2707 jsonIteratorFree(search_itr);
2709 return buffer_release(join_buf);
2714 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2715 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2716 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2718 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2720 search_hash is the JSON expression of the conditions.
2721 meta is the class definition from the IDL, for the relevant table.
2722 opjoin_type indicates whether multiple conditions, if present, should be
2723 connected by AND or OR.
2724 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2725 to pass it to other functions -- and all they do with it is to use the session
2726 and request members to send error messages back to the client.
2730 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
2731 int opjoin_type, osrfMethodContext* ctx ) {
2735 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2738 class_info->class_def,
2743 growing_buffer* sql_buf = buffer_init(128);
2745 jsonObject* node = NULL;
2748 if ( search_hash->type == JSON_ARRAY ) {
2749 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2750 if( 0 == search_hash->size ) {
2753 "%s: Invalid predicate structure: empty JSON array",
2756 buffer_free( sql_buf );
2760 unsigned long i = 0;
2761 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
2765 if (opjoin_type == OR_OP_JOIN)
2766 buffer_add(sql_buf, " OR ");
2768 buffer_add(sql_buf, " AND ");
2771 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2773 buffer_free( sql_buf );
2777 buffer_fadd(sql_buf, "( %s )", subpred);
2781 } else if ( search_hash->type == JSON_HASH ) {
2782 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2783 jsonIterator* search_itr = jsonNewIterator( search_hash );
2784 if( !jsonIteratorHasNext( search_itr ) ) {
2787 "%s: Invalid predicate structure: empty JSON object",
2790 jsonIteratorFree( search_itr );
2791 buffer_free( sql_buf );
2795 while ( (node = jsonIteratorNext( search_itr )) ) {
2800 if (opjoin_type == OR_OP_JOIN)
2801 buffer_add(sql_buf, " OR ");
2803 buffer_add(sql_buf, " AND ");
2806 if ( '+' == search_itr->key[ 0 ] ) {
2808 // This plus sign prefixes a class name or other table alias;
2809 // make sure the table alias is in scope
2810 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2811 if( ! alias_info ) {
2814 "%s: Invalid table alias \"%s\" in WHERE clause",
2818 jsonIteratorFree( search_itr );
2819 buffer_free( sql_buf );
2823 if ( node->type == JSON_STRING ) {
2824 // It's the name of a column; make sure it belongs to the class
2825 const char* fieldname = jsonObjectGetString( node );
2826 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2829 "%s: Invalid column name \"%s\" in WHERE clause for table alias \"%s\"",
2834 jsonIteratorFree( search_itr );
2835 buffer_free( sql_buf );
2839 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
2841 // It's something more complicated
2842 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
2844 jsonIteratorFree( search_itr );
2845 buffer_free( sql_buf );
2849 buffer_fadd(sql_buf, "( %s )", subpred);
2852 } else if ( '-' == search_itr->key[ 0 ] ) {
2853 if ( !strcasecmp("-or",search_itr->key) ) {
2854 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
2856 jsonIteratorFree( search_itr );
2857 buffer_free( sql_buf );
2861 buffer_fadd(sql_buf, "( %s )", subpred);
2863 } else if ( !strcasecmp("-and",search_itr->key) ) {
2864 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2866 jsonIteratorFree( search_itr );
2867 buffer_free( sql_buf );
2871 buffer_fadd(sql_buf, "( %s )", subpred);
2873 } else if ( !strcasecmp("-not",search_itr->key) ) {
2874 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2876 jsonIteratorFree( search_itr );
2877 buffer_free( sql_buf );
2881 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2883 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2884 char* subpred = buildQuery( ctx, node, SUBSELECT );
2886 jsonIteratorFree( search_itr );
2887 buffer_free( sql_buf );
2891 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2893 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2894 char* subpred = buildQuery( ctx, node, SUBSELECT );
2896 jsonIteratorFree( search_itr );
2897 buffer_free( sql_buf );
2901 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2903 } else { // Invalid "minus" operator
2906 "%s: Invalid operator \"%s\" in WHERE clause",
2910 jsonIteratorFree( search_itr );
2911 buffer_free( sql_buf );
2917 const char* class = class_info->class_name;
2918 osrfHash* fields = class_info->fields;
2919 osrfHash* field = osrfHashGet( fields, search_itr->key );
2922 const char* table = class_info->source_def;
2925 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2928 table ? table : "?",
2931 jsonIteratorFree(search_itr);
2932 buffer_free(sql_buf);
2936 char* subpred = searchPredicate( class_info, field, node, ctx );
2938 buffer_free(sql_buf);
2939 jsonIteratorFree(search_itr);
2943 buffer_add( sql_buf, subpred );
2947 jsonIteratorFree(search_itr);
2950 // ERROR ... only hash and array allowed at this level
2951 char* predicate_string = jsonObjectToJSON( search_hash );
2954 "%s: Invalid predicate structure: %s",
2958 buffer_free(sql_buf);
2959 free(predicate_string);
2963 return buffer_release(sql_buf);
2966 /* Build a JSON_ARRAY of field names for a given table alias
2968 static jsonObject* defaultSelectList( const char* table_alias ) {
2973 ClassInfo* class_info = search_all_alias( table_alias );
2974 if( ! class_info ) {
2977 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
2984 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
2985 osrfHash* field_def = NULL;
2986 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
2987 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2988 const char* field_name = osrfHashIteratorKey( field_itr );
2989 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2990 jsonObjectPush( array, jsonNewObject( field_name ) );
2993 osrfHashIteratorFree( field_itr );
2998 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
2999 // The jsonObject must be a JSON_HASH with an single entry for "union",
3000 // "intersect", or "except". The data associated with this key must be an
3001 // array of hashes, each hash being a query.
3002 // Also allowed but currently ignored: entries for "order_by" and "alias".
3003 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3005 if( ! combo || combo->type != JSON_HASH )
3006 return NULL; // should be impossible; validated by caller
3008 const jsonObject* query_array = NULL; // array of subordinate queries
3009 const char* op = NULL; // name of operator, e.g. UNION
3010 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3011 int op_count = 0; // for detecting conflicting operators
3012 int excepting = 0; // boolean
3013 int all = 0; // boolean
3014 jsonObject* order_obj = NULL;
3016 // Identify the elements in the hash
3017 jsonIterator* query_itr = jsonNewIterator( combo );
3018 jsonObject* curr_obj = NULL;
3019 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3020 if( ! strcmp( "union", query_itr->key ) ) {
3023 query_array = curr_obj;
3024 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3027 query_array = curr_obj;
3028 } else if( ! strcmp( "except", query_itr->key ) ) {
3032 query_array = curr_obj;
3033 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3036 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3039 order_obj = curr_obj;
3040 } else if( ! strcmp( "alias", query_itr->key ) ) {
3041 if( curr_obj->type != JSON_STRING ) {
3042 jsonIteratorFree( query_itr );
3045 alias = jsonObjectGetString( curr_obj );
3046 } else if( ! strcmp( "all", query_itr->key ) ) {
3047 if( obj_is_true( curr_obj ) )
3051 osrfAppSessionStatus(
3053 OSRF_STATUS_INTERNALSERVERERROR,
3054 "osrfMethodException",
3056 "Malformed query; unexpected entry in query object"
3060 "%s: Unexpected entry for \"%s\" in%squery",
3065 jsonIteratorFree( query_itr );
3069 jsonIteratorFree( query_itr );
3071 // More sanity checks
3072 if( ! query_array ) {
3074 osrfAppSessionStatus(
3076 OSRF_STATUS_INTERNALSERVERERROR,
3077 "osrfMethodException",
3079 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3083 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3086 return NULL; // should be impossible...
3087 } else if( op_count > 1 ) {
3089 osrfAppSessionStatus(
3091 OSRF_STATUS_INTERNALSERVERERROR,
3092 "osrfMethodException",
3094 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3098 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3102 } if( query_array->type != JSON_ARRAY ) {
3104 osrfAppSessionStatus(
3106 OSRF_STATUS_INTERNALSERVERERROR,
3107 "osrfMethodException",
3109 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3113 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3116 json_type( query_array->type )
3119 } if( query_array->size < 2 ) {
3121 osrfAppSessionStatus(
3123 OSRF_STATUS_INTERNALSERVERERROR,
3124 "osrfMethodException",
3126 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3130 "%s:%srequires multiple queries as operands",
3135 } else if( excepting && query_array->size > 2 ) {
3137 osrfAppSessionStatus(
3139 OSRF_STATUS_INTERNALSERVERERROR,
3140 "osrfMethodException",
3142 "EXCEPT operator has too many queries as operands"
3146 "%s:EXCEPT operator has too many queries as operands",
3150 } else if( order_obj && ! alias ) {
3152 osrfAppSessionStatus(
3154 OSRF_STATUS_INTERNALSERVERERROR,
3155 "osrfMethodException",
3157 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3161 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3167 // So far so good. Now build the SQL.
3168 growing_buffer* sql = buffer_init( 256 );
3170 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3171 // Add a layer of parentheses
3172 if( flags & SUBCOMBO )
3173 OSRF_BUFFER_ADD( sql, "( " );
3175 // Traverse the query array. Each entry should be a hash.
3176 int first = 1; // boolean
3178 jsonObject* query = NULL;
3179 while((query = jsonObjectGetIndex( query_array, i++ ) )) {
3180 if( query->type != JSON_HASH ) {
3182 osrfAppSessionStatus(
3184 OSRF_STATUS_INTERNALSERVERERROR,
3185 "osrfMethodException",
3187 "Malformed query under UNION, INTERSECT or EXCEPT"
3191 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3194 json_type( query->type )
3203 OSRF_BUFFER_ADD( sql, op );
3205 OSRF_BUFFER_ADD( sql, "ALL " );
3208 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3212 "%s: Error building query under%s",
3220 OSRF_BUFFER_ADD( sql, query_str );
3223 if( flags & SUBCOMBO )
3224 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3226 if ( !(flags & SUBSELECT) )
3227 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3229 return buffer_release( sql );
3232 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3233 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3234 // or "except" to indicate the type of query.
3235 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3239 osrfAppSessionStatus(
3241 OSRF_STATUS_INTERNALSERVERERROR,
3242 "osrfMethodException",
3244 "Malformed query; no query object"
3246 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", MODULENAME );
3248 } else if( query->type != JSON_HASH ) {
3250 osrfAppSessionStatus(
3252 OSRF_STATUS_INTERNALSERVERERROR,
3253 "osrfMethodException",
3255 "Malformed query object"
3259 "%s: Query object is %s instead of JSON_HASH",
3261 json_type( query->type )
3266 // Determine what kind of query it purports to be, and dispatch accordingly.
3267 if( jsonObjectGetKey( query, "union" ) ||
3268 jsonObjectGetKey( query, "intersect" ) ||
3269 jsonObjectGetKey( query, "except" ) ) {
3270 return doCombo( ctx, query, flags );
3272 // It is presumably a SELECT query
3274 // Push a node onto the stack for the current query. Every level of
3275 // subquery gets its own QueryFrame on the Stack.
3278 // Build an SQL SELECT statement
3281 jsonObjectGetKey( query, "select" ),
3282 jsonObjectGetKey( query, "from" ),
3283 jsonObjectGetKey( query, "where" ),
3284 jsonObjectGetKey( query, "having" ),
3285 jsonObjectGetKey( query, "order_by" ),
3286 jsonObjectGetKey( query, "limit" ),
3287 jsonObjectGetKey( query, "offset" ),
3296 /* method context */ osrfMethodContext* ctx,
3298 /* SELECT */ jsonObject* selhash,
3299 /* FROM */ jsonObject* join_hash,
3300 /* WHERE */ jsonObject* search_hash,
3301 /* HAVING */ jsonObject* having_hash,
3302 /* ORDER BY */ jsonObject* order_hash,
3303 /* LIMIT */ jsonObject* limit,
3304 /* OFFSET */ jsonObject* offset,
3305 /* flags */ int flags
3307 const char* locale = osrf_message_get_last_locale();
3309 // general tmp objects
3310 const jsonObject* tmp_const;
3311 jsonObject* selclass = NULL;
3312 jsonObject* snode = NULL;
3313 jsonObject* onode = NULL;
3315 char* string = NULL;
3316 int from_function = 0;
3321 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3323 // punt if there's no FROM clause
3324 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3327 "%s: FROM clause is missing or empty",
3331 osrfAppSessionStatus(
3333 OSRF_STATUS_INTERNALSERVERERROR,
3334 "osrfMethodException",
3336 "FROM clause is missing or empty in JSON query"
3341 // the core search class
3342 const char* core_class = NULL;
3344 // get the core class -- the only key of the top level FROM clause, or a string
3345 if (join_hash->type == JSON_HASH) {
3346 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3347 snode = jsonIteratorNext( tmp_itr );
3349 // Populate the current QueryFrame with information
3350 // about the core class
3351 if( add_query_core( NULL, tmp_itr->key ) ) {
3353 osrfAppSessionStatus(
3355 OSRF_STATUS_INTERNALSERVERERROR,
3356 "osrfMethodException",
3358 "Unable to look up core class"
3362 core_class = curr_query->core.class_name;
3365 jsonObject* extra = jsonIteratorNext( tmp_itr );
3367 jsonIteratorFree( tmp_itr );
3370 // There shouldn't be more than one entry in join_hash
3374 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3378 osrfAppSessionStatus(
3380 OSRF_STATUS_INTERNALSERVERERROR,
3381 "osrfMethodException",
3383 "Malformed FROM clause in JSON query"
3385 return NULL; // Malformed join_hash; extra entry
3387 } else if (join_hash->type == JSON_ARRAY) {
3388 // We're selecting from a function, not from a table
3390 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
3393 } else if (join_hash->type == JSON_STRING) {
3394 // Populate the current QueryFrame with information
3395 // about the core class
3396 core_class = jsonObjectGetString( join_hash );
3398 if( add_query_core( NULL, core_class ) ) {
3400 osrfAppSessionStatus(
3402 OSRF_STATUS_INTERNALSERVERERROR,
3403 "osrfMethodException",
3405 "Unable to look up core class"
3413 "%s: FROM clause is unexpected JSON type: %s",
3415 json_type( join_hash->type )
3418 osrfAppSessionStatus(
3420 OSRF_STATUS_INTERNALSERVERERROR,
3421 "osrfMethodException",
3423 "Ill-formed FROM clause in JSON query"
3428 // Build the join clause, if any, while filling out the list
3429 // of joined classes in the current QueryFrame.
3430 char* join_clause = NULL;
3431 if( join_hash && ! from_function ) {
3433 join_clause = searchJOIN( join_hash, &curr_query->core );
3434 if( ! join_clause ) {
3436 osrfAppSessionStatus(
3438 OSRF_STATUS_INTERNALSERVERERROR,
3439 "osrfMethodException",
3441 "Unable to construct JOIN clause(s)"
3447 // For in case we don't get a select list
3448 jsonObject* defaultselhash = NULL;
3450 // if there is no select list, build a default select list ...
3451 if (!selhash && !from_function) {
3452 jsonObject* default_list = defaultSelectList( core_class );
3453 if( ! default_list ) {
3455 osrfAppSessionStatus(
3457 OSRF_STATUS_INTERNALSERVERERROR,
3458 "osrfMethodException",
3460 "Unable to build default SELECT clause in JSON query"
3462 free( join_clause );
3467 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3468 jsonObjectSetKey( selhash, core_class, default_list );
3471 // The SELECT clause can be encoded only by a hash
3472 if( !from_function && selhash->type != JSON_HASH ) {
3475 "%s: Expected JSON_HASH for SELECT clause; found %s",
3477 json_type( selhash->type )
3481 osrfAppSessionStatus(
3483 OSRF_STATUS_INTERNALSERVERERROR,
3484 "osrfMethodException",
3486 "Malformed SELECT clause in JSON query"
3488 free( join_clause );
3492 // If you see a null or wild card specifier for the core class, or an
3493 // empty array, replace it with a default SELECT list
3494 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3496 int default_needed = 0; // boolean
3497 if( JSON_STRING == tmp_const->type
3498 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3500 else if( JSON_NULL == tmp_const->type )
3503 if( default_needed ) {
3504 // Build a default SELECT list
3505 jsonObject* default_list = defaultSelectList( core_class );
3506 if( ! default_list ) {
3508 osrfAppSessionStatus(
3510 OSRF_STATUS_INTERNALSERVERERROR,
3511 "osrfMethodException",
3513 "Can't build default SELECT clause in JSON query"
3515 free( join_clause );
3520 jsonObjectSetKey( selhash, core_class, default_list );
3524 // temp buffers for the SELECT list and GROUP BY clause
3525 growing_buffer* select_buf = buffer_init(128);
3526 growing_buffer* group_buf = buffer_init(128);
3528 int aggregate_found = 0; // boolean
3530 // Build a select list
3531 if(from_function) // From a function we select everything
3532 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3535 // Build the SELECT list as SQL
3539 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3540 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3542 const char* cname = selclass_itr->key;
3544 // Make sure the target relation is in the FROM clause.
3546 // At this point join_hash is a step down from the join_hash we
3547 // received as a parameter. If the original was a JSON_STRING,
3548 // then json_hash is now NULL. If the original was a JSON_HASH,
3549 // then json_hash is now the first (and only) entry in it,
3550 // denoting the core class. We've already excluded the
3551 // possibility that the original was a JSON_ARRAY, because in
3552 // that case from_function would be non-NULL, and we wouldn't
3555 // If the current table alias isn't in scope, bail out
3556 ClassInfo* class_info = search_alias( cname );
3557 if( ! class_info ) {
3560 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3565 osrfAppSessionStatus(
3567 OSRF_STATUS_INTERNALSERVERERROR,
3568 "osrfMethodException",
3570 "Selected class not in FROM clause in JSON query"
3572 jsonIteratorFree( selclass_itr );
3573 buffer_free( select_buf );
3574 buffer_free( group_buf );
3575 if( defaultselhash ) jsonObjectFree( defaultselhash );
3576 free( join_clause );
3580 if( selclass->type != JSON_ARRAY ) {
3583 "%s: Malformed SELECT list for class \"%s\"; not an array",
3588 osrfAppSessionStatus(
3590 OSRF_STATUS_INTERNALSERVERERROR,
3591 "osrfMethodException",
3593 "Selected class not in FROM clause in JSON query"
3596 jsonIteratorFree( selclass_itr );
3597 buffer_free( select_buf );
3598 buffer_free( group_buf );
3599 if( defaultselhash ) jsonObjectFree( defaultselhash );
3600 free( join_clause );
3604 // Look up some attributes of the current class
3605 osrfHash* idlClass = class_info->class_def;
3606 osrfHash* class_field_set = class_info->fields;
3607 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3608 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3610 if( 0 == selclass->size ) {
3613 "%s: No columns selected from \"%s\"",
3619 // stitch together the column list for the current table alias...
3620 unsigned long field_idx = 0;
3621 jsonObject* selfield = NULL;
3622 while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3624 // If we need a separator comma, add one
3628 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3631 // if the field specification is a string, add it to the list
3632 if (selfield->type == JSON_STRING) {
3634 // Look up the field in the IDL
3635 const char* col_name = jsonObjectGetString( selfield );
3636 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3638 // No such field in current class
3641 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3647 osrfAppSessionStatus(
3649 OSRF_STATUS_INTERNALSERVERERROR,
3650 "osrfMethodException",
3652 "Selected column not defined in JSON query"
3654 jsonIteratorFree( selclass_itr );
3655 buffer_free( select_buf );
3656 buffer_free( group_buf );
3657 if( defaultselhash ) jsonObjectFree( defaultselhash );
3658 free( join_clause );
3660 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3661 // Virtual field not allowed
3664 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3670 osrfAppSessionStatus(
3672 OSRF_STATUS_INTERNALSERVERERROR,
3673 "osrfMethodException",
3675 "Selected column may not be virtual in JSON query"
3677 jsonIteratorFree( selclass_itr );
3678 buffer_free( select_buf );
3679 buffer_free( group_buf );
3680 if( defaultselhash ) jsonObjectFree( defaultselhash );
3681 free( join_clause );
3687 if (flags & DISABLE_I18N)
3690 i18n = osrfHashGet(field_def, "i18n");
3692 if( str_is_true( i18n ) ) {
3693 buffer_fadd( select_buf,
3694 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3695 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3697 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3700 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3703 // ... but it could be an object, in which case we check for a Field Transform
3704 } else if (selfield->type == JSON_HASH) {
3706 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3708 // Get the field definition from the IDL
3709 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3711 // No such field in current class
3714 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3720 osrfAppSessionStatus(
3722 OSRF_STATUS_INTERNALSERVERERROR,
3723 "osrfMethodException",
3725 "Selected column is not defined in JSON query"
3727 jsonIteratorFree( selclass_itr );
3728 buffer_free( select_buf );
3729 buffer_free( group_buf );
3730 if( defaultselhash ) jsonObjectFree( defaultselhash );
3731 free( join_clause );
3733 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3734 // No such field in current class
3737 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3743 osrfAppSessionStatus(
3745 OSRF_STATUS_INTERNALSERVERERROR,
3746 "osrfMethodException",
3748 "Selected column is virtual in JSON query"
3750 jsonIteratorFree( selclass_itr );
3751 buffer_free( select_buf );
3752 buffer_free( group_buf );
3753 if( defaultselhash ) jsonObjectFree( defaultselhash );
3754 free( join_clause );
3758 // Decide what to use as a column alias
3760 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3761 _alias = jsonObjectGetString( tmp_const );
3762 } else { // Use field name as the alias
3766 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3767 char* transform_str = searchFieldTransform(class_info->alias, field_def, selfield);
3768 if( transform_str ) {
3769 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3770 free(transform_str);
3773 osrfAppSessionStatus(
3775 OSRF_STATUS_INTERNALSERVERERROR,
3776 "osrfMethodException",
3778 "Unable to generate transform function in JSON query"
3780 jsonIteratorFree( selclass_itr );
3781 buffer_free( select_buf );
3782 buffer_free( group_buf );
3783 if( defaultselhash ) jsonObjectFree( defaultselhash );
3784 free( join_clause );
3791 if (flags & DISABLE_I18N)
3794 i18n = osrfHashGet(field_def, "i18n");
3796 if( str_is_true( i18n ) ) {
3797 buffer_fadd( select_buf,
3798 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3799 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3801 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3804 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3811 "%s: Selected item is unexpected JSON type: %s",
3813 json_type( selfield->type )
3816 osrfAppSessionStatus(
3818 OSRF_STATUS_INTERNALSERVERERROR,
3819 "osrfMethodException",
3821 "Ill-formed SELECT item in JSON query"
3823 jsonIteratorFree( selclass_itr );
3824 buffer_free( select_buf );
3825 buffer_free( group_buf );
3826 if( defaultselhash ) jsonObjectFree( defaultselhash );
3827 free( join_clause );
3831 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3832 if( obj_is_true( agg_obj ) )
3833 aggregate_found = 1;
3835 // Append a comma (except for the first one)
3836 // and add the column to a GROUP BY clause
3840 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3842 buffer_fadd(group_buf, " %d", sel_pos);
3846 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3848 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3849 if ( ! obj_is_true( aggregate_obj ) ) {
3853 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3856 buffer_fadd(group_buf, " %d", sel_pos);
3859 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3863 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3866 _column = searchFieldTransform(class_info->alias, field, selfield);
3867 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3868 OSRF_BUFFER_ADD(group_buf, _column);
3869 _column = searchFieldTransform(class_info->alias, field, selfield);
3876 } // end while -- iterating across SELECT columns
3878 } // end while -- iterating across classes
3880 jsonIteratorFree(selclass_itr);
3884 char* col_list = buffer_release(select_buf);
3886 // Make sure the SELECT list isn't empty. This can happen, for example,
3887 // if we try to build a default SELECT clause from a non-core table.
3890 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
3892 osrfAppSessionStatus(
3894 OSRF_STATUS_INTERNALSERVERERROR,
3895 "osrfMethodException",
3897 "SELECT list is empty"
3900 buffer_free( group_buf );
3901 if( defaultselhash ) jsonObjectFree( defaultselhash );
3902 free( join_clause );
3907 if (from_function) table = searchValueTransform(join_hash);
3908 else table = strdup( curr_query->core.source_def );
3912 osrfAppSessionStatus(
3914 OSRF_STATUS_INTERNALSERVERERROR,
3915 "osrfMethodException",
3917 "Unable to identify table for core class"
3920 buffer_free( group_buf );
3921 if( defaultselhash ) jsonObjectFree( defaultselhash );
3922 free( join_clause );
3926 // Put it all together
3927 growing_buffer* sql_buf = buffer_init(128);
3928 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3932 // Append the join clause, if any
3934 buffer_add(sql_buf, join_clause);
3938 char* order_by_list = NULL;
3939 char* having_buf = NULL;
3941 if (!from_function) {
3943 // Build a WHERE clause, if there is one
3944 if ( search_hash ) {
3945 buffer_add(sql_buf, " WHERE ");
3947 // and it's on the WHERE clause
3948 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
3951 osrfAppSessionStatus(
3953 OSRF_STATUS_INTERNALSERVERERROR,
3954 "osrfMethodException",
3956 "Severe query error in WHERE predicate -- see error log for more details"
3959 buffer_free(group_buf);
3960 buffer_free(sql_buf);
3961 if (defaultselhash) jsonObjectFree(defaultselhash);
3965 buffer_add(sql_buf, pred);
3969 // Build a HAVING clause, if there is one
3970 if ( having_hash ) {
3972 // and it's on the the WHERE clause
3973 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
3975 if( ! having_buf ) {
3977 osrfAppSessionStatus(
3979 OSRF_STATUS_INTERNALSERVERERROR,
3980 "osrfMethodException",
3982 "Severe query error in HAVING predicate -- see error log for more details"
3985 buffer_free(group_buf);
3986 buffer_free(sql_buf);
3987 if (defaultselhash) jsonObjectFree(defaultselhash);
3992 growing_buffer* order_buf = NULL; // to collect ORDER BY list
3994 // Build an ORDER BY clause, if there is one
3995 if( NULL == order_hash )
3996 ; // No ORDER BY? do nothing
3997 else if( JSON_ARRAY == order_hash->type ) {
3998 // Array of field specifications, each specification being a
3999 // hash to define the class, field, and other details
4001 jsonObject* order_spec;
4002 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4004 if( JSON_HASH != order_spec->type ) {
4005 osrfLogError(OSRF_LOG_MARK,
4006 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4007 MODULENAME, json_type( order_spec->type ) );
4009 osrfAppSessionStatus(
4011 OSRF_STATUS_INTERNALSERVERERROR,
4012 "osrfMethodException",
4014 "Malformed ORDER BY clause -- see error log for more details"
4016 buffer_free( order_buf );
4018 buffer_free(group_buf);
4019 buffer_free(sql_buf);
4020 if (defaultselhash) jsonObjectFree(defaultselhash);
4024 const char* class_alias =
4025 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4027 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4030 OSRF_BUFFER_ADD(order_buf, ", ");
4032 order_buf = buffer_init(128);
4034 if( !field || !class_alias ) {
4035 osrfLogError(OSRF_LOG_MARK,
4036 "%s: Missing class or field name in field specification of ORDER BY clause",
4039 osrfAppSessionStatus(
4041 OSRF_STATUS_INTERNALSERVERERROR,
4042 "osrfMethodException",
4044 "Malformed ORDER BY clause -- see error log for more details"
4046 buffer_free( order_buf );
4048 buffer_free(group_buf);
4049 buffer_free(sql_buf);
4050 if (defaultselhash) jsonObjectFree(defaultselhash);
4054 ClassInfo* order_class_info = search_alias( class_alias );
4055 if( ! order_class_info ) {
4056 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4057 "not in FROM clause", MODULENAME, class_alias );
4059 osrfAppSessionStatus(
4061 OSRF_STATUS_INTERNALSERVERERROR,
4062 "osrfMethodException",
4064 "Invalid class referenced in ORDER BY clause -- see error log for more details"
4067 buffer_free(group_buf);
4068 buffer_free(sql_buf);
4069 if (defaultselhash) jsonObjectFree(defaultselhash);
4073 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4075 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4076 MODULENAME, class_alias, field );
4078 osrfAppSessionStatus(
4080 OSRF_STATUS_INTERNALSERVERERROR,
4081 "osrfMethodException",
4083 "Invalid field referenced in ORDER BY clause -- see error log for more details"
4086 buffer_free(group_buf);
4087 buffer_free(sql_buf);
4088 if (defaultselhash) jsonObjectFree(defaultselhash);
4090 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4091 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4092 MODULENAME, field );
4094 osrfAppSessionStatus(
4096 OSRF_STATUS_INTERNALSERVERERROR,
4097 "osrfMethodException",
4099 "Virtual field in ORDER BY clause -- see error log for more details"
4101 buffer_free( order_buf );
4103 buffer_free(group_buf);
4104 buffer_free(sql_buf);
4105 if (defaultselhash) jsonObjectFree(defaultselhash);
4109 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4110 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
4111 if( ! transform_str ) {
4113 osrfAppSessionStatus(
4115 OSRF_STATUS_INTERNALSERVERERROR,
4116 "osrfMethodException",
4118 "Severe query error in ORDER BY clause -- see error log for more details"
4120 buffer_free( order_buf );
4122 buffer_free(group_buf);
4123 buffer_free(sql_buf);
4124 if (defaultselhash) jsonObjectFree(defaultselhash);
4128 OSRF_BUFFER_ADD( order_buf, transform_str );
4129 free( transform_str );
4132 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4134 const char* direction =
4135 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4137 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4138 OSRF_BUFFER_ADD( order_buf, " DESC" );
4140 OSRF_BUFFER_ADD( order_buf, " ASC" );
4143 } else if( JSON_HASH == order_hash->type ) {
4144 // This hash is keyed on class alias. Each class has either
4145 // an array of field names or a hash keyed on field name.
4146 jsonIterator* class_itr = jsonNewIterator( order_hash );
4147 while ( (snode = jsonIteratorNext( class_itr )) ) {
4149 ClassInfo* order_class_info = search_alias( class_itr->key );
4150 if( ! order_class_info ) {
4151 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4152 MODULENAME, class_itr->key );
4154 osrfAppSessionStatus(
4156 OSRF_STATUS_INTERNALSERVERERROR,
4157 "osrfMethodException",
4159 "Invalid class referenced in ORDER BY clause -- see error log for more details"
4161 jsonIteratorFree( class_itr );
4162 buffer_free( order_buf );
4164 buffer_free(group_buf);
4165 buffer_free(sql_buf);
4166 if (defaultselhash) jsonObjectFree(defaultselhash);
4170 osrfHash* field_list_def = order_class_info->fields;
4172 if ( snode->type == JSON_HASH ) {
4174 // Hash is keyed on field names from the current class. For each field
4175 // there is another layer of hash to define the sorting details, if any,
4176 // or a string to indicate direction of sorting.
4177 jsonIterator* order_itr = jsonNewIterator( snode );
4178 while ( (onode = jsonIteratorNext( order_itr )) ) {
4180 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4182 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4183 MODULENAME, order_itr->key );
4185 osrfAppSessionStatus(
4187 OSRF_STATUS_INTERNALSERVERERROR,
4188 "osrfMethodException",
4190 "Invalid field in ORDER BY clause -- see error log for more details"
4192 jsonIteratorFree( order_itr );
4193 jsonIteratorFree( class_itr );
4194 buffer_free( order_buf );
4196 buffer_free(group_buf);
4197 buffer_free(sql_buf);
4198 if (defaultselhash) jsonObjectFree(defaultselhash);
4200 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4201 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4202 MODULENAME, order_itr->key );
4204 osrfAppSessionStatus(
4206 OSRF_STATUS_INTERNALSERVERERROR,
4207 "osrfMethodException",
4209 "Virtual field in ORDER BY clause -- see error log for more details"
4211 jsonIteratorFree( order_itr );
4212 jsonIteratorFree( class_itr );
4213 buffer_free( order_buf );
4215 buffer_free(group_buf);
4216 buffer_free(sql_buf);
4217 if (defaultselhash) jsonObjectFree(defaultselhash);
4221 const char* direction = NULL;
4222 if ( onode->type == JSON_HASH ) {
4223 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4224 string = searchFieldTransform(
4226 osrfHashGet( field_list_def, order_itr->key ),
4230 if( ctx ) osrfAppSessionStatus(
4232 OSRF_STATUS_INTERNALSERVERERROR,
4233 "osrfMethodException",
4235 "Severe query error in ORDER BY clause -- see error log for more details"
4237 jsonIteratorFree( order_itr );
4238 jsonIteratorFree( class_itr );
4240 buffer_free(group_buf);
4241 buffer_free(order_buf);
4242 buffer_free(sql_buf);
4243 if (defaultselhash) jsonObjectFree(defaultselhash);
4247 growing_buffer* field_buf = buffer_init(16);
4248 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4249 string = buffer_release(field_buf);
4252 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4253 const char* dir = jsonObjectGetString(tmp_const);
4254 if (!strncasecmp(dir, "d", 1)) {
4255 direction = " DESC";
4261 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4262 osrfLogError( OSRF_LOG_MARK,
4263 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4264 MODULENAME, json_type( onode->type ) );
4266 osrfAppSessionStatus(
4268 OSRF_STATUS_INTERNALSERVERERROR,
4269 "osrfMethodException",
4271 "Malformed ORDER BY clause -- see error log for more details"
4273 jsonIteratorFree( order_itr );
4274 jsonIteratorFree( class_itr );
4276 buffer_free(group_buf);
4277 buffer_free(order_buf);
4278 buffer_free(sql_buf);
4279 if (defaultselhash) jsonObjectFree(defaultselhash);
4283 string = strdup(order_itr->key);
4284 const char* dir = jsonObjectGetString(onode);
4285 if (!strncasecmp(dir, "d", 1)) {
4286 direction = " DESC";
4293 OSRF_BUFFER_ADD(order_buf, ", ");
4295 order_buf = buffer_init(128);
4297 OSRF_BUFFER_ADD(order_buf, string);
4301 OSRF_BUFFER_ADD(order_buf, direction);
4305 jsonIteratorFree(order_itr);
4307 } else if ( snode->type == JSON_ARRAY ) {
4309 // Array is a list of fields from the current class
4310 unsigned long order_idx = 0;
4311 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4313 const char* _f = jsonObjectGetString( onode );
4315 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4317 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4320 osrfAppSessionStatus(
4322 OSRF_STATUS_INTERNALSERVERERROR,
4323 "osrfMethodException",
4325 "Invalid field in ORDER BY clause -- see error log for more details"
4327 jsonIteratorFree( class_itr );
4328 buffer_free( order_buf );
4330 buffer_free(group_buf);
4331 buffer_free(sql_buf);
4332 if (defaultselhash) jsonObjectFree(defaultselhash);
4334 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4335 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4338 osrfAppSessionStatus(
4340 OSRF_STATUS_INTERNALSERVERERROR,
4341 "osrfMethodException",
4343 "Virtual field in ORDER BY clause -- see error log for more details"
4345 jsonIteratorFree( class_itr );
4346 buffer_free( order_buf );
4348 buffer_free(group_buf);
4349 buffer_free(sql_buf);
4350 if (defaultselhash) jsonObjectFree(defaultselhash);
4355 OSRF_BUFFER_ADD(order_buf, ", ");
4357 order_buf = buffer_init(128);
4359 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
4363 // IT'S THE OOOOOOOOOOOLD STYLE!
4365 osrfLogError(OSRF_LOG_MARK,
4366 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
4368 osrfAppSessionStatus(
4370 OSRF_STATUS_INTERNALSERVERERROR,
4371 "osrfMethodException",
4373 "Severe query error -- see error log for more details"
4378 buffer_free(group_buf);
4379 buffer_free(order_buf);
4380 buffer_free(sql_buf);
4381 if (defaultselhash) jsonObjectFree(defaultselhash);
4382 jsonIteratorFree(class_itr);
4386 jsonIteratorFree( class_itr );
4388 osrfLogError(OSRF_LOG_MARK,
4389 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4390 MODULENAME, json_type( order_hash->type ) );
4392 osrfAppSessionStatus(
4394 OSRF_STATUS_INTERNALSERVERERROR,
4395 "osrfMethodException",
4397 "Malformed ORDER BY clause -- see error log for more details"
4399 buffer_free( order_buf );
4401 buffer_free(group_buf);
4402 buffer_free(sql_buf);
4403 if (defaultselhash) jsonObjectFree(defaultselhash);
4408 order_by_list = buffer_release( order_buf );
4412 string = buffer_release(group_buf);
4414 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4415 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4416 OSRF_BUFFER_ADD( sql_buf, string );
4421 if( having_buf && *having_buf ) {
4422 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4423 OSRF_BUFFER_ADD( sql_buf, having_buf );
4427 if( order_by_list ) {
4429 if ( *order_by_list ) {
4430 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4431 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4434 free( order_by_list );
4438 const char* str = jsonObjectGetString(limit);
4439 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
4443 const char* str = jsonObjectGetString(offset);
4444 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
4447 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4449 if (defaultselhash) jsonObjectFree(defaultselhash);
4451 return buffer_release(sql_buf);
4453 } // end of SELECT()
4455 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4457 const char* locale = osrf_message_get_last_locale();
4459 osrfHash* fields = osrfHashGet(meta, "fields");
4460 char* core_class = osrfHashGet(meta, "classname");
4462 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4464 jsonObject* node = NULL;
4465 jsonObject* snode = NULL;
4466 jsonObject* onode = NULL;
4467 const jsonObject* _tmp = NULL;
4468 jsonObject* selhash = NULL;
4469 jsonObject* defaultselhash = NULL;
4471 growing_buffer* sql_buf = buffer_init(128);
4472 growing_buffer* select_buf = buffer_init(128);
4474 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4475 defaultselhash = jsonNewObjectType(JSON_HASH);
4476 selhash = defaultselhash;
4479 // If there's no SELECT list for the core class, build one
4480 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4481 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4483 // Add every non-virtual field to the field list
4484 osrfHash* field_def = NULL;
4485 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4486 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4487 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4488 const char* field = osrfHashIteratorKey( field_itr );
4489 jsonObjectPush( field_list, jsonNewObject( field ) );
4492 osrfHashIteratorFree( field_itr );
4493 jsonObjectSetKey( selhash, core_class, field_list );
4497 jsonIterator* class_itr = jsonNewIterator( selhash );
4498 while ( (snode = jsonIteratorNext( class_itr )) ) {
4500 const char* cname = class_itr->key;
4501 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4502 if (!idlClass) continue;
4504 if (strcmp(core_class,class_itr->key)) {
4505 if (!join_hash) continue;
4507 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4509 jsonObjectFree(found);
4513 jsonObjectFree(found);
4516 jsonIterator* select_itr = jsonNewIterator( snode );
4517 while ( (node = jsonIteratorNext( select_itr )) ) {
4518 const char* item_str = jsonObjectGetString( node );
4519 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4520 char* fname = osrfHashGet(field, "name");
4522 if (!field) continue;
4527 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
4532 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4533 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4536 i18n = osrfHashGet(field, "i18n");
4538 if( str_is_true( i18n ) ) {
4539 char* pkey = osrfHashGet(idlClass, "primarykey");
4540 char* tname = osrfHashGet(idlClass, "tablename");
4542 buffer_fadd(select_buf, " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"", tname, cname, fname, pkey, cname, pkey, locale, fname);
4544 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4547 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4551 jsonIteratorFree(select_itr);
4554 jsonIteratorFree(class_itr);
4556 char* col_list = buffer_release(select_buf);
4557 char* table = getRelation(meta);
4559 table = strdup( "(null)" );
4561 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4565 // Clear the query stack (as a fail-safe precaution against possible
4566 // leftover garbage); then push the first query frame onto the stack.
4567 clear_query_stack();
4569 if( add_query_core( NULL, core_class ) ) {
4571 osrfAppSessionStatus(
4573 OSRF_STATUS_INTERNALSERVERERROR,
4574 "osrfMethodException",
4576 "Unable to build query frame for core class"
4582 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4583 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
4584 OSRF_BUFFER_ADD(sql_buf, join_clause);
4588 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4589 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4591 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4593 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4595 osrfAppSessionStatus(
4597 OSRF_STATUS_INTERNALSERVERERROR,
4598 "osrfMethodException",
4600 "Severe query error -- see error log for more details"
4602 buffer_free(sql_buf);
4603 if(defaultselhash) jsonObjectFree(defaultselhash);
4604 clear_query_stack();
4607 buffer_add(sql_buf, pred);
4612 char* string = NULL;
4613 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4615 growing_buffer* order_buf = buffer_init(128);
4618 jsonIterator* class_itr = jsonNewIterator( _tmp );
4619 while ( (snode = jsonIteratorNext( class_itr )) ) {
4621 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4624 if ( snode->type == JSON_HASH ) {
4626 jsonIterator* order_itr = jsonNewIterator( snode );
4627 while ( (onode = jsonIteratorNext( order_itr )) ) {
4629 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4630 class_itr->key, order_itr->key );
4634 char* direction = NULL;
4635 if ( onode->type == JSON_HASH ) {
4636 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4637 string = searchFieldTransform( class_itr->key, field_def, onode );
4639 osrfAppSessionStatus(
4641 OSRF_STATUS_INTERNALSERVERERROR,
4642 "osrfMethodException",
4644 "Severe query error in ORDER BY clause -- see error log for more details"
4646 jsonIteratorFree( order_itr );
4647 jsonIteratorFree( class_itr );
4648 buffer_free( order_buf );
4649 buffer_free( sql_buf );
4650 if( defaultselhash ) jsonObjectFree( defaultselhash );
4651 clear_query_stack();
4655 growing_buffer* field_buf = buffer_init(16);
4656 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4657 string = buffer_release(field_buf);
4660 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4661 const char* dir = jsonObjectGetString(_tmp);
4662 if (!strncasecmp(dir, "d", 1)) {
4663 direction = " DESC";
4670 string = strdup(order_itr->key);
4671 const char* dir = jsonObjectGetString(onode);
4672 if (!strncasecmp(dir, "d", 1)) {
4673 direction = " DESC";
4682 buffer_add(order_buf, ", ");
4685 buffer_add(order_buf, string);
4689 buffer_add(order_buf, direction);
4694 jsonIteratorFree(order_itr);
4697 const char* str = jsonObjectGetString(snode);
4698 buffer_add(order_buf, str);
4704 jsonIteratorFree(class_itr);
4706 string = buffer_release(order_buf);
4709 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4710 OSRF_BUFFER_ADD( sql_buf, string );
4716 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4717 const char* str = jsonObjectGetString(_tmp);
4725 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4727 const char* str = jsonObjectGetString(_tmp);
4736 if (defaultselhash) jsonObjectFree(defaultselhash);
4737 clear_query_stack();
4739 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4740 return buffer_release(sql_buf);
4743 int doJSONSearch ( osrfMethodContext* ctx ) {
4744 if(osrfMethodVerifyContext( ctx )) {
4745 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4749 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
4754 dbhandle = writehandle;
4756 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4760 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4761 flags |= SELECT_DISTINCT;
4763 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4764 flags |= DISABLE_I18N;
4766 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4767 clear_query_stack(); // a possibly needless precaution
4768 char* sql = buildQuery( ctx, hash, flags );
4769 clear_query_stack();
4776 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4777 dbi_result result = dbi_conn_query(dbhandle, sql);
4780 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4782 if (dbi_result_first_row(result)) {
4783 /* JSONify the result */
4784 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4787 jsonObject* return_val = oilsMakeJSONFromResult( result );
4788 osrfAppRespond( ctx, return_val );
4789 jsonObjectFree( return_val );
4790 } while (dbi_result_next_row(result));
4793 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4796 osrfAppRespondComplete( ctx, NULL );
4798 /* clean up the query */
4799 dbi_result_free(result);
4803 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4804 osrfAppSessionStatus(
4806 OSRF_STATUS_INTERNALSERVERERROR,
4807 "osrfMethodException",
4809 "Severe query error -- see error log for more details"
4817 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4818 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4821 dbhandle = writehandle;
4823 osrfHash* links = osrfHashGet(meta, "links");
4824 osrfHash* fields = osrfHashGet(meta, "fields");
4825 char* core_class = osrfHashGet(meta, "classname");
4826 char* pkey = osrfHashGet(meta, "primarykey");
4828 const jsonObject* _tmp;
4831 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4833 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4838 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4840 dbi_result result = dbi_conn_query(dbhandle, sql);
4841 if( NULL == result ) {
4842 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4843 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4844 osrfAppSessionStatus(
4846 OSRF_STATUS_INTERNALSERVERERROR,
4847 "osrfMethodException",
4849 "Severe query error -- see error log for more details"
4856 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4859 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4860 osrfHash* dedup = osrfNewHash();
4862 if (dbi_result_first_row(result)) {
4863 /* JSONify the result */
4864 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4866 obj = oilsMakeFieldmapperFromResult( result, meta );
4867 char* pkey_val = oilsFMGetString( obj, pkey );
4868 if ( osrfHashGet( dedup, pkey_val ) ) {
4869 jsonObjectFree(obj);
4872 osrfHashSet( dedup, pkey_val, pkey_val );
4873 jsonObjectPush(res_list, obj);
4875 } while (dbi_result_next_row(result));
4877 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4881 osrfHashFree(dedup);
4882 /* clean up the query */
4883 dbi_result_free(result);
4886 if (res_list->size && query_hash) {
4887 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4889 int x = (int)jsonObjectGetNumber(_tmp);
4890 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4892 const jsonObject* temp_blob;
4893 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4895 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4896 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4898 osrfStringArray* link_fields = NULL;
4901 if (flesh_fields->size == 1) {
4902 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4903 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4908 link_fields = osrfNewStringArray(1);
4909 jsonIterator* _i = jsonNewIterator( flesh_fields );
4910 while ((_f = jsonIteratorNext( _i ))) {
4911 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4913 jsonIteratorFree(_i);
4918 unsigned long res_idx = 0;
4919 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
4922 const char* link_field;
4924 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4926 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4928 osrfHash* kid_link = osrfHashGet(links, link_field);
4929 if (!kid_link) continue;
4931 osrfHash* field = osrfHashGet(fields, link_field);
4932 if (!field) continue;
4934 osrfHash* value_field = field;
4936 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4937 if (!kid_idl) continue;
4939 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4940 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4943 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4944 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4947 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4949 if (link_map->size > 0) {
4950 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4953 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4958 osrfHashGet(kid_link, "class"),
4965 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4966 osrfHashGet(kid_link, "field"),
4967 osrfHashGet(kid_link, "class"),
4968 osrfHashGet(kid_link, "key"),
4969 osrfHashGet(kid_link, "reltype")
4972 const char* search_key = jsonObjectGetString(
4975 atoi( osrfHashGet(value_field, "array_position") )
4980 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4984 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4986 // construct WHERE clause
4987 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
4990 osrfHashGet(kid_link, "key"),
4991 jsonNewObject( search_key )
4994 // construct the rest of the query
4995 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4996 jsonObjectSetKey( rest_of_query, "flesh",
4997 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
5001 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
5003 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
5004 jsonObjectSetKey( rest_of_query, "order_by",
5005 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
5009 if (jsonObjectGetKeyConst(query_hash, "select")) {
5010 jsonObjectSetKey( rest_of_query, "select",
5011 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
5015 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5016 where_clause, rest_of_query, err);
5018 jsonObjectFree( where_clause );
5019 jsonObjectFree( rest_of_query );
5022 osrfStringArrayFree(link_fields);
5023 jsonObjectFree(res_list);
5024 jsonObjectFree(flesh_blob);
5028 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
5030 jsonObject* X = NULL;
5031 if ( link_map->size > 0 && kids->size > 0 ) {
5033 kids = jsonNewObjectType(JSON_ARRAY);
5035 jsonObject* _k_node;
5036 unsigned long res_idx = 0;
5037 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5043 (unsigned long)atoi(
5049 osrfHashGet(kid_link, "class")
5053 osrfStringArrayGetString( link_map, 0 )
5061 } // end while loop traversing X
5064 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
5065 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
5068 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5069 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
5073 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
5074 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
5077 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5078 jsonObjectClone( kids )
5083 jsonObjectFree(kids);
5087 jsonObjectFree( kids );
5089 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
5090 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
5093 } // end while loop traversing res_list
5094 jsonObjectFree( flesh_blob );
5095 osrfStringArrayFree(link_fields);
5104 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
5106 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5108 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
5110 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
5113 if (!verifyObjectClass(ctx, target)) {
5118 if( getXactId( ctx ) == NULL ) {
5119 osrfAppSessionStatus(
5121 OSRF_STATUS_BADREQUEST,
5122 "osrfMethodException",
5124 "No active transaction -- required for UPDATE"
5130 // The following test is harmless but redundant. If a class is
5131 // readonly, we don't register an update method for it.
5132 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5133 osrfAppSessionStatus(
5135 OSRF_STATUS_BADREQUEST,
5136 "osrfMethodException",
5138 "Cannot UPDATE readonly class"
5144 dbhandle = writehandle;
5145 const char* trans_id = getXactId( ctx );
5147 // Set the last_xact_id
5148 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5150 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5151 trans_id, target->classname, index);
5152 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
5155 char* pkey = osrfHashGet(meta, "primarykey");
5156 osrfHash* fields = osrfHashGet(meta, "fields");
5158 char* id = oilsFMGetString( target, pkey );
5162 "%s updating %s object with %s = %s",
5164 osrfHashGet(meta, "fieldmapper"),
5169 growing_buffer* sql = buffer_init(128);
5170 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
5173 osrfHash* field_def = NULL;
5174 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5175 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
5177 // Skip virtual fields, and the primary key
5178 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5181 const char* field_name = osrfHashIteratorKey( field_itr );
5182 if( ! strcmp( field_name, pkey ) )
5185 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5187 int value_is_numeric = 0; // boolean
5189 if (field_object && field_object->classname) {
5190 value = oilsFMGetString(
5192 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
5194 } else if( field_object && JSON_BOOL == field_object->type ) {
5195 if( jsonBoolIsTrue( field_object ) )
5196 value = strdup( "t" );
5198 value = strdup( "f" );
5200 value = jsonObjectToSimpleString( field_object );
5201 if( field_object && JSON_NUMBER == field_object->type )
5202 value_is_numeric = 1;
5205 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5206 osrfHashGet(meta, "fieldmapper"), field_name, value);
5208 if (!field_object || field_object->type == JSON_NULL) {
5209 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
5210 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5211 if (first) first = 0;
5212 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5213 buffer_fadd( sql, " %s = NULL", field_name );
5216 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5217 if (first) first = 0;
5218 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5220 const char* numtype = get_datatype( field_def );
5221 if ( !strncmp( numtype, "INT", 3 ) ) {
5222 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
5223 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
5224 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
5226 // Must really be intended as a string, so quote it
5227 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5228 buffer_fadd( sql, " %s = %s", field_name, value );
5230 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5231 osrfAppSessionStatus(
5233 OSRF_STATUS_INTERNALSERVERERROR,
5234 "osrfMethodException",
5236 "Error quoting string -- please see the error log for more details"
5240 osrfHashIteratorFree( field_itr );
5247 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5250 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5251 if (first) first = 0;
5252 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5253 buffer_fadd( sql, " %s = %s", field_name, value );
5256 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5257 osrfAppSessionStatus(
5259 OSRF_STATUS_INTERNALSERVERERROR,
5260 "osrfMethodException",
5262 "Error quoting string -- please see the error log for more details"
5266 osrfHashIteratorFree( field_itr );
5277 osrfHashIteratorFree( field_itr );
5279 jsonObject* obj = jsonNewObject(id);
5281 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5282 dbi_conn_quote_string(dbhandle, &id);
5284 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5286 char* query = buffer_release(sql);
5287 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
5289 dbi_result result = dbi_conn_query(dbhandle, query);
5293 jsonObjectFree(obj);
5294 obj = jsonNewObject(NULL);
5297 "%s ERROR updating %s object with %s = %s",
5299 osrfHashGet(meta, "fieldmapper"),
5310 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
5312 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5314 if( getXactId( ctx ) == NULL ) {
5315 osrfAppSessionStatus(
5317 OSRF_STATUS_BADREQUEST,
5318 "osrfMethodException",
5320 "No active transaction -- required for DELETE"
5326 // The following test is harmless but redundant. If a class is
5327 // readonly, we don't register a delete method for it.
5328 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5329 osrfAppSessionStatus(
5331 OSRF_STATUS_BADREQUEST,
5332 "osrfMethodException",
5334 "Cannot DELETE readonly class"
5340 dbhandle = writehandle;
5344 char* pkey = osrfHashGet(meta, "primarykey");
5352 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
5353 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5358 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5361 if (!verifyObjectPCRUD( ctx, NULL )) {
5366 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
5371 "%s deleting %s object with %s = %s",
5373 osrfHashGet(meta, "fieldmapper"),
5378 obj = jsonNewObject(id);
5380 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5381 dbi_conn_quote_string(writehandle, &id);
5383 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
5386 jsonObjectFree(obj);
5387 obj = jsonNewObject(NULL);
5390 "%s ERROR deleting %s object with %s = %s",
5392 osrfHashGet(meta, "fieldmapper"),
5405 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5406 if(!(result && meta)) return jsonNULL;
5408 jsonObject* object = jsonNewObject(NULL);
5409 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
5411 osrfHash* fields = osrfHashGet(meta, "fields");
5413 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
5417 char dt_string[256];
5421 int columnIndex = 1;
5423 unsigned short type;
5424 const char* columnName;
5426 /* cycle through the column list */
5427 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5429 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5431 fmIndex = -1; // reset the position
5433 /* determine the field type and storage attributes */
5434 type = dbi_result_get_field_type_idx(result, columnIndex);
5435 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5437 /* fetch the fieldmapper index */
5438 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
5440 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
5443 const char* pos = (char*)osrfHashGet(_f, "array_position");
5444 if ( !pos ) continue;
5446 fmIndex = atoi( pos );
5447 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
5452 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5453 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
5458 case DBI_TYPE_INTEGER :
5460 if( attr & DBI_INTEGER_SIZE8 )
5461 jsonObjectSetIndex( object, fmIndex,
5462 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
5464 jsonObjectSetIndex( object, fmIndex,
5465 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
5469 case DBI_TYPE_DECIMAL :
5470 jsonObjectSetIndex( object, fmIndex,
5471 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
5474 case DBI_TYPE_STRING :
5480 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
5485 case DBI_TYPE_DATETIME :
5487 memset(dt_string, '\0', sizeof(dt_string));
5488 memset(&gmdt, '\0', sizeof(gmdt));
5490 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5493 if (!(attr & DBI_DATETIME_DATE)) {
5494 gmtime_r( &_tmp_dt, &gmdt );
5495 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5496 } else if (!(attr & DBI_DATETIME_TIME)) {
5497 localtime_r( &_tmp_dt, &gmdt );
5498 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5500 localtime_r( &_tmp_dt, &gmdt );
5501 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5504 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
5508 case DBI_TYPE_BINARY :
5509 osrfLogError( OSRF_LOG_MARK,
5510 "Can't do binary at column %s : index %d", columnName, columnIndex);
5519 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5520 if(!result) return jsonNULL;
5522 jsonObject* object = jsonNewObject(NULL);
5525 char dt_string[256];
5529 int columnIndex = 1;
5531 unsigned short type;
5532 const char* columnName;
5534 /* cycle through the column list */
5535 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5537 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5539 fmIndex = -1; // reset the position
5541 /* determine the field type and storage attributes */
5542 type = dbi_result_get_field_type_idx(result, columnIndex);
5543 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5545 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5546 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
5551 case DBI_TYPE_INTEGER :
5553 if( attr & DBI_INTEGER_SIZE8 )
5554 jsonObjectSetKey( object, columnName,
5555 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
5557 jsonObjectSetKey( object, columnName,
5558 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
5561 case DBI_TYPE_DECIMAL :
5562 jsonObjectSetKey( object, columnName,
5563 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
5566 case DBI_TYPE_STRING :
5567 jsonObjectSetKey( object, columnName,
5568 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
5571 case DBI_TYPE_DATETIME :
5573 memset(dt_string, '\0', sizeof(dt_string));
5574 memset(&gmdt, '\0', sizeof(gmdt));
5576 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5579 if (!(attr & DBI_DATETIME_DATE)) {
5580 gmtime_r( &_tmp_dt, &gmdt );
5581 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5582 } else if (!(attr & DBI_DATETIME_TIME)) {
5583 localtime_r( &_tmp_dt, &gmdt );
5584 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5586 localtime_r( &_tmp_dt, &gmdt );
5587 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5590 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5593 case DBI_TYPE_BINARY :
5594 osrfLogError( OSRF_LOG_MARK,
5595 "Can't do binary at column %s : index %d", columnName, columnIndex );
5599 } // end while loop traversing result
5604 // Interpret a string as true or false
5605 static int str_is_true( const char* str ) {
5606 if( NULL == str || strcasecmp( str, "true" ) )
5612 // Interpret a jsonObject as true or false
5613 static int obj_is_true( const jsonObject* obj ) {
5616 else switch( obj->type )
5624 if( strcasecmp( obj->value.s, "true" ) )
5628 case JSON_NUMBER : // Support 1/0 for perl's sake
5629 if( jsonObjectGetNumber( obj ) == 1.0 )
5638 // Translate a numeric code into a text string identifying a type of
5639 // jsonObject. To be used for building error messages.
5640 static const char* json_type( int code ) {
5646 return "JSON_ARRAY";
5648 return "JSON_STRING";
5650 return "JSON_NUMBER";
5656 return "(unrecognized)";
5660 // Extract the "primitive" attribute from an IDL field definition.
5661 // If we haven't initialized the app, then we must be running in
5662 // some kind of testbed. In that case, default to "string".
5663 static const char* get_primitive( osrfHash* field ) {
5664 const char* s = osrfHashGet( field, "primitive" );
5666 if( child_initialized )
5669 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5671 osrfHashGet( field, "name" )
5679 // Extract the "datatype" attribute from an IDL field definition.
5680 // If we haven't initialized the app, then we must be running in
5681 // some kind of testbed. In that case, default to to NUMERIC,
5682 // since we look at the datatype only for numbers.
5683 static const char* get_datatype( osrfHash* field ) {
5684 const char* s = osrfHashGet( field, "datatype" );
5686 if( child_initialized )
5689 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5691 osrfHashGet( field, "name" )
5700 If the input string is potentially a valid SQL identifier, return 1.
5703 Purpose: to prevent certain kinds of SQL injection. To that end we
5704 don't necessarily need to follow all the rules exactly, such as requiring
5705 that the first character not be a digit.
5707 We allow leading and trailing white space. In between, we do not allow
5708 punctuation (except for underscores and dollar signs), control
5709 characters, or embedded white space.
5711 More pedantically we should allow quoted identifiers containing arbitrary
5712 characters, but for the foreseeable future such quoted identifiers are not
5713 likely to be an issue.
5715 static int is_identifier( const char* s) {
5719 // Skip leading white space
5720 while( isspace( (unsigned char) *s ) )
5724 return 0; // Nothing but white space? Not okay.
5726 // Check each character until we reach white space or
5727 // end-of-string. Letters, digits, underscores, and
5728 // dollar signs are okay. With the exception of periods
5729 // (as in schema.identifier), control characters and other
5730 // punctuation characters are not okay. Anything else
5731 // is okay -- it could for example be part of a multibyte
5732 // UTF8 character such as a letter with diacritical marks,
5733 // and those are allowed.
5735 if( isalnum( (unsigned char) *s )
5739 ; // Fine; keep going
5740 else if( ispunct( (unsigned char) *s )
5741 || iscntrl( (unsigned char) *s ) )
5744 } while( *s && ! isspace( (unsigned char) *s ) );
5746 // If we found any white space in the above loop,
5747 // the rest had better be all white space.
5749 while( isspace( (unsigned char) *s ) )
5753 return 0; // White space was embedded within non-white space
5759 Determine whether to accept a character string as a comparison operator.
5760 Return 1 if it's good, or 0 if it's bad.
5762 We don't validate it for real. We just make sure that it doesn't contain
5763 any semicolons or white space (with special exceptions for a few specific
5764 operators). The idea is to block certain kinds of SQL injection. If it
5765 has no semicolons or white space but it's still not a valid operator, then
5766 the database will complain.
5768 Another approach would be to compare the string against a short list of
5769 approved operators. We don't do that because we want to allow custom
5770 operators like ">100*", which would be difficult or impossible to
5771 express otherwise in a JSON query.
5773 static int is_good_operator( const char* op ) {
5774 if( !op ) return 0; // Sanity check
5778 if( isspace( (unsigned char) *s ) ) {
5779 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
5780 // and IS NOT DISTINCT FROM.
5781 if( !strcasecmp( op, "similar to" ) )
5783 else if( !strcasecmp( op, "is distinct from" ) )
5785 else if( !strcasecmp( op, "is not distinct from" ) )
5790 else if( ';' == *s )
5797 /* ----------------------------------------------------------------------------------
5798 The following machinery supports a stack of query frames for use by SELECT().
5800 A query frame caches information about one level of a SELECT query. When we enter
5801 a subquery, we push another query frame onto the stack, and pop it off when we leave.
5803 The query frame stores information about the core class, and about any joined classes
5806 The main purpose is to map table aliases to classes and tables, so that a query can
5807 join to the same table more than once. A secondary goal is to reduce the number of
5808 lookups in the IDL by caching the results.
5809 ----------------------------------------------------------------------------------*/
5811 #define STATIC_CLASS_INFO_COUNT 3
5813 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
5815 /* ---------------------------------------------------------------------------
5816 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5818 ---------------------------------------------------------------------------*/
5819 static ClassInfo* allocate_class_info( void ) {
5820 // In order to reduce the number of mallocs and frees, we return a static
5821 // instance of ClassInfo, if we can find one that we're not already using.
5822 // We rely on the fact that the compiler will implicitly initialize the
5823 // static instances so that in_use == 0.
5826 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5827 if( ! static_class_info[ i ].in_use ) {
5828 static_class_info[ i ].in_use = 1;
5829 return static_class_info + i;
5833 // The static ones are all in use. Malloc one.
5835 return safe_malloc( sizeof( ClassInfo ) );
5838 /* --------------------------------------------------------------------------
5839 Free any malloc'd memory owned by a ClassInfo; return it to a pristine state
5840 ---------------------------------------------------------------------------*/
5841 static void clear_class_info( ClassInfo* info ) {
5846 // Free any malloc'd strings
5848 if( info->alias != info->alias_store )
5849 free( info->alias );
5851 if( info->class_name != info->class_name_store )
5852 free( info->class_name );
5854 free( info->source_def );
5856 info->alias = info->class_name = info->source_def = NULL;
5860 /* --------------------------------------------------------------------------
5861 Deallocate a ClassInfo and everything it owns
5862 ---------------------------------------------------------------------------*/
5863 static void free_class_info( ClassInfo* info ) {
5868 clear_class_info( info );
5870 // If it's one of the static instances, just mark it as not in use
5873 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5874 if( info == static_class_info + i ) {
5875 static_class_info[ i ].in_use = 0;
5880 // Otherwise it must have been malloc'd, so free it
5885 /* --------------------------------------------------------------------------
5886 Populate an already-allocated ClassInfo. Return 0 if successful, 1 if not.
5887 ---------------------------------------------------------------------------*/
5888 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
5891 osrfLogError( OSRF_LOG_MARK,
5892 "%s ERROR: No ClassInfo available to populate", MODULENAME );
5893 info->alias = info->class_name = info->source_def = NULL;
5894 info->class_def = info->fields = info->links = NULL;
5899 osrfLogError( OSRF_LOG_MARK,
5900 "%s ERROR: No class name provided for lookup", MODULENAME );
5901 info->alias = info->class_name = info->source_def = NULL;
5902 info->class_def = info->fields = info->links = NULL;
5906 // Alias defaults to class name if not supplied
5907 if( ! alias || ! alias[ 0 ] )
5910 // Look up class info in the IDL
5911 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
5913 osrfLogError( OSRF_LOG_MARK,
5914 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
5915 info->alias = info->class_name = info->source_def = NULL;
5916 info->class_def = info->fields = info->links = NULL;
5918 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
5919 osrfLogError( OSRF_LOG_MARK,
5920 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
5921 info->alias = info->class_name = info->source_def = NULL;
5922 info->class_def = info->fields = info->links = NULL;
5926 osrfHash* links = osrfHashGet( class_def, "links" );
5928 osrfLogError( OSRF_LOG_MARK,
5929 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
5930 info->alias = info->class_name = info->source_def = NULL;
5931 info->class_def = info->fields = info->links = NULL;
5935 osrfHash* fields = osrfHashGet( class_def, "fields" );
5937 osrfLogError( OSRF_LOG_MARK,
5938 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
5939 info->alias = info->class_name = info->source_def = NULL;
5940 info->class_def = info->fields = info->links = NULL;
5944 char* source_def = getRelation( class_def );
5948 // We got everything we need, so populate the ClassInfo
5949 if( strlen( alias ) > ALIAS_STORE_SIZE )
5950 info->alias = strdup( alias );
5952 strcpy( info->alias_store, alias );
5953 info->alias = info->alias_store;
5956 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
5957 info->class_name = strdup( class );
5959 strcpy( info->class_name_store, class );
5960 info->class_name = info->class_name_store;
5963 info->source_def = source_def;
5965 info->class_def = class_def;
5966 info->links = links;
5967 info->fields = fields;
5972 #define STATIC_FRAME_COUNT 3
5974 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
5976 /* ---------------------------------------------------------------------------
5977 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5979 ---------------------------------------------------------------------------*/
5980 static QueryFrame* allocate_frame( void ) {
5981 // In order to reduce the number of mallocs and frees, we return a static
5982 // instance of QueryFrame, if we can find one that we're not already using.
5983 // We rely on the fact that the compiler will implicitly initialize the
5984 // static instances so that in_use == 0.
5987 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5988 if( ! static_frame[ i ].in_use ) {
5989 static_frame[ i ].in_use = 1;
5990 return static_frame + i;
5994 // The static ones are all in use. Malloc one.
5996 return safe_malloc( sizeof( QueryFrame ) );
5999 /* --------------------------------------------------------------------------
6000 Free a QueryFrame, and all the memory it owns.
6001 ---------------------------------------------------------------------------*/
6002 static void free_query_frame( QueryFrame* frame ) {
6007 clear_class_info( &frame->core );
6009 // Free the join list
6011 ClassInfo* info = frame->join_list;
6014 free_class_info( info );
6018 frame->join_list = NULL;
6021 // If the frame is a static instance, just mark it as unused
6023 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6024 if( frame == static_frame + i ) {
6025 static_frame[ i ].in_use = 0;
6030 // Otherwise it must have been malloc'd, so free it
6035 /* --------------------------------------------------------------------------
6036 Search a given QueryFrame for a specified alias. If you find it, return
6037 a pointer to the corresponding ClassInfo. Otherwise return NULL.
6038 ---------------------------------------------------------------------------*/
6039 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6040 if( ! frame || ! target ) {
6044 ClassInfo* found_class = NULL;
6046 if( !strcmp( target, frame->core.alias ) )
6047 return &(frame->core);
6049 ClassInfo* curr_class = frame->join_list;
6050 while( curr_class ) {
6051 if( strcmp( target, curr_class->alias ) )
6052 curr_class = curr_class->next;
6054 found_class = curr_class;
6063 /* --------------------------------------------------------------------------
6064 Push a new (blank) QueryFrame onto the stack.
6065 ---------------------------------------------------------------------------*/
6066 static void push_query_frame( void ) {
6067 QueryFrame* frame = allocate_frame();
6068 frame->join_list = NULL;
6069 frame->next = curr_query;
6071 // Initialize the ClassInfo for the core class
6072 ClassInfo* core = &frame->core;
6073 core->alias = core->class_name = core->source_def = NULL;
6074 core->class_def = core->fields = core->links = NULL;
6079 /* --------------------------------------------------------------------------
6080 Pop a QueryFrame off the stack and destroy it
6081 ---------------------------------------------------------------------------*/
6082 static void pop_query_frame( void ) {
6087 QueryFrame* popped = curr_query;
6088 curr_query = popped->next;
6090 free_query_frame( popped );
6093 /* --------------------------------------------------------------------------
6094 Populate the ClassInfo for the core class. Return 0 if successful, 1 if not.
6095 ---------------------------------------------------------------------------*/
6096 static int add_query_core( const char* alias, const char* class_name ) {
6099 if( ! curr_query ) {
6100 osrfLogError( OSRF_LOG_MARK,
6101 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
6103 } else if( curr_query->core.alias ) {
6104 osrfLogError( OSRF_LOG_MARK,
6105 "%s ERROR: Core class %s already populated as %s",
6106 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
6110 build_class_info( &curr_query->core, alias, class_name );
6111 if( curr_query->core.alias )
6114 osrfLogError( OSRF_LOG_MARK,
6115 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
6120 /* --------------------------------------------------------------------------
6121 Search the current QueryFrame for a specified alias. If you find it,
6122 return a pointer to the corresponding ClassInfo. Otherwise return NULL.
6123 ---------------------------------------------------------------------------*/
6124 static ClassInfo* search_alias( const char* target ) {
6125 return search_alias_in_frame( curr_query, target );
6128 /* --------------------------------------------------------------------------
6129 Search all levels of query for a specified alias, starting with the
6130 current query. If you find it, return a pointer to the corresponding
6131 ClassInfo. Otherwise return NULL.
6132 ---------------------------------------------------------------------------*/
6133 static ClassInfo* search_all_alias( const char* target ) {
6134 ClassInfo* found_class = NULL;
6135 QueryFrame* curr_frame = curr_query;
6137 while( curr_frame ) {
6138 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6141 curr_frame = curr_frame->next;
6147 /* --------------------------------------------------------------------------
6148 Add a class to the list of classes joined to the current query.
6149 ---------------------------------------------------------------------------*/
6150 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6152 if( ! classname || ! *classname ) { // sanity check
6153 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6160 const ClassInfo* conflict = search_alias( alias );
6162 osrfLogError( OSRF_LOG_MARK,
6163 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6164 MODULENAME, alias, conflict->class_name );
6168 ClassInfo* info = allocate_class_info();
6170 if( build_class_info( info, alias, classname ) ) {
6171 free_class_info( info );
6175 // Add the new ClassInfo to the join list of the current QueryFrame
6176 info->next = curr_query->join_list;
6177 curr_query->join_list = info;
6182 /* --------------------------------------------------------------------------
6183 Destroy all nodes on the query stack.
6184 ---------------------------------------------------------------------------*/
6185 static void clear_query_stack( void ) {