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. Save a transaction ID for future reference.
762 - authkey (PCRUD only)
764 Return to client: Transaction ID
766 int beginTransaction ( osrfMethodContext* ctx ) {
767 if(osrfMethodVerifyContext( ctx )) {
768 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
773 jsonObject* user = verifyUserPCRUD( ctx );
776 jsonObjectFree(user);
779 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
781 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
782 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
783 "osrfMethodException", ctx->request, "Error starting transaction" );
787 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
788 osrfAppRespondComplete( ctx, ret );
795 @brief Implement the savepoint.set method.
796 @param ctx Pointer to the method context.
797 @return Zero if successful, or -1 if not.
799 Issue a SAVEPOINT to the database server.
802 - authkey (PCRUD only)
805 Return to client: Savepoint name
807 int setSavepoint ( osrfMethodContext* ctx ) {
808 if(osrfMethodVerifyContext( ctx )) {
809 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
816 jsonObject* user = verifyUserPCRUD( ctx );
819 jsonObjectFree(user);
822 // Verify that a transaction is pending
823 if( getXactId( ctx ) == NULL ) {
824 osrfAppSessionStatus(
826 OSRF_STATUS_INTERNALSERVERERROR,
827 "osrfMethodException",
829 "No active transaction -- required for savepoints"
834 // Get the savepoint name from the method params
835 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
837 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
841 "%s: Error creating savepoint %s in transaction %s",
846 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
847 "osrfMethodException", ctx->request, "Error creating savepoint" );
850 jsonObject* ret = jsonNewObject(spName);
851 osrfAppRespondComplete( ctx, ret );
858 @brief Implement the savepoint.release method.
859 @param ctx Pointer to the method context.
860 @return Zero if successful, or -1 if not.
862 Issue a RELEASE SAVEPOINT to the database server.
865 - authkey (PCRUD only)
868 Return to client: Savepoint name
870 int releaseSavepoint ( osrfMethodContext* ctx ) {
871 if(osrfMethodVerifyContext( ctx )) {
872 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
879 jsonObject* user = verifyUserPCRUD( ctx );
882 jsonObjectFree(user);
885 // Verify that a transaction is pending
886 if( getXactId( ctx ) == NULL ) {
887 osrfAppSessionStatus(
889 OSRF_STATUS_INTERNALSERVERERROR,
890 "osrfMethodException",
892 "No active transaction -- required for savepoints"
897 // Get the savepoint name from the method params
898 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
900 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
904 "%s: Error releasing savepoint %s in transaction %s",
909 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
910 "osrfMethodException", ctx->request, "Error releasing savepoint" );
913 jsonObject* ret = jsonNewObject(spName);
914 osrfAppRespondComplete( ctx, ret );
921 @brief Implement the savepoint.rollback method.
922 @param ctx Pointer to the method context.
923 @return Zero if successful, or -1 if not.
925 Issue a ROLLBACK TO SAVEPOINT to the database server.
928 - authkey (PCRUD only)
931 Return to client: Savepoint name
933 int rollbackSavepoint ( osrfMethodContext* ctx ) {
934 if(osrfMethodVerifyContext( ctx )) {
935 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
942 jsonObject* user = verifyUserPCRUD( ctx );
945 jsonObjectFree(user);
948 // Verify that a transaction is pending
949 if( getXactId( ctx ) == NULL ) {
950 osrfAppSessionStatus(
952 OSRF_STATUS_INTERNALSERVERERROR,
953 "osrfMethodException",
955 "No active transaction -- required for savepoints"
960 // Get the savepoint name from the method params
961 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
963 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
967 "%s: Error rolling back savepoint %s in transaction %s",
972 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
973 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
976 jsonObject* ret = jsonNewObject(spName);
977 osrfAppRespondComplete( ctx, ret );
984 @brief Implement the transaction.commit method.
985 @param ctx Pointer to the method context.
986 @return Zero if successful, or -1 if not.
988 Issue a COMMIT to the database server.
991 - authkey (PCRUD only)
993 Return to client: Transaction ID.
995 int commitTransaction ( osrfMethodContext* ctx ) {
996 if(osrfMethodVerifyContext( ctx )) {
997 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1002 jsonObject* user = verifyUserPCRUD( ctx );
1005 jsonObjectFree(user);
1008 // Verify that a transaction is pending
1009 if( getXactId( ctx ) == NULL ) {
1010 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1011 "osrfMethodException", ctx->request, "No active transaction to commit" );
1015 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
1017 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
1018 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1019 "osrfMethodException", ctx->request, "Error committing transaction" );
1023 jsonObject* ret = jsonNewObject(ctx->session->session_id);
1024 osrfAppRespondComplete( ctx, ret );
1025 jsonObjectFree(ret);
1031 @brief Implement the transaction.rollback method.
1032 @param ctx Pointer to the method context.
1033 @return Zero if successful, or -1 if not.
1035 Issue a ROLLBACK to the database server.
1038 - authkey (PCRUD only)
1040 Return to client: Transaction ID
1042 int rollbackTransaction ( osrfMethodContext* ctx ) {
1043 if(osrfMethodVerifyContext( ctx )) {
1044 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1049 jsonObject* user = verifyUserPCRUD( ctx );
1052 jsonObjectFree(user);
1055 // Verify that a transaction is pending
1056 if( getXactId( ctx ) == NULL ) {
1057 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1058 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1062 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
1064 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
1065 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1066 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1070 jsonObject* ret = jsonNewObject(ctx->session->session_id);
1071 osrfAppRespondComplete( ctx, ret );
1072 jsonObjectFree(ret);
1077 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
1078 if(osrfMethodVerifyContext( ctx )) {
1079 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1083 osrfHash* meta = (osrfHash*) ctx->method->userData;
1084 osrfHash* class_obj = osrfHashGet( meta, "class" );
1088 const char* methodtype = osrfHashGet(meta, "methodtype");
1089 jsonObject * obj = NULL;
1091 if (!strcmp(methodtype, "create")) {
1092 obj = doCreate(ctx, &err);
1093 osrfAppRespondComplete( ctx, obj );
1095 else if (!strcmp(methodtype, "retrieve")) {
1096 obj = doRetrieve(ctx, &err);
1097 osrfAppRespondComplete( ctx, obj );
1099 else if (!strcmp(methodtype, "update")) {
1100 obj = doUpdate(ctx, &err);
1101 osrfAppRespondComplete( ctx, obj );
1103 else if (!strcmp(methodtype, "delete")) {
1104 obj = doDelete(ctx, &err);
1105 osrfAppRespondComplete( ctx, obj );
1107 else if (!strcmp(methodtype, "search")) {
1109 jsonObject* where_clause;
1110 jsonObject* rest_of_query;
1113 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1114 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1116 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1117 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1120 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
1124 jsonObject* cur = 0;
1125 unsigned long res_idx = 0;
1126 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1128 if(!verifyObjectPCRUD(ctx, cur)) continue;
1130 osrfAppRespond( ctx, cur );
1132 osrfAppRespondComplete( ctx, NULL );
1134 } else if (!strcmp(methodtype, "id_list")) {
1136 jsonObject* where_clause;
1137 jsonObject* rest_of_query;
1139 // We use the where clause without change. But we need
1140 // to massage the rest of the query, so we work with a copy
1141 // of it instead of modifying the original.
1143 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1144 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1146 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1147 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1150 if ( rest_of_query ) {
1151 jsonObjectRemoveKey( rest_of_query, "select" );
1152 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1153 jsonObjectRemoveKey( rest_of_query, "flesh" );
1154 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
1156 rest_of_query = jsonNewObjectType( JSON_HASH );
1159 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1161 // Build a SELECT list containing just the primary key,
1162 // i.e. like { "classname":["keyname"] }
1163 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1164 jsonObjectPush( col_list_obj, // Load array with name of primary key
1165 jsonNewObject( osrfHashGet( class_obj, "primarykey" ) ) );
1166 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1167 jsonObjectSetKey( select_clause, osrfHashGet( class_obj, "classname" ), col_list_obj );
1169 jsonObjectSetKey( rest_of_query, "select", select_clause );
1171 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
1173 jsonObjectFree( rest_of_query );
1177 unsigned long res_idx = 0;
1178 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1180 if(!verifyObjectPCRUD(ctx, cur)) continue;
1182 osrfAppRespond( ctx,
1183 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
1186 osrfAppRespondComplete( ctx, NULL );
1189 osrfAppRespondComplete( ctx, obj );
1192 jsonObjectFree(obj);
1197 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1200 osrfHash* meta = (osrfHash*) ctx->method->userData;
1201 osrfHash* class = osrfHashGet( meta, "class" );
1203 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1205 const char* temp_classname = param->classname;
1206 if( ! temp_classname )
1207 temp_classname = "(null)";
1209 growing_buffer* msg = buffer_init(128);
1212 "%s: %s method for type %s was passed a %s",
1214 osrfHashGet(meta, "methodtype"),
1215 osrfHashGet(class, "classname"),
1219 char* m = buffer_release(msg);
1220 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1229 ret = verifyObjectPCRUD( ctx, param );
1237 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1238 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1239 jsonObject* auth_object = jsonNewObject(auth);
1240 jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve",
1242 jsonObjectFree(auth_object);
1244 if (!user->classname || strcmp(user->classname, "au")) {
1246 growing_buffer* msg = buffer_init(128);
1249 "%s: permacrud received a bad auth token: %s",
1254 char* m = buffer_release(msg);
1255 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1259 jsonObjectFree(user);
1267 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1269 dbhandle = writehandle;
1271 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1272 osrfHash* class = osrfHashGet( method_metadata, "class" );
1273 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1276 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
1277 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1278 } else if ( *method_type == 'u' || *method_type == 'd' ) {
1279 fetch = 1; // MUST go to the db for the object for update and delete
1282 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
1285 // No permacrud for this method type on this class
1287 growing_buffer* msg = buffer_init(128);
1290 "%s: %s on class %s has no permacrud IDL entry",
1292 osrfHashGet(method_metadata, "methodtype"),
1293 osrfHashGet(class, "classname")
1296 char* m = buffer_release(msg);
1297 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1298 "osrfMethodException", ctx->request, m );
1305 jsonObject* user = verifyUserPCRUD( ctx );
1309 int userid = atoi( oilsFMGetString( user, "id" ) );
1310 jsonObjectFree(user);
1312 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1313 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1314 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1316 osrfStringArray* context_org_array = osrfNewStringArray(1);
1319 char* pkey_value = NULL;
1320 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1321 osrfLogDebug( OSRF_LOG_MARK,
1322 "global-level permissions required, fetching top of the org tree" );
1324 // check for perm at top of org tree
1325 char* org_tree_root_id = org_tree_root( ctx );
1326 if( org_tree_root_id ) {
1327 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1328 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1330 osrfStringArrayFree( context_org_array );
1335 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1336 "fetching context org ids" );
1337 const char* pkey = osrfHashGet(class, "primarykey");
1338 jsonObject *param = NULL;
1340 if (obj->classname) {
1341 pkey_value = oilsFMGetString( obj, pkey );
1343 param = jsonObjectClone(obj);
1344 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1347 pkey_value = jsonObjectToSimpleString( obj );
1349 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1350 "of %s and retrieving from the database", pkey_value );
1354 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1355 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1356 jsonObjectFree(_tmp_params);
1358 param = jsonObjectExtractIndex(_list, 0);
1359 jsonObjectFree(_list);
1363 osrfLogDebug( OSRF_LOG_MARK,
1364 "Object not found in the database with primary key %s of %s",
1367 growing_buffer* msg = buffer_init(128);
1370 "%s: no object found with primary key %s of %s",
1376 char* m = buffer_release(msg);
1377 osrfAppSessionStatus(
1379 OSRF_STATUS_INTERNALSERVERERROR,
1380 "osrfMethodException",
1386 if (pkey_value) free(pkey_value);
1391 if (local_context->size > 0) {
1392 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1393 local_context->size);
1395 const char* lcontext = NULL;
1396 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1397 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1400 "adding class-local field %s (value: %s) to the context org list",
1402 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1407 if (foreign_context) {
1408 unsigned long class_count = osrfHashGetCount( foreign_context );
1409 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1411 if (class_count > 0) {
1413 osrfHash* fcontext = NULL;
1414 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1415 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1416 const char* class_name = osrfHashIteratorKey( class_itr );
1417 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1421 "%d foreign context fields(s) specified for class %s",
1422 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1426 char* foreign_pkey = osrfHashGet(fcontext, "field");
1427 char* foreign_pkey_value =
1428 oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1430 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1432 jsonObject* _list = doFieldmapperSearch(
1433 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1435 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1436 jsonObjectFree(_tmp_params);
1437 jsonObjectFree(_list);
1439 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1441 if (_fparam && jump_list) {
1442 const char* flink = NULL;
1444 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1445 free(foreign_pkey_value);
1447 osrfHash* foreign_link_hash =
1448 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1450 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1451 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1453 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1455 _list = doFieldmapperSearch(
1457 osrfHashGet( oilsIDL(),
1458 osrfHashGet( foreign_link_hash, "class" ) ),
1464 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1465 jsonObjectFree(_tmp_params);
1466 jsonObjectFree(_list);
1472 growing_buffer* msg = buffer_init(128);
1475 "%s: no object found with primary key %s of %s",
1481 char* m = buffer_release(msg);
1482 osrfAppSessionStatus(
1484 OSRF_STATUS_INTERNALSERVERERROR,
1485 "osrfMethodException",
1491 osrfHashIteratorFree(class_itr);
1492 free(foreign_pkey_value);
1493 jsonObjectFree(param);
1498 free(foreign_pkey_value);
1501 const char* foreign_field = NULL;
1502 while ( (foreign_field = osrfStringArrayGetString(
1503 osrfHashGet(fcontext,"context" ), j++)) ) {
1504 osrfStringArrayAdd( context_org_array,
1505 oilsFMGetString( _fparam, foreign_field ) );
1508 "adding foreign class %s field %s (value: %s) to the context org list",
1511 osrfStringArrayGetString(
1512 context_org_array, context_org_array->size - 1)
1516 jsonObjectFree(_fparam);
1519 osrfHashIteratorFree( class_itr );
1523 jsonObjectFree(param);
1526 const char* context_org = NULL;
1527 const char* perm = NULL;
1530 if (permission->size == 0) {
1531 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1536 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1538 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1544 "Checking object permission [%s] for user %d "
1545 "on object %s (class %s) at org %d",
1549 osrfHashGet(class, "classname"),
1553 result = dbi_conn_queryf(
1555 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1558 osrfHashGet(class, "classname"),
1566 "Received a result for object permission [%s] "
1567 "for user %d on object %s (class %s) at org %d",
1571 osrfHashGet(class, "classname"),
1575 if (dbi_result_first_row(result)) {
1576 jsonObject* return_val = oilsMakeJSONFromResult( result );
1577 const char* has_perm = jsonObjectGetString(
1578 jsonObjectGetKeyConst(return_val, "has_perm") );
1582 "Status of object permission [%s] for user %d "
1583 "on object %s (class %s) at org %d is %s",
1587 osrfHashGet(class, "classname"),
1592 if ( *has_perm == 't' ) OK = 1;
1593 jsonObjectFree(return_val);
1596 dbi_result_free(result);
1602 osrfLogDebug( OSRF_LOG_MARK,
1603 "Checking non-object permission [%s] for user %d at org %d",
1604 perm, userid, atoi(context_org) );
1605 result = dbi_conn_queryf(
1607 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1614 osrfLogDebug( OSRF_LOG_MARK,
1615 "Received a result for permission [%s] for user %d at org %d",
1616 perm, userid, atoi(context_org) );
1617 if ( dbi_result_first_row(result) ) {
1618 jsonObject* return_val = oilsMakeJSONFromResult( result );
1619 const char* has_perm = jsonObjectGetString(
1620 jsonObjectGetKeyConst(return_val, "has_perm") );
1621 osrfLogDebug( OSRF_LOG_MARK,
1622 "Status of permission [%s] for user %d at org %d is [%s]",
1623 perm, userid, atoi(context_org), has_perm );
1624 if ( *has_perm == 't' )
1626 jsonObjectFree(return_val);
1629 dbi_result_free(result);
1638 if (pkey_value) free(pkey_value);
1639 osrfStringArrayFree(context_org_array);
1645 @brief Look up the root of the org_unit tree.
1646 @param ctx Pointer to the method context.
1647 @return The id of the root org unit, as a character string.
1649 Query actor.org_unit where parent_ou is null, and return the id as a string.
1651 This function assumes that there is only one root org unit, i.e. that we
1652 have a single tree, not a forest.
1654 The calling code is responsible for freeing the returned string.
1656 static char* org_tree_root( osrfMethodContext* ctx ) {
1658 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1659 static time_t last_lookup_time = 0;
1660 time_t current_time = time( NULL );
1662 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1663 // We successfully looked this up less than an hour ago.
1664 // It's not likely to have changed since then.
1665 return strdup( cached_root_id );
1667 last_lookup_time = current_time;
1670 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1671 jsonObject* result = doFieldmapperSearch(
1672 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1673 jsonObjectFree( where_clause );
1675 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1678 jsonObjectFree( result );
1680 growing_buffer* msg = buffer_init(128);
1681 OSRF_BUFFER_ADD( msg, MODULENAME );
1682 OSRF_BUFFER_ADD( msg,
1683 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1685 char* m = buffer_release(msg);
1686 osrfAppSessionStatus( ctx->session,
1687 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1690 cached_root_id[ 0 ] = '\0';
1694 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1695 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1697 jsonObjectFree( result );
1699 strcpy( cached_root_id, root_org_unit_id );
1700 return root_org_unit_id;
1704 @brief Create a JSON_HASH with a single key/value pair.
1705 @param key The key of the key/value pair.
1706 @param value the value of the key/value pair.
1707 @return Pointer to a newly created jsonObject of type JSON_HASH.
1709 The value of the key/value is either a string or (if @a value is NULL) a null.
1711 static jsonObject* single_hash( const char* key, const char* value ) {
1713 if( ! key ) key = "";
1715 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1716 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1722 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1724 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1726 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1727 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1729 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1730 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1733 if (!verifyObjectClass(ctx, target)) {
1738 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1740 const char* trans_id = getXactId( ctx );
1742 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1744 osrfAppSessionStatus(
1746 OSRF_STATUS_BADREQUEST,
1747 "osrfMethodException",
1749 "No active transaction -- required for CREATE"
1755 // The following test is harmless but redundant. If a class is
1756 // readonly, we don't register a create method for it.
1757 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1758 osrfAppSessionStatus(
1760 OSRF_STATUS_BADREQUEST,
1761 "osrfMethodException",
1763 "Cannot INSERT readonly class"
1769 // Set the last_xact_id
1770 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1772 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1773 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1776 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1778 dbhandle = writehandle;
1780 osrfHash* fields = osrfHashGet(meta, "fields");
1781 char* pkey = osrfHashGet(meta, "primarykey");
1782 char* seq = osrfHashGet(meta, "sequence");
1784 growing_buffer* table_buf = buffer_init(128);
1785 growing_buffer* col_buf = buffer_init(128);
1786 growing_buffer* val_buf = buffer_init(128);
1788 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1789 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1790 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1791 buffer_add(val_buf,"VALUES (");
1795 osrfHash* field = NULL;
1796 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1797 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1799 const char* field_name = osrfHashIteratorKey( field_itr );
1801 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1804 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1807 if (field_object && field_object->classname) {
1808 value = oilsFMGetString(
1810 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1812 } else if( field_object && JSON_BOOL == field_object->type ) {
1813 if( jsonBoolIsTrue( field_object ) )
1814 value = strdup( "t" );
1816 value = strdup( "f" );
1818 value = jsonObjectToSimpleString( field_object );
1824 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1825 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1828 buffer_add(col_buf, field_name);
1830 if (!field_object || field_object->type == JSON_NULL) {
1831 buffer_add( val_buf, "DEFAULT" );
1833 } else if ( !strcmp(get_primitive( field ), "number") ) {
1834 const char* numtype = get_datatype( field );
1835 if ( !strcmp( numtype, "INT8") ) {
1836 buffer_fadd( val_buf, "%lld", atoll(value) );
1838 } else if ( !strcmp( numtype, "INT") ) {
1839 buffer_fadd( val_buf, "%d", atoi(value) );
1841 } else if ( !strcmp( numtype, "NUMERIC") ) {
1842 buffer_fadd( val_buf, "%f", atof(value) );
1845 if ( dbi_conn_quote_string(writehandle, &value) ) {
1846 OSRF_BUFFER_ADD( val_buf, value );
1849 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1850 osrfAppSessionStatus(
1852 OSRF_STATUS_INTERNALSERVERERROR,
1853 "osrfMethodException",
1855 "Error quoting string -- please see the error log for more details"
1858 buffer_free(table_buf);
1859 buffer_free(col_buf);
1860 buffer_free(val_buf);
1870 osrfHashIteratorFree( field_itr );
1872 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1873 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1875 char* table_str = buffer_release(table_buf);
1876 char* col_str = buffer_release(col_buf);
1877 char* val_str = buffer_release(val_buf);
1878 growing_buffer* sql = buffer_init(128);
1879 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1884 char* query = buffer_release(sql);
1886 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1889 dbi_result result = dbi_conn_query(writehandle, query);
1891 jsonObject* obj = NULL;
1894 obj = jsonNewObject(NULL);
1897 "%s ERROR inserting %s object using query [%s]",
1899 osrfHashGet(meta, "fieldmapper"),
1902 osrfAppSessionStatus(
1904 OSRF_STATUS_INTERNALSERVERERROR,
1905 "osrfMethodException",
1907 "INSERT error -- please see the error log for more details"
1912 char* id = oilsFMGetString(target, pkey);
1914 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1915 growing_buffer* _id = buffer_init(10);
1916 buffer_fadd(_id, "%lld", new_id);
1917 id = buffer_release(_id);
1920 // Find quietness specification, if present
1921 const char* quiet_str = NULL;
1923 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1925 quiet_str = jsonObjectGetString( quiet_obj );
1928 if( str_is_true( quiet_str ) ) { // if quietness is specified
1929 obj = jsonNewObject(id);
1933 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1934 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1936 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1938 jsonObjectFree( where_clause );
1943 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1946 jsonObjectFree( list );
1959 * Fetch one row from a specified table, using a specified value
1960 * for the primary key
1962 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1972 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1974 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos); // key value
1978 "%s retrieving %s object with primary key value of %s",
1980 osrfHashGet( class_def, "fieldmapper" ),
1981 jsonObjectGetString( id_obj )
1984 // Build a WHERE clause based on the key value
1985 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1988 osrfHashGet( class_def, "primarykey" ),
1989 jsonObjectClone( id_obj )
1992 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
1994 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
1996 jsonObjectFree( where_clause );
2000 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2001 jsonObjectFree( list );
2004 if(!verifyObjectPCRUD(ctx, obj)) {
2005 jsonObjectFree(obj);
2008 growing_buffer* msg = buffer_init(128);
2009 OSRF_BUFFER_ADD( msg, MODULENAME );
2010 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2012 char* m = buffer_release(msg);
2013 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
2024 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
2025 growing_buffer* val_buf = buffer_init(32);
2026 const char* numtype = get_datatype( field );
2028 if ( !strncmp( numtype, "INT", 3 ) ) {
2029 if (value->type == JSON_NUMBER)
2030 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2031 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2033 //const char* val_str = jsonObjectGetString( value );
2034 //buffer_fadd( val_buf, "%ld", atol(val_str) );
2035 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2038 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
2039 if (value->type == JSON_NUMBER)
2040 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
2041 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2043 //const char* val_str = jsonObjectGetString( value );
2044 //buffer_fadd( val_buf, "%f", atof(val_str) );
2045 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2049 // Presumably this was really intended ot be a string, so quote it
2050 char* str = jsonObjectToSimpleString( value );
2051 if ( dbi_conn_quote_string(dbhandle, &str) ) {
2052 OSRF_BUFFER_ADD( val_buf, str );
2055 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
2057 buffer_free(val_buf);
2062 return buffer_release(val_buf);
2065 static char* searchINPredicate (const char* class_alias, osrfHash* field,
2066 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2067 growing_buffer* sql_buf = buffer_init(32);
2073 osrfHashGet(field, "name")
2077 buffer_add(sql_buf, "IN (");
2078 } else if (!(strcasecmp(op,"not in"))) {
2079 buffer_add(sql_buf, "NOT IN (");
2081 buffer_add(sql_buf, "IN (");
2084 if (node->type == JSON_HASH) {
2085 // subquery predicate
2086 char* subpred = buildQuery( ctx, node, SUBSELECT );
2088 buffer_free( sql_buf );
2092 buffer_add(sql_buf, subpred);
2095 } else if (node->type == JSON_ARRAY) {
2096 // literal value list
2097 int in_item_index = 0;
2098 int in_item_first = 1;
2099 const jsonObject* in_item;
2100 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
2105 buffer_add(sql_buf, ", ");
2108 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2109 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
2110 MODULENAME, json_type( in_item->type ) );
2111 buffer_free(sql_buf);
2115 // Append the literal value -- quoted if not a number
2116 if ( JSON_NUMBER == in_item->type ) {
2117 char* val = jsonNumberToDBString( field, in_item );
2118 OSRF_BUFFER_ADD( sql_buf, val );
2121 } else if ( !strcmp( get_primitive( field ), "number") ) {
2122 char* val = jsonNumberToDBString( field, in_item );
2123 OSRF_BUFFER_ADD( sql_buf, val );
2127 char* key_string = jsonObjectToSimpleString(in_item);
2128 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
2129 OSRF_BUFFER_ADD( sql_buf, key_string );
2132 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
2134 buffer_free(sql_buf);
2140 if( in_item_first ) {
2141 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
2142 buffer_free( sql_buf );
2146 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2147 MODULENAME, json_type( node->type ) );
2148 buffer_free(sql_buf);
2152 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2154 return buffer_release(sql_buf);
2157 // Receive a JSON_ARRAY representing a function call. The first
2158 // entry in the array is the function name. The rest are parameters.
2159 static char* searchValueTransform( const jsonObject* array ) {
2161 if( array->size < 1 ) {
2162 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
2166 // Get the function name
2167 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2168 if( func_item->type != JSON_STRING ) {
2169 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2170 MODULENAME, json_type( func_item->type ) );
2174 growing_buffer* sql_buf = buffer_init(32);
2176 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2177 OSRF_BUFFER_ADD( sql_buf, "( " );
2179 // Get the parameters
2180 int func_item_index = 1; // We already grabbed the zeroth entry
2181 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2183 // Add a separator comma, if we need one
2184 if( func_item_index > 2 )
2185 buffer_add( sql_buf, ", " );
2187 // Add the current parameter
2188 if (func_item->type == JSON_NULL) {
2189 buffer_add( sql_buf, "NULL" );
2191 char* val = jsonObjectToSimpleString(func_item);
2192 if ( dbi_conn_quote_string(dbhandle, &val) ) {
2193 OSRF_BUFFER_ADD( sql_buf, val );
2196 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2197 buffer_free(sql_buf);
2204 buffer_add( sql_buf, " )" );
2206 return buffer_release(sql_buf);
2209 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
2210 const jsonObject* node, const char* op) {
2212 if( ! is_good_operator( op ) ) {
2213 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2217 char* val = searchValueTransform(node);
2221 growing_buffer* sql_buf = buffer_init(32);
2226 osrfHashGet(field, "name"),
2233 return buffer_release(sql_buf);
2236 // class_alias is a class name or other table alias
2237 // field is a field definition as stored in the IDL
2238 // node comes from the method parameter, and may represent an entry in the SELECT list
2239 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
2240 growing_buffer* sql_buf = buffer_init(32);
2242 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
2243 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
2245 if(transform_subcolumn) {
2246 if( ! is_identifier( transform_subcolumn ) ) {
2247 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2248 MODULENAME, transform_subcolumn );
2249 buffer_free( sql_buf );
2252 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2255 if (field_transform) {
2257 if( ! is_identifier( field_transform ) ) {
2258 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2259 MODULENAME, field_transform );
2260 buffer_free( sql_buf );
2264 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class_alias, osrfHashGet(field, "name"));
2265 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2268 if( array->type != JSON_ARRAY ) {
2269 osrfLogError( OSRF_LOG_MARK,
2270 "%s: Expected JSON_ARRAY for function params; found %s",
2271 MODULENAME, json_type( array->type ) );
2272 buffer_free( sql_buf );
2275 int func_item_index = 0;
2276 jsonObject* func_item;
2277 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2279 char* val = jsonObjectToSimpleString(func_item);
2282 buffer_add( sql_buf, ",NULL" );
2283 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
2284 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2285 OSRF_BUFFER_ADD( sql_buf, val );
2287 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2289 buffer_free(sql_buf);
2296 buffer_add( sql_buf, " )" );
2299 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2302 if (transform_subcolumn)
2303 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2305 return buffer_release(sql_buf);
2308 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2309 const jsonObject* node, const char* op ) {
2311 if( ! is_good_operator( op ) ) {
2312 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2316 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2317 if( ! field_transform )
2320 int extra_parens = 0; // boolean
2322 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2323 if ( ! value_obj ) {
2324 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2326 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
2327 free(field_transform);
2331 } else if ( value_obj->type == JSON_ARRAY ) {
2332 value = searchValueTransform( value_obj );
2334 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
2335 free( field_transform );
2338 } else if ( value_obj->type == JSON_HASH ) {
2339 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2341 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
2342 free(field_transform);
2346 } else if ( value_obj->type == JSON_NUMBER ) {
2347 value = jsonNumberToDBString( field, value_obj );
2348 } else if ( value_obj->type == JSON_NULL ) {
2349 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
2350 free(field_transform);
2352 } else if ( value_obj->type == JSON_BOOL ) {
2353 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
2354 free(field_transform);
2357 if ( !strcmp( get_primitive( field ), "number") ) {
2358 value = jsonNumberToDBString( field, value_obj );
2360 value = jsonObjectToSimpleString( value_obj );
2361 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2362 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
2364 free(field_transform);
2370 const char* left_parens = "";
2371 const char* right_parens = "";
2373 if( extra_parens ) {
2378 growing_buffer* sql_buf = buffer_init(32);
2382 "%s%s %s %s %s %s%s",
2393 free(field_transform);
2395 return buffer_release(sql_buf);
2398 static char* searchSimplePredicate (const char* op, const char* class_alias,
2399 osrfHash* field, const jsonObject* node) {
2401 if( ! is_good_operator( op ) ) {
2402 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2408 // Get the value to which we are comparing the specified column
2409 if (node->type != JSON_NULL) {
2410 if ( node->type == JSON_NUMBER ) {
2411 val = jsonNumberToDBString( field, node );
2412 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2413 val = jsonNumberToDBString( field, node );
2415 val = jsonObjectToSimpleString(node);
2420 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2421 // Value is not numeric; enclose it in quotes
2422 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2423 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2429 // Compare to a null value
2430 val = strdup( "NULL" );
2431 if (strcmp( op, "=" ))
2437 growing_buffer* sql_buf = buffer_init(32);
2438 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2439 char* pred = buffer_release( sql_buf );
2446 static char* searchBETWEENPredicate (const char* class_alias,
2447 osrfHash* field, const jsonObject* node) {
2449 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2450 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2452 if( NULL == y_node ) {
2453 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2456 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2457 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2464 if ( !strcmp( get_primitive( field ), "number") ) {
2465 x_string = jsonNumberToDBString(field, x_node);
2466 y_string = jsonNumberToDBString(field, y_node);
2469 x_string = jsonObjectToSimpleString(x_node);
2470 y_string = jsonObjectToSimpleString(y_node);
2471 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2472 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2473 MODULENAME, x_string, y_string);
2480 growing_buffer* sql_buf = buffer_init(32);
2481 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2482 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2486 return buffer_release(sql_buf);
2489 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2490 jsonObject* node, osrfMethodContext* ctx ) {
2493 if (node->type == JSON_ARRAY) { // equality IN search
2494 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2495 } else if (node->type == JSON_HASH) { // other search
2496 jsonIterator* pred_itr = jsonNewIterator( node );
2497 if( !jsonIteratorHasNext( pred_itr ) ) {
2498 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2499 MODULENAME, osrfHashGet(field, "name") );
2501 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2503 // Verify that there are no additional predicates
2504 if( jsonIteratorHasNext( pred_itr ) ) {
2505 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2506 MODULENAME, osrfHashGet(field, "name") );
2507 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2508 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2509 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2510 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
2511 else if ( pred_node->type == JSON_ARRAY )
2512 pred = searchFunctionPredicate( class_info->alias, field, pred_node, pred_itr->key );
2513 else if ( pred_node->type == JSON_HASH )
2514 pred = searchFieldTransformPredicate( class_info, field, pred_node, pred_itr->key );
2516 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2518 jsonIteratorFree(pred_itr);
2520 } else if (node->type == JSON_NULL) { // IS NULL search
2521 growing_buffer* _p = buffer_init(64);
2524 "\"%s\".%s IS NULL",
2525 class_info->class_name,
2526 osrfHashGet(field, "name")
2528 pred = buffer_release(_p);
2529 } else { // equality search
2530 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2549 field : call_number,
2565 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2567 const jsonObject* working_hash;
2568 jsonObject* freeable_hash = NULL;
2570 if (join_hash->type == JSON_HASH) {
2571 working_hash = join_hash;
2572 } else if (join_hash->type == JSON_STRING) {
2573 // turn it into a JSON_HASH by creating a wrapper
2574 // around a copy of the original
2575 const char* _tmp = jsonObjectGetString( join_hash );
2576 freeable_hash = jsonNewObjectType(JSON_HASH);
2577 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2578 working_hash = freeable_hash;
2582 "%s: JOIN failed; expected JSON object type not found",
2588 growing_buffer* join_buf = buffer_init(128);
2589 const char* leftclass = left_info->class_name;
2591 jsonObject* snode = NULL;
2592 jsonIterator* search_itr = jsonNewIterator( working_hash );
2594 while ( (snode = jsonIteratorNext( search_itr )) ) {
2595 const char* right_alias = search_itr->key;
2597 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2599 class = right_alias;
2601 const ClassInfo* right_info = add_joined_class( right_alias, class );
2605 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2609 jsonIteratorFree( search_itr );
2610 buffer_free( join_buf );
2612 jsonObjectFree( freeable_hash );
2615 osrfHash* links = right_info->links;
2616 const char* table = right_info->source_def;
2618 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2619 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2621 if (field && !fkey) {
2622 // Look up the corresponding join column in the IDL.
2623 // The link must be defined in the child table,
2624 // and point to the right parent table.
2625 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2626 const char* reltype = NULL;
2627 const char* other_class = NULL;
2628 reltype = osrfHashGet( idl_link, "reltype" );
2629 if( reltype && strcmp( reltype, "has_many" ) )
2630 other_class = osrfHashGet( idl_link, "class" );
2631 if( other_class && !strcmp( other_class, leftclass ) )
2632 fkey = osrfHashGet( idl_link, "key" );
2636 "%s: JOIN failed. No link defined from %s.%s to %s",
2642 buffer_free(join_buf);
2644 jsonObjectFree(freeable_hash);
2645 jsonIteratorFree(search_itr);
2649 } else if (!field && fkey) {
2650 // Look up the corresponding join column in the IDL.
2651 // The link must be defined in the child table,
2652 // and point to the right parent table.
2653 osrfHash* left_links = left_info->links;
2654 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2655 const char* reltype = NULL;
2656 const char* other_class = NULL;
2657 reltype = osrfHashGet( idl_link, "reltype" );
2658 if( reltype && strcmp( reltype, "has_many" ) )
2659 other_class = osrfHashGet( idl_link, "class" );
2660 if( other_class && !strcmp( other_class, class ) )
2661 field = osrfHashGet( idl_link, "key" );
2665 "%s: JOIN failed. No link defined from %s.%s to %s",
2671 buffer_free(join_buf);
2673 jsonObjectFree(freeable_hash);
2674 jsonIteratorFree(search_itr);
2678 } else if (!field && !fkey) {
2679 osrfHash* left_links = left_info->links;
2681 // For each link defined for the left class:
2682 // see if the link references the joined class
2683 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2684 osrfHash* curr_link = NULL;
2685 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2686 const char* other_class = osrfHashGet( curr_link, "class" );
2687 if( other_class && !strcmp( other_class, class ) ) {
2689 // In the IDL, the parent class doesn't know then names of the child
2690 // columns that are pointing to it, so don't use that end of the link
2691 const char* reltype = osrfHashGet( curr_link, "reltype" );
2692 if( reltype && strcmp( reltype, "has_many" ) ) {
2693 // Found a link between the classes
2694 fkey = osrfHashIteratorKey( itr );
2695 field = osrfHashGet( curr_link, "key" );
2700 osrfHashIteratorFree( itr );
2702 if (!field || !fkey) {
2703 // Do another such search, with the classes reversed
2705 // For each link defined for the joined class:
2706 // see if the link references the left class
2707 osrfHashIterator* itr = osrfNewHashIterator( links );
2708 osrfHash* curr_link = NULL;
2709 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2710 const char* other_class = osrfHashGet( curr_link, "class" );
2711 if( other_class && !strcmp( other_class, leftclass ) ) {
2713 // In the IDL, the parent class doesn't know then names of the child
2714 // columns that are pointing to it, so don't use that end of the link
2715 const char* reltype = osrfHashGet( curr_link, "reltype" );
2716 if( reltype && strcmp( reltype, "has_many" ) ) {
2717 // Found a link between the classes
2718 field = osrfHashIteratorKey( itr );
2719 fkey = osrfHashGet( curr_link, "key" );
2724 osrfHashIteratorFree( itr );
2727 if (!field || !fkey) {
2730 "%s: JOIN failed. No link defined between %s and %s",
2735 buffer_free(join_buf);
2737 jsonObjectFree(freeable_hash);
2738 jsonIteratorFree(search_itr);
2744 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2746 if ( !strcasecmp(type,"left") ) {
2747 buffer_add(join_buf, " LEFT JOIN");
2748 } else if ( !strcasecmp(type,"right") ) {
2749 buffer_add(join_buf, " RIGHT JOIN");
2750 } else if ( !strcasecmp(type,"full") ) {
2751 buffer_add(join_buf, " FULL JOIN");
2753 buffer_add(join_buf, " INNER JOIN");
2756 buffer_add(join_buf, " INNER JOIN");
2759 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2760 table, right_alias, right_alias, field, left_info->alias, fkey);
2762 // Add any other join conditions as specified by "filter"
2763 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2765 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2766 if ( filter_op && !strcasecmp("or",filter_op) ) {
2767 buffer_add( join_buf, " OR " );
2769 buffer_add( join_buf, " AND " );
2772 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2774 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2775 OSRF_BUFFER_ADD( join_buf, jpred );
2780 "%s: JOIN failed. Invalid conditional expression.",
2783 jsonIteratorFree( search_itr );
2784 buffer_free( join_buf );
2786 jsonObjectFree( freeable_hash );
2791 buffer_add(join_buf, " ) ");
2793 // Recursively add a nested join, if one is present
2794 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2796 char* jpred = searchJOIN( join_filter, right_info );
2798 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2799 OSRF_BUFFER_ADD( join_buf, jpred );
2802 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2803 jsonIteratorFree( search_itr );
2804 buffer_free( join_buf );
2806 jsonObjectFree( freeable_hash );
2813 jsonObjectFree(freeable_hash);
2814 jsonIteratorFree(search_itr);
2816 return buffer_release(join_buf);
2821 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2822 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2823 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2825 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2827 search_hash is the JSON expression of the conditions.
2828 meta is the class definition from the IDL, for the relevant table.
2829 opjoin_type indicates whether multiple conditions, if present, should be
2830 connected by AND or OR.
2831 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2832 to pass it to other functions -- and all they do with it is to use the session
2833 and request members to send error messages back to the client.
2837 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
2838 int opjoin_type, osrfMethodContext* ctx ) {
2842 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2845 class_info->class_def,
2850 growing_buffer* sql_buf = buffer_init(128);
2852 jsonObject* node = NULL;
2855 if ( search_hash->type == JSON_ARRAY ) {
2856 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2857 if( 0 == search_hash->size ) {
2860 "%s: Invalid predicate structure: empty JSON array",
2863 buffer_free( sql_buf );
2867 unsigned long i = 0;
2868 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
2872 if (opjoin_type == OR_OP_JOIN)
2873 buffer_add(sql_buf, " OR ");
2875 buffer_add(sql_buf, " AND ");
2878 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2880 buffer_free( sql_buf );
2884 buffer_fadd(sql_buf, "( %s )", subpred);
2888 } else if ( search_hash->type == JSON_HASH ) {
2889 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2890 jsonIterator* search_itr = jsonNewIterator( search_hash );
2891 if( !jsonIteratorHasNext( search_itr ) ) {
2894 "%s: Invalid predicate structure: empty JSON object",
2897 jsonIteratorFree( search_itr );
2898 buffer_free( sql_buf );
2902 while ( (node = jsonIteratorNext( search_itr )) ) {
2907 if (opjoin_type == OR_OP_JOIN)
2908 buffer_add(sql_buf, " OR ");
2910 buffer_add(sql_buf, " AND ");
2913 if ( '+' == search_itr->key[ 0 ] ) {
2915 // This plus sign prefixes a class name or other table alias;
2916 // make sure the table alias is in scope
2917 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2918 if( ! alias_info ) {
2921 "%s: Invalid table alias \"%s\" in WHERE clause",
2925 jsonIteratorFree( search_itr );
2926 buffer_free( sql_buf );
2930 if ( node->type == JSON_STRING ) {
2931 // It's the name of a column; make sure it belongs to the class
2932 const char* fieldname = jsonObjectGetString( node );
2933 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2936 "%s: Invalid column name \"%s\" in WHERE clause for table alias \"%s\"",
2941 jsonIteratorFree( search_itr );
2942 buffer_free( sql_buf );
2946 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
2948 // It's something more complicated
2949 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
2951 jsonIteratorFree( search_itr );
2952 buffer_free( sql_buf );
2956 buffer_fadd(sql_buf, "( %s )", subpred);
2959 } else if ( '-' == search_itr->key[ 0 ] ) {
2960 if ( !strcasecmp("-or",search_itr->key) ) {
2961 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
2963 jsonIteratorFree( search_itr );
2964 buffer_free( sql_buf );
2968 buffer_fadd(sql_buf, "( %s )", subpred);
2970 } else if ( !strcasecmp("-and",search_itr->key) ) {
2971 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2973 jsonIteratorFree( search_itr );
2974 buffer_free( sql_buf );
2978 buffer_fadd(sql_buf, "( %s )", subpred);
2980 } else if ( !strcasecmp("-not",search_itr->key) ) {
2981 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2983 jsonIteratorFree( search_itr );
2984 buffer_free( sql_buf );
2988 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2990 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2991 char* subpred = buildQuery( ctx, node, SUBSELECT );
2993 jsonIteratorFree( search_itr );
2994 buffer_free( sql_buf );
2998 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
3000 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
3001 char* subpred = buildQuery( ctx, node, SUBSELECT );
3003 jsonIteratorFree( search_itr );
3004 buffer_free( sql_buf );
3008 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
3010 } else { // Invalid "minus" operator
3013 "%s: Invalid operator \"%s\" in WHERE clause",
3017 jsonIteratorFree( search_itr );
3018 buffer_free( sql_buf );
3024 const char* class = class_info->class_name;
3025 osrfHash* fields = class_info->fields;
3026 osrfHash* field = osrfHashGet( fields, search_itr->key );
3029 const char* table = class_info->source_def;
3032 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3035 table ? table : "?",
3038 jsonIteratorFree(search_itr);
3039 buffer_free(sql_buf);
3043 char* subpred = searchPredicate( class_info, field, node, ctx );
3045 buffer_free(sql_buf);
3046 jsonIteratorFree(search_itr);
3050 buffer_add( sql_buf, subpred );
3054 jsonIteratorFree(search_itr);
3057 // ERROR ... only hash and array allowed at this level
3058 char* predicate_string = jsonObjectToJSON( search_hash );
3061 "%s: Invalid predicate structure: %s",
3065 buffer_free(sql_buf);
3066 free(predicate_string);
3070 return buffer_release(sql_buf);
3073 /* Build a JSON_ARRAY of field names for a given table alias
3075 static jsonObject* defaultSelectList( const char* table_alias ) {
3080 ClassInfo* class_info = search_all_alias( table_alias );
3081 if( ! class_info ) {
3084 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3091 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3092 osrfHash* field_def = NULL;
3093 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3094 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3095 const char* field_name = osrfHashIteratorKey( field_itr );
3096 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3097 jsonObjectPush( array, jsonNewObject( field_name ) );
3100 osrfHashIteratorFree( field_itr );
3105 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3106 // The jsonObject must be a JSON_HASH with an single entry for "union",
3107 // "intersect", or "except". The data associated with this key must be an
3108 // array of hashes, each hash being a query.
3109 // Also allowed but currently ignored: entries for "order_by" and "alias".
3110 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3112 if( ! combo || combo->type != JSON_HASH )
3113 return NULL; // should be impossible; validated by caller
3115 const jsonObject* query_array = NULL; // array of subordinate queries
3116 const char* op = NULL; // name of operator, e.g. UNION
3117 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3118 int op_count = 0; // for detecting conflicting operators
3119 int excepting = 0; // boolean
3120 int all = 0; // boolean
3121 jsonObject* order_obj = NULL;
3123 // Identify the elements in the hash
3124 jsonIterator* query_itr = jsonNewIterator( combo );
3125 jsonObject* curr_obj = NULL;
3126 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3127 if( ! strcmp( "union", query_itr->key ) ) {
3130 query_array = curr_obj;
3131 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3134 query_array = curr_obj;
3135 } else if( ! strcmp( "except", query_itr->key ) ) {
3139 query_array = curr_obj;
3140 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3143 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3146 order_obj = curr_obj;
3147 } else if( ! strcmp( "alias", query_itr->key ) ) {
3148 if( curr_obj->type != JSON_STRING ) {
3149 jsonIteratorFree( query_itr );
3152 alias = jsonObjectGetString( curr_obj );
3153 } else if( ! strcmp( "all", query_itr->key ) ) {
3154 if( obj_is_true( curr_obj ) )
3158 osrfAppSessionStatus(
3160 OSRF_STATUS_INTERNALSERVERERROR,
3161 "osrfMethodException",
3163 "Malformed query; unexpected entry in query object"
3167 "%s: Unexpected entry for \"%s\" in%squery",
3172 jsonIteratorFree( query_itr );
3176 jsonIteratorFree( query_itr );
3178 // More sanity checks
3179 if( ! query_array ) {
3181 osrfAppSessionStatus(
3183 OSRF_STATUS_INTERNALSERVERERROR,
3184 "osrfMethodException",
3186 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3190 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3193 return NULL; // should be impossible...
3194 } else if( op_count > 1 ) {
3196 osrfAppSessionStatus(
3198 OSRF_STATUS_INTERNALSERVERERROR,
3199 "osrfMethodException",
3201 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3205 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3209 } if( query_array->type != JSON_ARRAY ) {
3211 osrfAppSessionStatus(
3213 OSRF_STATUS_INTERNALSERVERERROR,
3214 "osrfMethodException",
3216 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3220 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3223 json_type( query_array->type )
3226 } if( query_array->size < 2 ) {
3228 osrfAppSessionStatus(
3230 OSRF_STATUS_INTERNALSERVERERROR,
3231 "osrfMethodException",
3233 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3237 "%s:%srequires multiple queries as operands",
3242 } else if( excepting && query_array->size > 2 ) {
3244 osrfAppSessionStatus(
3246 OSRF_STATUS_INTERNALSERVERERROR,
3247 "osrfMethodException",
3249 "EXCEPT operator has too many queries as operands"
3253 "%s:EXCEPT operator has too many queries as operands",
3257 } else if( order_obj && ! alias ) {
3259 osrfAppSessionStatus(
3261 OSRF_STATUS_INTERNALSERVERERROR,
3262 "osrfMethodException",
3264 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3268 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3274 // So far so good. Now build the SQL.
3275 growing_buffer* sql = buffer_init( 256 );
3277 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3278 // Add a layer of parentheses
3279 if( flags & SUBCOMBO )
3280 OSRF_BUFFER_ADD( sql, "( " );
3282 // Traverse the query array. Each entry should be a hash.
3283 int first = 1; // boolean
3285 jsonObject* query = NULL;
3286 while((query = jsonObjectGetIndex( query_array, i++ ) )) {
3287 if( query->type != JSON_HASH ) {
3289 osrfAppSessionStatus(
3291 OSRF_STATUS_INTERNALSERVERERROR,
3292 "osrfMethodException",
3294 "Malformed query under UNION, INTERSECT or EXCEPT"
3298 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3301 json_type( query->type )
3310 OSRF_BUFFER_ADD( sql, op );
3312 OSRF_BUFFER_ADD( sql, "ALL " );
3315 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3319 "%s: Error building query under%s",
3327 OSRF_BUFFER_ADD( sql, query_str );
3330 if( flags & SUBCOMBO )
3331 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3333 if ( !(flags & SUBSELECT) )
3334 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3336 return buffer_release( sql );
3339 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3340 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3341 // or "except" to indicate the type of query.
3342 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3346 osrfAppSessionStatus(
3348 OSRF_STATUS_INTERNALSERVERERROR,
3349 "osrfMethodException",
3351 "Malformed query; no query object"
3353 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", MODULENAME );
3355 } else if( query->type != JSON_HASH ) {
3357 osrfAppSessionStatus(
3359 OSRF_STATUS_INTERNALSERVERERROR,
3360 "osrfMethodException",
3362 "Malformed query object"
3366 "%s: Query object is %s instead of JSON_HASH",
3368 json_type( query->type )
3373 // Determine what kind of query it purports to be, and dispatch accordingly.
3374 if( jsonObjectGetKey( query, "union" ) ||
3375 jsonObjectGetKey( query, "intersect" ) ||
3376 jsonObjectGetKey( query, "except" ) ) {
3377 return doCombo( ctx, query, flags );
3379 // It is presumably a SELECT query
3381 // Push a node onto the stack for the current query. Every level of
3382 // subquery gets its own QueryFrame on the Stack.
3385 // Build an SQL SELECT statement
3388 jsonObjectGetKey( query, "select" ),
3389 jsonObjectGetKey( query, "from" ),
3390 jsonObjectGetKey( query, "where" ),
3391 jsonObjectGetKey( query, "having" ),
3392 jsonObjectGetKey( query, "order_by" ),
3393 jsonObjectGetKey( query, "limit" ),
3394 jsonObjectGetKey( query, "offset" ),
3403 /* method context */ osrfMethodContext* ctx,
3405 /* SELECT */ jsonObject* selhash,
3406 /* FROM */ jsonObject* join_hash,
3407 /* WHERE */ jsonObject* search_hash,
3408 /* HAVING */ jsonObject* having_hash,
3409 /* ORDER BY */ jsonObject* order_hash,
3410 /* LIMIT */ jsonObject* limit,
3411 /* OFFSET */ jsonObject* offset,
3412 /* flags */ int flags
3414 const char* locale = osrf_message_get_last_locale();
3416 // general tmp objects
3417 const jsonObject* tmp_const;
3418 jsonObject* selclass = NULL;
3419 jsonObject* snode = NULL;
3420 jsonObject* onode = NULL;
3422 char* string = NULL;
3423 int from_function = 0;
3428 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3430 // punt if there's no FROM clause
3431 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3434 "%s: FROM clause is missing or empty",
3438 osrfAppSessionStatus(
3440 OSRF_STATUS_INTERNALSERVERERROR,
3441 "osrfMethodException",
3443 "FROM clause is missing or empty in JSON query"
3448 // the core search class
3449 const char* core_class = NULL;
3451 // get the core class -- the only key of the top level FROM clause, or a string
3452 if (join_hash->type == JSON_HASH) {
3453 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3454 snode = jsonIteratorNext( tmp_itr );
3456 // Populate the current QueryFrame with information
3457 // about the core class
3458 if( add_query_core( NULL, tmp_itr->key ) ) {
3460 osrfAppSessionStatus(
3462 OSRF_STATUS_INTERNALSERVERERROR,
3463 "osrfMethodException",
3465 "Unable to look up core class"
3469 core_class = curr_query->core.class_name;
3472 jsonObject* extra = jsonIteratorNext( tmp_itr );
3474 jsonIteratorFree( tmp_itr );
3477 // There shouldn't be more than one entry in join_hash
3481 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3485 osrfAppSessionStatus(
3487 OSRF_STATUS_INTERNALSERVERERROR,
3488 "osrfMethodException",
3490 "Malformed FROM clause in JSON query"
3492 return NULL; // Malformed join_hash; extra entry
3494 } else if (join_hash->type == JSON_ARRAY) {
3495 // We're selecting from a function, not from a table
3497 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
3500 } else if (join_hash->type == JSON_STRING) {
3501 // Populate the current QueryFrame with information
3502 // about the core class
3503 core_class = jsonObjectGetString( join_hash );
3505 if( add_query_core( NULL, core_class ) ) {
3507 osrfAppSessionStatus(
3509 OSRF_STATUS_INTERNALSERVERERROR,
3510 "osrfMethodException",
3512 "Unable to look up core class"
3520 "%s: FROM clause is unexpected JSON type: %s",
3522 json_type( join_hash->type )
3525 osrfAppSessionStatus(
3527 OSRF_STATUS_INTERNALSERVERERROR,
3528 "osrfMethodException",
3530 "Ill-formed FROM clause in JSON query"
3535 // Build the join clause, if any, while filling out the list
3536 // of joined classes in the current QueryFrame.
3537 char* join_clause = NULL;
3538 if( join_hash && ! from_function ) {
3540 join_clause = searchJOIN( join_hash, &curr_query->core );
3541 if( ! join_clause ) {
3543 osrfAppSessionStatus(
3545 OSRF_STATUS_INTERNALSERVERERROR,
3546 "osrfMethodException",
3548 "Unable to construct JOIN clause(s)"
3554 // For in case we don't get a select list
3555 jsonObject* defaultselhash = NULL;
3557 // if there is no select list, build a default select list ...
3558 if (!selhash && !from_function) {
3559 jsonObject* default_list = defaultSelectList( core_class );
3560 if( ! default_list ) {
3562 osrfAppSessionStatus(
3564 OSRF_STATUS_INTERNALSERVERERROR,
3565 "osrfMethodException",
3567 "Unable to build default SELECT clause in JSON query"
3569 free( join_clause );
3574 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3575 jsonObjectSetKey( selhash, core_class, default_list );
3578 // The SELECT clause can be encoded only by a hash
3579 if( !from_function && selhash->type != JSON_HASH ) {
3582 "%s: Expected JSON_HASH for SELECT clause; found %s",
3584 json_type( selhash->type )
3588 osrfAppSessionStatus(
3590 OSRF_STATUS_INTERNALSERVERERROR,
3591 "osrfMethodException",
3593 "Malformed SELECT clause in JSON query"
3595 free( join_clause );
3599 // If you see a null or wild card specifier for the core class, or an
3600 // empty array, replace it with a default SELECT list
3601 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3603 int default_needed = 0; // boolean
3604 if( JSON_STRING == tmp_const->type
3605 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3607 else if( JSON_NULL == tmp_const->type )
3610 if( default_needed ) {
3611 // Build a default SELECT list
3612 jsonObject* default_list = defaultSelectList( core_class );
3613 if( ! default_list ) {
3615 osrfAppSessionStatus(
3617 OSRF_STATUS_INTERNALSERVERERROR,
3618 "osrfMethodException",
3620 "Can't build default SELECT clause in JSON query"
3622 free( join_clause );
3627 jsonObjectSetKey( selhash, core_class, default_list );
3631 // temp buffers for the SELECT list and GROUP BY clause
3632 growing_buffer* select_buf = buffer_init(128);
3633 growing_buffer* group_buf = buffer_init(128);
3635 int aggregate_found = 0; // boolean
3637 // Build a select list
3638 if(from_function) // From a function we select everything
3639 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3642 // Build the SELECT list as SQL
3646 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3647 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3649 const char* cname = selclass_itr->key;
3651 // Make sure the target relation is in the FROM clause.
3653 // At this point join_hash is a step down from the join_hash we
3654 // received as a parameter. If the original was a JSON_STRING,
3655 // then json_hash is now NULL. If the original was a JSON_HASH,
3656 // then json_hash is now the first (and only) entry in it,
3657 // denoting the core class. We've already excluded the
3658 // possibility that the original was a JSON_ARRAY, because in
3659 // that case from_function would be non-NULL, and we wouldn't
3662 // If the current table alias isn't in scope, bail out
3663 ClassInfo* class_info = search_alias( cname );
3664 if( ! class_info ) {
3667 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3672 osrfAppSessionStatus(
3674 OSRF_STATUS_INTERNALSERVERERROR,
3675 "osrfMethodException",
3677 "Selected class not in FROM clause in JSON query"
3679 jsonIteratorFree( selclass_itr );
3680 buffer_free( select_buf );
3681 buffer_free( group_buf );
3682 if( defaultselhash ) jsonObjectFree( defaultselhash );
3683 free( join_clause );
3687 if( selclass->type != JSON_ARRAY ) {
3690 "%s: Malformed SELECT list for class \"%s\"; not an array",
3695 osrfAppSessionStatus(
3697 OSRF_STATUS_INTERNALSERVERERROR,
3698 "osrfMethodException",
3700 "Selected class not in FROM clause in JSON query"
3703 jsonIteratorFree( selclass_itr );
3704 buffer_free( select_buf );
3705 buffer_free( group_buf );
3706 if( defaultselhash ) jsonObjectFree( defaultselhash );
3707 free( join_clause );
3711 // Look up some attributes of the current class
3712 osrfHash* idlClass = class_info->class_def;
3713 osrfHash* class_field_set = class_info->fields;
3714 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3715 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3717 if( 0 == selclass->size ) {
3720 "%s: No columns selected from \"%s\"",
3726 // stitch together the column list for the current table alias...
3727 unsigned long field_idx = 0;
3728 jsonObject* selfield = NULL;
3729 while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3731 // If we need a separator comma, add one
3735 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3738 // if the field specification is a string, add it to the list
3739 if (selfield->type == JSON_STRING) {
3741 // Look up the field in the IDL
3742 const char* col_name = jsonObjectGetString( selfield );
3743 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3745 // No such field in current class
3748 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3754 osrfAppSessionStatus(
3756 OSRF_STATUS_INTERNALSERVERERROR,
3757 "osrfMethodException",
3759 "Selected column not defined in JSON query"
3761 jsonIteratorFree( selclass_itr );
3762 buffer_free( select_buf );
3763 buffer_free( group_buf );
3764 if( defaultselhash ) jsonObjectFree( defaultselhash );
3765 free( join_clause );
3767 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3768 // Virtual field not allowed
3771 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3777 osrfAppSessionStatus(
3779 OSRF_STATUS_INTERNALSERVERERROR,
3780 "osrfMethodException",
3782 "Selected column may not be virtual in JSON query"
3784 jsonIteratorFree( selclass_itr );
3785 buffer_free( select_buf );
3786 buffer_free( group_buf );
3787 if( defaultselhash ) jsonObjectFree( defaultselhash );
3788 free( join_clause );
3794 if (flags & DISABLE_I18N)
3797 i18n = osrfHashGet(field_def, "i18n");
3799 if( str_is_true( i18n ) ) {
3800 buffer_fadd( select_buf,
3801 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3802 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3804 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3807 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3810 // ... but it could be an object, in which case we check for a Field Transform
3811 } else if (selfield->type == JSON_HASH) {
3813 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3815 // Get the field definition from the IDL
3816 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3818 // No such field in current class
3821 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3827 osrfAppSessionStatus(
3829 OSRF_STATUS_INTERNALSERVERERROR,
3830 "osrfMethodException",
3832 "Selected column is not defined in JSON query"
3834 jsonIteratorFree( selclass_itr );
3835 buffer_free( select_buf );
3836 buffer_free( group_buf );
3837 if( defaultselhash ) jsonObjectFree( defaultselhash );
3838 free( join_clause );
3840 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3841 // No such field in current class
3844 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3850 osrfAppSessionStatus(
3852 OSRF_STATUS_INTERNALSERVERERROR,
3853 "osrfMethodException",
3855 "Selected column is virtual in JSON query"
3857 jsonIteratorFree( selclass_itr );
3858 buffer_free( select_buf );
3859 buffer_free( group_buf );
3860 if( defaultselhash ) jsonObjectFree( defaultselhash );
3861 free( join_clause );
3865 // Decide what to use as a column alias
3867 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3868 _alias = jsonObjectGetString( tmp_const );
3869 } else { // Use field name as the alias
3873 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3874 char* transform_str = searchFieldTransform(class_info->alias, field_def, selfield);
3875 if( transform_str ) {
3876 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3877 free(transform_str);
3880 osrfAppSessionStatus(
3882 OSRF_STATUS_INTERNALSERVERERROR,
3883 "osrfMethodException",
3885 "Unable to generate transform function in JSON query"
3887 jsonIteratorFree( selclass_itr );
3888 buffer_free( select_buf );
3889 buffer_free( group_buf );
3890 if( defaultselhash ) jsonObjectFree( defaultselhash );
3891 free( join_clause );
3898 if (flags & DISABLE_I18N)
3901 i18n = osrfHashGet(field_def, "i18n");
3903 if( str_is_true( i18n ) ) {
3904 buffer_fadd( select_buf,
3905 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3906 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3908 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3911 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3918 "%s: Selected item is unexpected JSON type: %s",
3920 json_type( selfield->type )
3923 osrfAppSessionStatus(
3925 OSRF_STATUS_INTERNALSERVERERROR,
3926 "osrfMethodException",
3928 "Ill-formed SELECT item in JSON query"
3930 jsonIteratorFree( selclass_itr );
3931 buffer_free( select_buf );
3932 buffer_free( group_buf );
3933 if( defaultselhash ) jsonObjectFree( defaultselhash );
3934 free( join_clause );
3938 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3939 if( obj_is_true( agg_obj ) )
3940 aggregate_found = 1;
3942 // Append a comma (except for the first one)
3943 // and add the column to a GROUP BY clause
3947 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3949 buffer_fadd(group_buf, " %d", sel_pos);
3953 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3955 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3956 if ( ! obj_is_true( aggregate_obj ) ) {
3960 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3963 buffer_fadd(group_buf, " %d", sel_pos);
3966 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3970 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3973 _column = searchFieldTransform(class_info->alias, field, selfield);
3974 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3975 OSRF_BUFFER_ADD(group_buf, _column);
3976 _column = searchFieldTransform(class_info->alias, field, selfield);
3983 } // end while -- iterating across SELECT columns
3985 } // end while -- iterating across classes
3987 jsonIteratorFree(selclass_itr);
3991 char* col_list = buffer_release(select_buf);
3993 // Make sure the SELECT list isn't empty. This can happen, for example,
3994 // if we try to build a default SELECT clause from a non-core table.
3997 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
3999 osrfAppSessionStatus(
4001 OSRF_STATUS_INTERNALSERVERERROR,
4002 "osrfMethodException",
4004 "SELECT list is empty"
4007 buffer_free( group_buf );
4008 if( defaultselhash ) jsonObjectFree( defaultselhash );
4009 free( join_clause );
4014 if (from_function) table = searchValueTransform(join_hash);
4015 else table = strdup( curr_query->core.source_def );
4019 osrfAppSessionStatus(
4021 OSRF_STATUS_INTERNALSERVERERROR,
4022 "osrfMethodException",
4024 "Unable to identify table for core class"
4027 buffer_free( group_buf );
4028 if( defaultselhash ) jsonObjectFree( defaultselhash );
4029 free( join_clause );
4033 // Put it all together
4034 growing_buffer* sql_buf = buffer_init(128);
4035 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4039 // Append the join clause, if any
4041 buffer_add(sql_buf, join_clause);
4045 char* order_by_list = NULL;
4046 char* having_buf = NULL;
4048 if (!from_function) {
4050 // Build a WHERE clause, if there is one
4051 if ( search_hash ) {
4052 buffer_add(sql_buf, " WHERE ");
4054 // and it's on the WHERE clause
4055 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4058 osrfAppSessionStatus(
4060 OSRF_STATUS_INTERNALSERVERERROR,
4061 "osrfMethodException",
4063 "Severe query error in WHERE predicate -- see error log for more details"
4066 buffer_free(group_buf);
4067 buffer_free(sql_buf);
4068 if (defaultselhash) jsonObjectFree(defaultselhash);
4072 buffer_add(sql_buf, pred);
4076 // Build a HAVING clause, if there is one
4077 if ( having_hash ) {
4079 // and it's on the the WHERE clause
4080 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4082 if( ! having_buf ) {
4084 osrfAppSessionStatus(
4086 OSRF_STATUS_INTERNALSERVERERROR,
4087 "osrfMethodException",
4089 "Severe query error in HAVING predicate -- see error log for more details"
4092 buffer_free(group_buf);
4093 buffer_free(sql_buf);
4094 if (defaultselhash) jsonObjectFree(defaultselhash);
4099 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4101 // Build an ORDER BY clause, if there is one
4102 if( NULL == order_hash )
4103 ; // No ORDER BY? do nothing
4104 else if( JSON_ARRAY == order_hash->type ) {
4105 // Array of field specifications, each specification being a
4106 // hash to define the class, field, and other details
4108 jsonObject* order_spec;
4109 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4111 if( JSON_HASH != order_spec->type ) {
4112 osrfLogError(OSRF_LOG_MARK,
4113 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4114 MODULENAME, json_type( order_spec->type ) );
4116 osrfAppSessionStatus(
4118 OSRF_STATUS_INTERNALSERVERERROR,
4119 "osrfMethodException",
4121 "Malformed ORDER BY clause -- see error log for more details"
4123 buffer_free( order_buf );
4125 buffer_free(group_buf);
4126 buffer_free(sql_buf);
4127 if (defaultselhash) jsonObjectFree(defaultselhash);
4131 const char* class_alias =
4132 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4134 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4137 OSRF_BUFFER_ADD(order_buf, ", ");
4139 order_buf = buffer_init(128);
4141 if( !field || !class_alias ) {
4142 osrfLogError(OSRF_LOG_MARK,
4143 "%s: Missing class or field name in field specification of ORDER BY clause",
4146 osrfAppSessionStatus(
4148 OSRF_STATUS_INTERNALSERVERERROR,
4149 "osrfMethodException",
4151 "Malformed ORDER BY clause -- see error log for more details"
4153 buffer_free( order_buf );
4155 buffer_free(group_buf);
4156 buffer_free(sql_buf);
4157 if (defaultselhash) jsonObjectFree(defaultselhash);
4161 ClassInfo* order_class_info = search_alias( class_alias );
4162 if( ! order_class_info ) {
4163 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4164 "not in FROM clause", MODULENAME, class_alias );
4166 osrfAppSessionStatus(
4168 OSRF_STATUS_INTERNALSERVERERROR,
4169 "osrfMethodException",
4171 "Invalid class referenced in ORDER BY clause -- see error log for more details"
4174 buffer_free(group_buf);
4175 buffer_free(sql_buf);
4176 if (defaultselhash) jsonObjectFree(defaultselhash);
4180 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4182 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4183 MODULENAME, class_alias, field );
4185 osrfAppSessionStatus(
4187 OSRF_STATUS_INTERNALSERVERERROR,
4188 "osrfMethodException",
4190 "Invalid field referenced in ORDER BY clause -- see error log for more details"
4193 buffer_free(group_buf);
4194 buffer_free(sql_buf);
4195 if (defaultselhash) jsonObjectFree(defaultselhash);
4197 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4198 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4199 MODULENAME, field );
4201 osrfAppSessionStatus(
4203 OSRF_STATUS_INTERNALSERVERERROR,
4204 "osrfMethodException",
4206 "Virtual field in ORDER BY clause -- see error log for more details"
4208 buffer_free( order_buf );
4210 buffer_free(group_buf);
4211 buffer_free(sql_buf);
4212 if (defaultselhash) jsonObjectFree(defaultselhash);
4216 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4217 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
4218 if( ! transform_str ) {
4220 osrfAppSessionStatus(
4222 OSRF_STATUS_INTERNALSERVERERROR,
4223 "osrfMethodException",
4225 "Severe query error in ORDER BY clause -- see error log for more details"
4227 buffer_free( order_buf );
4229 buffer_free(group_buf);
4230 buffer_free(sql_buf);
4231 if (defaultselhash) jsonObjectFree(defaultselhash);
4235 OSRF_BUFFER_ADD( order_buf, transform_str );
4236 free( transform_str );
4239 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4241 const char* direction =
4242 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4244 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4245 OSRF_BUFFER_ADD( order_buf, " DESC" );
4247 OSRF_BUFFER_ADD( order_buf, " ASC" );
4250 } else if( JSON_HASH == order_hash->type ) {
4251 // This hash is keyed on class alias. Each class has either
4252 // an array of field names or a hash keyed on field name.
4253 jsonIterator* class_itr = jsonNewIterator( order_hash );
4254 while ( (snode = jsonIteratorNext( class_itr )) ) {
4256 ClassInfo* order_class_info = search_alias( class_itr->key );
4257 if( ! order_class_info ) {
4258 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4259 MODULENAME, class_itr->key );
4261 osrfAppSessionStatus(
4263 OSRF_STATUS_INTERNALSERVERERROR,
4264 "osrfMethodException",
4266 "Invalid class referenced in ORDER BY clause -- see error log for more details"
4268 jsonIteratorFree( class_itr );
4269 buffer_free( order_buf );
4271 buffer_free(group_buf);
4272 buffer_free(sql_buf);
4273 if (defaultselhash) jsonObjectFree(defaultselhash);
4277 osrfHash* field_list_def = order_class_info->fields;
4279 if ( snode->type == JSON_HASH ) {
4281 // Hash is keyed on field names from the current class. For each field
4282 // there is another layer of hash to define the sorting details, if any,
4283 // or a string to indicate direction of sorting.
4284 jsonIterator* order_itr = jsonNewIterator( snode );
4285 while ( (onode = jsonIteratorNext( order_itr )) ) {
4287 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4289 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4290 MODULENAME, order_itr->key );
4292 osrfAppSessionStatus(
4294 OSRF_STATUS_INTERNALSERVERERROR,
4295 "osrfMethodException",
4297 "Invalid field in ORDER BY clause -- see error log for more details"
4299 jsonIteratorFree( order_itr );
4300 jsonIteratorFree( class_itr );
4301 buffer_free( order_buf );
4303 buffer_free(group_buf);
4304 buffer_free(sql_buf);
4305 if (defaultselhash) jsonObjectFree(defaultselhash);
4307 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4308 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4309 MODULENAME, order_itr->key );
4311 osrfAppSessionStatus(
4313 OSRF_STATUS_INTERNALSERVERERROR,
4314 "osrfMethodException",
4316 "Virtual field in ORDER BY clause -- see error log for more details"
4318 jsonIteratorFree( order_itr );
4319 jsonIteratorFree( class_itr );
4320 buffer_free( order_buf );
4322 buffer_free(group_buf);
4323 buffer_free(sql_buf);
4324 if (defaultselhash) jsonObjectFree(defaultselhash);
4328 const char* direction = NULL;
4329 if ( onode->type == JSON_HASH ) {
4330 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4331 string = searchFieldTransform(
4333 osrfHashGet( field_list_def, order_itr->key ),
4337 if( ctx ) osrfAppSessionStatus(
4339 OSRF_STATUS_INTERNALSERVERERROR,
4340 "osrfMethodException",
4342 "Severe query error in ORDER BY clause -- see error log for more details"
4344 jsonIteratorFree( order_itr );
4345 jsonIteratorFree( class_itr );
4347 buffer_free(group_buf);
4348 buffer_free(order_buf);
4349 buffer_free(sql_buf);
4350 if (defaultselhash) jsonObjectFree(defaultselhash);
4354 growing_buffer* field_buf = buffer_init(16);
4355 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4356 string = buffer_release(field_buf);
4359 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4360 const char* dir = jsonObjectGetString(tmp_const);
4361 if (!strncasecmp(dir, "d", 1)) {
4362 direction = " DESC";
4368 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4369 osrfLogError( OSRF_LOG_MARK,
4370 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4371 MODULENAME, json_type( onode->type ) );
4373 osrfAppSessionStatus(
4375 OSRF_STATUS_INTERNALSERVERERROR,
4376 "osrfMethodException",
4378 "Malformed ORDER BY clause -- see error log for more details"
4380 jsonIteratorFree( order_itr );
4381 jsonIteratorFree( class_itr );
4383 buffer_free(group_buf);
4384 buffer_free(order_buf);
4385 buffer_free(sql_buf);
4386 if (defaultselhash) jsonObjectFree(defaultselhash);
4390 string = strdup(order_itr->key);
4391 const char* dir = jsonObjectGetString(onode);
4392 if (!strncasecmp(dir, "d", 1)) {
4393 direction = " DESC";
4400 OSRF_BUFFER_ADD(order_buf, ", ");
4402 order_buf = buffer_init(128);
4404 OSRF_BUFFER_ADD(order_buf, string);
4408 OSRF_BUFFER_ADD(order_buf, direction);
4412 jsonIteratorFree(order_itr);
4414 } else if ( snode->type == JSON_ARRAY ) {
4416 // Array is a list of fields from the current class
4417 unsigned long order_idx = 0;
4418 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4420 const char* _f = jsonObjectGetString( onode );
4422 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4424 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4427 osrfAppSessionStatus(
4429 OSRF_STATUS_INTERNALSERVERERROR,
4430 "osrfMethodException",
4432 "Invalid field in ORDER BY clause -- see error log for more details"
4434 jsonIteratorFree( class_itr );
4435 buffer_free( order_buf );
4437 buffer_free(group_buf);
4438 buffer_free(sql_buf);
4439 if (defaultselhash) jsonObjectFree(defaultselhash);
4441 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4442 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4445 osrfAppSessionStatus(
4447 OSRF_STATUS_INTERNALSERVERERROR,
4448 "osrfMethodException",
4450 "Virtual field in ORDER BY clause -- see error log for more details"
4452 jsonIteratorFree( class_itr );
4453 buffer_free( order_buf );
4455 buffer_free(group_buf);
4456 buffer_free(sql_buf);
4457 if (defaultselhash) jsonObjectFree(defaultselhash);
4462 OSRF_BUFFER_ADD(order_buf, ", ");
4464 order_buf = buffer_init(128);
4466 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
4470 // IT'S THE OOOOOOOOOOOLD STYLE!
4472 osrfLogError(OSRF_LOG_MARK,
4473 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
4475 osrfAppSessionStatus(
4477 OSRF_STATUS_INTERNALSERVERERROR,
4478 "osrfMethodException",
4480 "Severe query error -- see error log for more details"
4485 buffer_free(group_buf);
4486 buffer_free(order_buf);
4487 buffer_free(sql_buf);
4488 if (defaultselhash) jsonObjectFree(defaultselhash);
4489 jsonIteratorFree(class_itr);
4493 jsonIteratorFree( class_itr );
4495 osrfLogError(OSRF_LOG_MARK,
4496 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4497 MODULENAME, json_type( order_hash->type ) );
4499 osrfAppSessionStatus(
4501 OSRF_STATUS_INTERNALSERVERERROR,
4502 "osrfMethodException",
4504 "Malformed ORDER BY clause -- see error log for more details"
4506 buffer_free( order_buf );
4508 buffer_free(group_buf);
4509 buffer_free(sql_buf);
4510 if (defaultselhash) jsonObjectFree(defaultselhash);
4515 order_by_list = buffer_release( order_buf );
4519 string = buffer_release(group_buf);
4521 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4522 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4523 OSRF_BUFFER_ADD( sql_buf, string );
4528 if( having_buf && *having_buf ) {
4529 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4530 OSRF_BUFFER_ADD( sql_buf, having_buf );
4534 if( order_by_list ) {
4536 if ( *order_by_list ) {
4537 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4538 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4541 free( order_by_list );
4545 const char* str = jsonObjectGetString(limit);
4546 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
4550 const char* str = jsonObjectGetString(offset);
4551 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
4554 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4556 if (defaultselhash) jsonObjectFree(defaultselhash);
4558 return buffer_release(sql_buf);
4560 } // end of SELECT()
4562 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4564 const char* locale = osrf_message_get_last_locale();
4566 osrfHash* fields = osrfHashGet(meta, "fields");
4567 char* core_class = osrfHashGet(meta, "classname");
4569 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4571 jsonObject* node = NULL;
4572 jsonObject* snode = NULL;
4573 jsonObject* onode = NULL;
4574 const jsonObject* _tmp = NULL;
4575 jsonObject* selhash = NULL;
4576 jsonObject* defaultselhash = NULL;
4578 growing_buffer* sql_buf = buffer_init(128);
4579 growing_buffer* select_buf = buffer_init(128);
4581 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4582 defaultselhash = jsonNewObjectType(JSON_HASH);
4583 selhash = defaultselhash;
4586 // If there's no SELECT list for the core class, build one
4587 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4588 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4590 // Add every non-virtual field to the field list
4591 osrfHash* field_def = NULL;
4592 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4593 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4594 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4595 const char* field = osrfHashIteratorKey( field_itr );
4596 jsonObjectPush( field_list, jsonNewObject( field ) );
4599 osrfHashIteratorFree( field_itr );
4600 jsonObjectSetKey( selhash, core_class, field_list );
4604 jsonIterator* class_itr = jsonNewIterator( selhash );
4605 while ( (snode = jsonIteratorNext( class_itr )) ) {
4607 const char* cname = class_itr->key;
4608 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4609 if (!idlClass) continue;
4611 if (strcmp(core_class,class_itr->key)) {
4612 if (!join_hash) continue;
4614 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4616 jsonObjectFree(found);
4620 jsonObjectFree(found);
4623 jsonIterator* select_itr = jsonNewIterator( snode );
4624 while ( (node = jsonIteratorNext( select_itr )) ) {
4625 const char* item_str = jsonObjectGetString( node );
4626 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4627 char* fname = osrfHashGet(field, "name");
4629 if (!field) continue;
4634 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
4639 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4640 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4643 i18n = osrfHashGet(field, "i18n");
4645 if( str_is_true( i18n ) ) {
4646 char* pkey = osrfHashGet(idlClass, "primarykey");
4647 char* tname = osrfHashGet(idlClass, "tablename");
4649 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);
4651 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4654 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4658 jsonIteratorFree(select_itr);
4661 jsonIteratorFree(class_itr);
4663 char* col_list = buffer_release(select_buf);
4664 char* table = getRelation(meta);
4666 table = strdup( "(null)" );
4668 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4672 // Clear the query stack (as a fail-safe precaution against possible
4673 // leftover garbage); then push the first query frame onto the stack.
4674 clear_query_stack();
4676 if( add_query_core( NULL, core_class ) ) {
4678 osrfAppSessionStatus(
4680 OSRF_STATUS_INTERNALSERVERERROR,
4681 "osrfMethodException",
4683 "Unable to build query frame for core class"
4689 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4690 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
4691 OSRF_BUFFER_ADD(sql_buf, join_clause);
4695 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4696 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4698 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4700 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4702 osrfAppSessionStatus(
4704 OSRF_STATUS_INTERNALSERVERERROR,
4705 "osrfMethodException",
4707 "Severe query error -- see error log for more details"
4709 buffer_free(sql_buf);
4710 if(defaultselhash) jsonObjectFree(defaultselhash);
4711 clear_query_stack();
4714 buffer_add(sql_buf, pred);
4719 char* string = NULL;
4720 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4722 growing_buffer* order_buf = buffer_init(128);
4725 jsonIterator* class_itr = jsonNewIterator( _tmp );
4726 while ( (snode = jsonIteratorNext( class_itr )) ) {
4728 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4731 if ( snode->type == JSON_HASH ) {
4733 jsonIterator* order_itr = jsonNewIterator( snode );
4734 while ( (onode = jsonIteratorNext( order_itr )) ) {
4736 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4737 class_itr->key, order_itr->key );
4741 char* direction = NULL;
4742 if ( onode->type == JSON_HASH ) {
4743 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4744 string = searchFieldTransform( class_itr->key, field_def, onode );
4746 osrfAppSessionStatus(
4748 OSRF_STATUS_INTERNALSERVERERROR,
4749 "osrfMethodException",
4751 "Severe query error in ORDER BY clause -- see error log for more details"
4753 jsonIteratorFree( order_itr );
4754 jsonIteratorFree( class_itr );
4755 buffer_free( order_buf );
4756 buffer_free( sql_buf );
4757 if( defaultselhash ) jsonObjectFree( defaultselhash );
4758 clear_query_stack();
4762 growing_buffer* field_buf = buffer_init(16);
4763 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4764 string = buffer_release(field_buf);
4767 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4768 const char* dir = jsonObjectGetString(_tmp);
4769 if (!strncasecmp(dir, "d", 1)) {
4770 direction = " DESC";
4777 string = strdup(order_itr->key);
4778 const char* dir = jsonObjectGetString(onode);
4779 if (!strncasecmp(dir, "d", 1)) {
4780 direction = " DESC";
4789 buffer_add(order_buf, ", ");
4792 buffer_add(order_buf, string);
4796 buffer_add(order_buf, direction);
4801 jsonIteratorFree(order_itr);
4804 const char* str = jsonObjectGetString(snode);
4805 buffer_add(order_buf, str);
4811 jsonIteratorFree(class_itr);
4813 string = buffer_release(order_buf);
4816 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4817 OSRF_BUFFER_ADD( sql_buf, string );
4823 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4824 const char* str = jsonObjectGetString(_tmp);
4832 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4834 const char* str = jsonObjectGetString(_tmp);
4843 if (defaultselhash) jsonObjectFree(defaultselhash);
4844 clear_query_stack();
4846 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4847 return buffer_release(sql_buf);
4850 int doJSONSearch ( osrfMethodContext* ctx ) {
4851 if(osrfMethodVerifyContext( ctx )) {
4852 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4856 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
4861 dbhandle = writehandle;
4863 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4867 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4868 flags |= SELECT_DISTINCT;
4870 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4871 flags |= DISABLE_I18N;
4873 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4874 clear_query_stack(); // a possibly needless precaution
4875 char* sql = buildQuery( ctx, hash, flags );
4876 clear_query_stack();
4883 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4884 dbi_result result = dbi_conn_query(dbhandle, sql);
4887 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4889 if (dbi_result_first_row(result)) {
4890 /* JSONify the result */
4891 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4894 jsonObject* return_val = oilsMakeJSONFromResult( result );
4895 osrfAppRespond( ctx, return_val );
4896 jsonObjectFree( return_val );
4897 } while (dbi_result_next_row(result));
4900 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4903 osrfAppRespondComplete( ctx, NULL );
4905 /* clean up the query */
4906 dbi_result_free(result);
4910 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4911 osrfAppSessionStatus(
4913 OSRF_STATUS_INTERNALSERVERERROR,
4914 "osrfMethodException",
4916 "Severe query error -- see error log for more details"
4924 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4925 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4928 dbhandle = writehandle;
4930 osrfHash* links = osrfHashGet(meta, "links");
4931 osrfHash* fields = osrfHashGet(meta, "fields");
4932 char* core_class = osrfHashGet(meta, "classname");
4933 char* pkey = osrfHashGet(meta, "primarykey");
4935 const jsonObject* _tmp;
4938 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4940 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4945 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4947 dbi_result result = dbi_conn_query(dbhandle, sql);
4948 if( NULL == result ) {
4949 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4950 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4951 osrfAppSessionStatus(
4953 OSRF_STATUS_INTERNALSERVERERROR,
4954 "osrfMethodException",
4956 "Severe query error -- see error log for more details"
4963 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4966 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4967 osrfHash* dedup = osrfNewHash();
4969 if (dbi_result_first_row(result)) {
4970 /* JSONify the result */
4971 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4973 obj = oilsMakeFieldmapperFromResult( result, meta );
4974 char* pkey_val = oilsFMGetString( obj, pkey );
4975 if ( osrfHashGet( dedup, pkey_val ) ) {
4976 jsonObjectFree(obj);
4979 osrfHashSet( dedup, pkey_val, pkey_val );
4980 jsonObjectPush(res_list, obj);
4982 } while (dbi_result_next_row(result));
4984 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4988 osrfHashFree(dedup);
4989 /* clean up the query */
4990 dbi_result_free(result);
4993 if (res_list->size && query_hash) {
4994 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4996 int x = (int)jsonObjectGetNumber(_tmp);
4997 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4999 const jsonObject* temp_blob;
5000 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
5002 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5003 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5005 osrfStringArray* link_fields = NULL;
5008 if (flesh_fields->size == 1) {
5009 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
5010 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
5015 link_fields = osrfNewStringArray(1);
5016 jsonIterator* _i = jsonNewIterator( flesh_fields );
5017 while ((_f = jsonIteratorNext( _i ))) {
5018 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5020 jsonIteratorFree(_i);
5025 unsigned long res_idx = 0;
5026 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5029 const char* link_field;
5031 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5033 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
5035 osrfHash* kid_link = osrfHashGet(links, link_field);
5036 if (!kid_link) continue;
5038 osrfHash* field = osrfHashGet(fields, link_field);
5039 if (!field) continue;
5041 osrfHash* value_field = field;
5043 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
5044 if (!kid_idl) continue;
5046 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
5047 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
5050 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
5051 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
5054 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5056 if (link_map->size > 0) {
5057 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
5060 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5065 osrfHashGet(kid_link, "class"),
5072 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5073 osrfHashGet(kid_link, "field"),
5074 osrfHashGet(kid_link, "class"),
5075 osrfHashGet(kid_link, "key"),
5076 osrfHashGet(kid_link, "reltype")
5079 const char* search_key = jsonObjectGetString(
5082 atoi( osrfHashGet(value_field, "array_position") )
5087 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
5091 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
5093 // construct WHERE clause
5094 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
5097 osrfHashGet(kid_link, "key"),
5098 jsonNewObject( search_key )
5101 // construct the rest of the query
5102 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
5103 jsonObjectSetKey( rest_of_query, "flesh",
5104 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
5108 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
5110 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
5111 jsonObjectSetKey( rest_of_query, "order_by",
5112 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
5116 if (jsonObjectGetKeyConst(query_hash, "select")) {
5117 jsonObjectSetKey( rest_of_query, "select",
5118 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
5122 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5123 where_clause, rest_of_query, err);
5125 jsonObjectFree( where_clause );
5126 jsonObjectFree( rest_of_query );
5129 osrfStringArrayFree(link_fields);
5130 jsonObjectFree(res_list);
5131 jsonObjectFree(flesh_blob);
5135 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
5137 jsonObject* X = NULL;
5138 if ( link_map->size > 0 && kids->size > 0 ) {
5140 kids = jsonNewObjectType(JSON_ARRAY);
5142 jsonObject* _k_node;
5143 unsigned long res_idx = 0;
5144 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5150 (unsigned long)atoi(
5156 osrfHashGet(kid_link, "class")
5160 osrfStringArrayGetString( link_map, 0 )
5168 } // end while loop traversing X
5171 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
5172 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
5175 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5176 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
5180 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
5181 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
5184 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5185 jsonObjectClone( kids )
5190 jsonObjectFree(kids);
5194 jsonObjectFree( kids );
5196 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
5197 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
5200 } // end while loop traversing res_list
5201 jsonObjectFree( flesh_blob );
5202 osrfStringArrayFree(link_fields);
5211 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
5213 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5215 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
5217 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
5220 if (!verifyObjectClass(ctx, target)) {
5225 if( getXactId( ctx ) == NULL ) {
5226 osrfAppSessionStatus(
5228 OSRF_STATUS_BADREQUEST,
5229 "osrfMethodException",
5231 "No active transaction -- required for UPDATE"
5237 // The following test is harmless but redundant. If a class is
5238 // readonly, we don't register an update method for it.
5239 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5240 osrfAppSessionStatus(
5242 OSRF_STATUS_BADREQUEST,
5243 "osrfMethodException",
5245 "Cannot UPDATE readonly class"
5251 dbhandle = writehandle;
5252 const char* trans_id = getXactId( ctx );
5254 // Set the last_xact_id
5255 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5257 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5258 trans_id, target->classname, index);
5259 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
5262 char* pkey = osrfHashGet(meta, "primarykey");
5263 osrfHash* fields = osrfHashGet(meta, "fields");
5265 char* id = oilsFMGetString( target, pkey );
5269 "%s updating %s object with %s = %s",
5271 osrfHashGet(meta, "fieldmapper"),
5276 growing_buffer* sql = buffer_init(128);
5277 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
5280 osrfHash* field_def = NULL;
5281 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5282 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
5284 // Skip virtual fields, and the primary key
5285 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5288 const char* field_name = osrfHashIteratorKey( field_itr );
5289 if( ! strcmp( field_name, pkey ) )
5292 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5294 int value_is_numeric = 0; // boolean
5296 if (field_object && field_object->classname) {
5297 value = oilsFMGetString(
5299 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
5301 } else if( field_object && JSON_BOOL == field_object->type ) {
5302 if( jsonBoolIsTrue( field_object ) )
5303 value = strdup( "t" );
5305 value = strdup( "f" );
5307 value = jsonObjectToSimpleString( field_object );
5308 if( field_object && JSON_NUMBER == field_object->type )
5309 value_is_numeric = 1;
5312 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5313 osrfHashGet(meta, "fieldmapper"), field_name, value);
5315 if (!field_object || field_object->type == JSON_NULL) {
5316 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
5317 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5318 if (first) first = 0;
5319 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5320 buffer_fadd( sql, " %s = NULL", field_name );
5323 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5324 if (first) first = 0;
5325 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5327 const char* numtype = get_datatype( field_def );
5328 if ( !strncmp( numtype, "INT", 3 ) ) {
5329 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
5330 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
5331 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
5333 // Must really be intended as a string, so quote it
5334 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5335 buffer_fadd( sql, " %s = %s", field_name, value );
5337 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5338 osrfAppSessionStatus(
5340 OSRF_STATUS_INTERNALSERVERERROR,
5341 "osrfMethodException",
5343 "Error quoting string -- please see the error log for more details"
5347 osrfHashIteratorFree( field_itr );
5354 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5357 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5358 if (first) first = 0;
5359 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5360 buffer_fadd( sql, " %s = %s", field_name, value );
5363 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5364 osrfAppSessionStatus(
5366 OSRF_STATUS_INTERNALSERVERERROR,
5367 "osrfMethodException",
5369 "Error quoting string -- please see the error log for more details"
5373 osrfHashIteratorFree( field_itr );
5384 osrfHashIteratorFree( field_itr );
5386 jsonObject* obj = jsonNewObject(id);
5388 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5389 dbi_conn_quote_string(dbhandle, &id);
5391 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5393 char* query = buffer_release(sql);
5394 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
5396 dbi_result result = dbi_conn_query(dbhandle, query);
5400 jsonObjectFree(obj);
5401 obj = jsonNewObject(NULL);
5404 "%s ERROR updating %s object with %s = %s",
5406 osrfHashGet(meta, "fieldmapper"),
5417 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
5419 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5421 if( getXactId( ctx ) == NULL ) {
5422 osrfAppSessionStatus(
5424 OSRF_STATUS_BADREQUEST,
5425 "osrfMethodException",
5427 "No active transaction -- required for DELETE"
5433 // The following test is harmless but redundant. If a class is
5434 // readonly, we don't register a delete method for it.
5435 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5436 osrfAppSessionStatus(
5438 OSRF_STATUS_BADREQUEST,
5439 "osrfMethodException",
5441 "Cannot DELETE readonly class"
5447 dbhandle = writehandle;
5451 char* pkey = osrfHashGet(meta, "primarykey");
5459 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
5460 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5465 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5468 if (!verifyObjectPCRUD( ctx, NULL )) {
5473 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
5478 "%s deleting %s object with %s = %s",
5480 osrfHashGet(meta, "fieldmapper"),
5485 obj = jsonNewObject(id);
5487 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5488 dbi_conn_quote_string(writehandle, &id);
5490 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
5493 jsonObjectFree(obj);
5494 obj = jsonNewObject(NULL);
5497 "%s ERROR deleting %s object with %s = %s",
5499 osrfHashGet(meta, "fieldmapper"),
5512 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5513 if(!(result && meta)) return jsonNULL;
5515 jsonObject* object = jsonNewObject(NULL);
5516 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
5518 osrfHash* fields = osrfHashGet(meta, "fields");
5520 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
5524 char dt_string[256];
5528 int columnIndex = 1;
5530 unsigned short type;
5531 const char* columnName;
5533 /* cycle through the column list */
5534 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5536 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5538 fmIndex = -1; // reset the position
5540 /* determine the field type and storage attributes */
5541 type = dbi_result_get_field_type_idx(result, columnIndex);
5542 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5544 /* fetch the fieldmapper index */
5545 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
5547 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
5550 const char* pos = (char*)osrfHashGet(_f, "array_position");
5551 if ( !pos ) continue;
5553 fmIndex = atoi( pos );
5554 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
5559 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5560 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
5565 case DBI_TYPE_INTEGER :
5567 if( attr & DBI_INTEGER_SIZE8 )
5568 jsonObjectSetIndex( object, fmIndex,
5569 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
5571 jsonObjectSetIndex( object, fmIndex,
5572 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
5576 case DBI_TYPE_DECIMAL :
5577 jsonObjectSetIndex( object, fmIndex,
5578 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
5581 case DBI_TYPE_STRING :
5587 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
5592 case DBI_TYPE_DATETIME :
5594 memset(dt_string, '\0', sizeof(dt_string));
5595 memset(&gmdt, '\0', sizeof(gmdt));
5597 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5600 if (!(attr & DBI_DATETIME_DATE)) {
5601 gmtime_r( &_tmp_dt, &gmdt );
5602 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5603 } else if (!(attr & DBI_DATETIME_TIME)) {
5604 localtime_r( &_tmp_dt, &gmdt );
5605 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5607 localtime_r( &_tmp_dt, &gmdt );
5608 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5611 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
5615 case DBI_TYPE_BINARY :
5616 osrfLogError( OSRF_LOG_MARK,
5617 "Can't do binary at column %s : index %d", columnName, columnIndex);
5626 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5627 if(!result) return jsonNULL;
5629 jsonObject* object = jsonNewObject(NULL);
5632 char dt_string[256];
5636 int columnIndex = 1;
5638 unsigned short type;
5639 const char* columnName;
5641 /* cycle through the column list */
5642 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5644 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5646 fmIndex = -1; // reset the position
5648 /* determine the field type and storage attributes */
5649 type = dbi_result_get_field_type_idx(result, columnIndex);
5650 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5652 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5653 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
5658 case DBI_TYPE_INTEGER :
5660 if( attr & DBI_INTEGER_SIZE8 )
5661 jsonObjectSetKey( object, columnName,
5662 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
5664 jsonObjectSetKey( object, columnName,
5665 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
5668 case DBI_TYPE_DECIMAL :
5669 jsonObjectSetKey( object, columnName,
5670 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
5673 case DBI_TYPE_STRING :
5674 jsonObjectSetKey( object, columnName,
5675 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
5678 case DBI_TYPE_DATETIME :
5680 memset(dt_string, '\0', sizeof(dt_string));
5681 memset(&gmdt, '\0', sizeof(gmdt));
5683 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5686 if (!(attr & DBI_DATETIME_DATE)) {
5687 gmtime_r( &_tmp_dt, &gmdt );
5688 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5689 } else if (!(attr & DBI_DATETIME_TIME)) {
5690 localtime_r( &_tmp_dt, &gmdt );
5691 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5693 localtime_r( &_tmp_dt, &gmdt );
5694 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5697 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5700 case DBI_TYPE_BINARY :
5701 osrfLogError( OSRF_LOG_MARK,
5702 "Can't do binary at column %s : index %d", columnName, columnIndex );
5706 } // end while loop traversing result
5711 // Interpret a string as true or false
5712 static int str_is_true( const char* str ) {
5713 if( NULL == str || strcasecmp( str, "true" ) )
5719 // Interpret a jsonObject as true or false
5720 static int obj_is_true( const jsonObject* obj ) {
5723 else switch( obj->type )
5731 if( strcasecmp( obj->value.s, "true" ) )
5735 case JSON_NUMBER : // Support 1/0 for perl's sake
5736 if( jsonObjectGetNumber( obj ) == 1.0 )
5745 // Translate a numeric code into a text string identifying a type of
5746 // jsonObject. To be used for building error messages.
5747 static const char* json_type( int code ) {
5753 return "JSON_ARRAY";
5755 return "JSON_STRING";
5757 return "JSON_NUMBER";
5763 return "(unrecognized)";
5767 // Extract the "primitive" attribute from an IDL field definition.
5768 // If we haven't initialized the app, then we must be running in
5769 // some kind of testbed. In that case, default to "string".
5770 static const char* get_primitive( osrfHash* field ) {
5771 const char* s = osrfHashGet( field, "primitive" );
5773 if( child_initialized )
5776 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5778 osrfHashGet( field, "name" )
5786 // Extract the "datatype" attribute from an IDL field definition.
5787 // If we haven't initialized the app, then we must be running in
5788 // some kind of testbed. In that case, default to to NUMERIC,
5789 // since we look at the datatype only for numbers.
5790 static const char* get_datatype( osrfHash* field ) {
5791 const char* s = osrfHashGet( field, "datatype" );
5793 if( child_initialized )
5796 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5798 osrfHashGet( field, "name" )
5807 If the input string is potentially a valid SQL identifier, return 1.
5810 Purpose: to prevent certain kinds of SQL injection. To that end we
5811 don't necessarily need to follow all the rules exactly, such as requiring
5812 that the first character not be a digit.
5814 We allow leading and trailing white space. In between, we do not allow
5815 punctuation (except for underscores and dollar signs), control
5816 characters, or embedded white space.
5818 More pedantically we should allow quoted identifiers containing arbitrary
5819 characters, but for the foreseeable future such quoted identifiers are not
5820 likely to be an issue.
5822 static int is_identifier( const char* s) {
5826 // Skip leading white space
5827 while( isspace( (unsigned char) *s ) )
5831 return 0; // Nothing but white space? Not okay.
5833 // Check each character until we reach white space or
5834 // end-of-string. Letters, digits, underscores, and
5835 // dollar signs are okay. With the exception of periods
5836 // (as in schema.identifier), control characters and other
5837 // punctuation characters are not okay. Anything else
5838 // is okay -- it could for example be part of a multibyte
5839 // UTF8 character such as a letter with diacritical marks,
5840 // and those are allowed.
5842 if( isalnum( (unsigned char) *s )
5846 ; // Fine; keep going
5847 else if( ispunct( (unsigned char) *s )
5848 || iscntrl( (unsigned char) *s ) )
5851 } while( *s && ! isspace( (unsigned char) *s ) );
5853 // If we found any white space in the above loop,
5854 // the rest had better be all white space.
5856 while( isspace( (unsigned char) *s ) )
5860 return 0; // White space was embedded within non-white space
5866 Determine whether to accept a character string as a comparison operator.
5867 Return 1 if it's good, or 0 if it's bad.
5869 We don't validate it for real. We just make sure that it doesn't contain
5870 any semicolons or white space (with special exceptions for a few specific
5871 operators). The idea is to block certain kinds of SQL injection. If it
5872 has no semicolons or white space but it's still not a valid operator, then
5873 the database will complain.
5875 Another approach would be to compare the string against a short list of
5876 approved operators. We don't do that because we want to allow custom
5877 operators like ">100*", which would be difficult or impossible to
5878 express otherwise in a JSON query.
5880 static int is_good_operator( const char* op ) {
5881 if( !op ) return 0; // Sanity check
5885 if( isspace( (unsigned char) *s ) ) {
5886 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
5887 // and IS NOT DISTINCT FROM.
5888 if( !strcasecmp( op, "similar to" ) )
5890 else if( !strcasecmp( op, "is distinct from" ) )
5892 else if( !strcasecmp( op, "is not distinct from" ) )
5897 else if( ';' == *s )
5904 /* ----------------------------------------------------------------------------------
5905 The following machinery supports a stack of query frames for use by SELECT().
5907 A query frame caches information about one level of a SELECT query. When we enter
5908 a subquery, we push another query frame onto the stack, and pop it off when we leave.
5910 The query frame stores information about the core class, and about any joined classes
5913 The main purpose is to map table aliases to classes and tables, so that a query can
5914 join to the same table more than once. A secondary goal is to reduce the number of
5915 lookups in the IDL by caching the results.
5916 ----------------------------------------------------------------------------------*/
5918 #define STATIC_CLASS_INFO_COUNT 3
5920 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
5922 /* ---------------------------------------------------------------------------
5923 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5925 ---------------------------------------------------------------------------*/
5926 static ClassInfo* allocate_class_info( void ) {
5927 // In order to reduce the number of mallocs and frees, we return a static
5928 // instance of ClassInfo, if we can find one that we're not already using.
5929 // We rely on the fact that the compiler will implicitly initialize the
5930 // static instances so that in_use == 0.
5933 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5934 if( ! static_class_info[ i ].in_use ) {
5935 static_class_info[ i ].in_use = 1;
5936 return static_class_info + i;
5940 // The static ones are all in use. Malloc one.
5942 return safe_malloc( sizeof( ClassInfo ) );
5945 /* --------------------------------------------------------------------------
5946 Free any malloc'd memory owned by a ClassInfo; return it to a pristine state
5947 ---------------------------------------------------------------------------*/
5948 static void clear_class_info( ClassInfo* info ) {
5953 // Free any malloc'd strings
5955 if( info->alias != info->alias_store )
5956 free( info->alias );
5958 if( info->class_name != info->class_name_store )
5959 free( info->class_name );
5961 free( info->source_def );
5963 info->alias = info->class_name = info->source_def = NULL;
5967 /* --------------------------------------------------------------------------
5968 Deallocate a ClassInfo and everything it owns
5969 ---------------------------------------------------------------------------*/
5970 static void free_class_info( ClassInfo* info ) {
5975 clear_class_info( info );
5977 // If it's one of the static instances, just mark it as not in use
5980 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5981 if( info == static_class_info + i ) {
5982 static_class_info[ i ].in_use = 0;
5987 // Otherwise it must have been malloc'd, so free it
5992 /* --------------------------------------------------------------------------
5993 Populate an already-allocated ClassInfo. Return 0 if successful, 1 if not.
5994 ---------------------------------------------------------------------------*/
5995 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
5998 osrfLogError( OSRF_LOG_MARK,
5999 "%s ERROR: No ClassInfo available to populate", MODULENAME );
6000 info->alias = info->class_name = info->source_def = NULL;
6001 info->class_def = info->fields = info->links = NULL;
6006 osrfLogError( OSRF_LOG_MARK,
6007 "%s ERROR: No class name provided for lookup", MODULENAME );
6008 info->alias = info->class_name = info->source_def = NULL;
6009 info->class_def = info->fields = info->links = NULL;
6013 // Alias defaults to class name if not supplied
6014 if( ! alias || ! alias[ 0 ] )
6017 // Look up class info in the IDL
6018 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6020 osrfLogError( OSRF_LOG_MARK,
6021 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
6022 info->alias = info->class_name = info->source_def = NULL;
6023 info->class_def = info->fields = info->links = NULL;
6025 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6026 osrfLogError( OSRF_LOG_MARK,
6027 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
6028 info->alias = info->class_name = info->source_def = NULL;
6029 info->class_def = info->fields = info->links = NULL;
6033 osrfHash* links = osrfHashGet( class_def, "links" );
6035 osrfLogError( OSRF_LOG_MARK,
6036 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
6037 info->alias = info->class_name = info->source_def = NULL;
6038 info->class_def = info->fields = info->links = NULL;
6042 osrfHash* fields = osrfHashGet( class_def, "fields" );
6044 osrfLogError( OSRF_LOG_MARK,
6045 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
6046 info->alias = info->class_name = info->source_def = NULL;
6047 info->class_def = info->fields = info->links = NULL;
6051 char* source_def = getRelation( class_def );
6055 // We got everything we need, so populate the ClassInfo
6056 if( strlen( alias ) > ALIAS_STORE_SIZE )
6057 info->alias = strdup( alias );
6059 strcpy( info->alias_store, alias );
6060 info->alias = info->alias_store;
6063 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6064 info->class_name = strdup( class );
6066 strcpy( info->class_name_store, class );
6067 info->class_name = info->class_name_store;
6070 info->source_def = source_def;
6072 info->class_def = class_def;
6073 info->links = links;
6074 info->fields = fields;
6079 #define STATIC_FRAME_COUNT 3
6081 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6083 /* ---------------------------------------------------------------------------
6084 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
6086 ---------------------------------------------------------------------------*/
6087 static QueryFrame* allocate_frame( void ) {
6088 // In order to reduce the number of mallocs and frees, we return a static
6089 // instance of QueryFrame, if we can find one that we're not already using.
6090 // We rely on the fact that the compiler will implicitly initialize the
6091 // static instances so that in_use == 0.
6094 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6095 if( ! static_frame[ i ].in_use ) {
6096 static_frame[ i ].in_use = 1;
6097 return static_frame + i;
6101 // The static ones are all in use. Malloc one.
6103 return safe_malloc( sizeof( QueryFrame ) );
6106 /* --------------------------------------------------------------------------
6107 Free a QueryFrame, and all the memory it owns.
6108 ---------------------------------------------------------------------------*/
6109 static void free_query_frame( QueryFrame* frame ) {
6114 clear_class_info( &frame->core );
6116 // Free the join list
6118 ClassInfo* info = frame->join_list;
6121 free_class_info( info );
6125 frame->join_list = NULL;
6128 // If the frame is a static instance, just mark it as unused
6130 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6131 if( frame == static_frame + i ) {
6132 static_frame[ i ].in_use = 0;
6137 // Otherwise it must have been malloc'd, so free it
6142 /* --------------------------------------------------------------------------
6143 Search a given QueryFrame for a specified alias. If you find it, return
6144 a pointer to the corresponding ClassInfo. Otherwise return NULL.
6145 ---------------------------------------------------------------------------*/
6146 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6147 if( ! frame || ! target ) {
6151 ClassInfo* found_class = NULL;
6153 if( !strcmp( target, frame->core.alias ) )
6154 return &(frame->core);
6156 ClassInfo* curr_class = frame->join_list;
6157 while( curr_class ) {
6158 if( strcmp( target, curr_class->alias ) )
6159 curr_class = curr_class->next;
6161 found_class = curr_class;
6170 /* --------------------------------------------------------------------------
6171 Push a new (blank) QueryFrame onto the stack.
6172 ---------------------------------------------------------------------------*/
6173 static void push_query_frame( void ) {
6174 QueryFrame* frame = allocate_frame();
6175 frame->join_list = NULL;
6176 frame->next = curr_query;
6178 // Initialize the ClassInfo for the core class
6179 ClassInfo* core = &frame->core;
6180 core->alias = core->class_name = core->source_def = NULL;
6181 core->class_def = core->fields = core->links = NULL;
6186 /* --------------------------------------------------------------------------
6187 Pop a QueryFrame off the stack and destroy it
6188 ---------------------------------------------------------------------------*/
6189 static void pop_query_frame( void ) {
6194 QueryFrame* popped = curr_query;
6195 curr_query = popped->next;
6197 free_query_frame( popped );
6200 /* --------------------------------------------------------------------------
6201 Populate the ClassInfo for the core class. Return 0 if successful, 1 if not.
6202 ---------------------------------------------------------------------------*/
6203 static int add_query_core( const char* alias, const char* class_name ) {
6206 if( ! curr_query ) {
6207 osrfLogError( OSRF_LOG_MARK,
6208 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
6210 } else if( curr_query->core.alias ) {
6211 osrfLogError( OSRF_LOG_MARK,
6212 "%s ERROR: Core class %s already populated as %s",
6213 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
6217 build_class_info( &curr_query->core, alias, class_name );
6218 if( curr_query->core.alias )
6221 osrfLogError( OSRF_LOG_MARK,
6222 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
6227 /* --------------------------------------------------------------------------
6228 Search the current QueryFrame for a specified alias. If you find it,
6229 return a pointer to the corresponding ClassInfo. Otherwise return NULL.
6230 ---------------------------------------------------------------------------*/
6231 static ClassInfo* search_alias( const char* target ) {
6232 return search_alias_in_frame( curr_query, target );
6235 /* --------------------------------------------------------------------------
6236 Search all levels of query for a specified alias, starting with the
6237 current query. If you find it, return a pointer to the corresponding
6238 ClassInfo. Otherwise return NULL.
6239 ---------------------------------------------------------------------------*/
6240 static ClassInfo* search_all_alias( const char* target ) {
6241 ClassInfo* found_class = NULL;
6242 QueryFrame* curr_frame = curr_query;
6244 while( curr_frame ) {
6245 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6248 curr_frame = curr_frame->next;
6254 /* --------------------------------------------------------------------------
6255 Add a class to the list of classes joined to the current query.
6256 ---------------------------------------------------------------------------*/
6257 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6259 if( ! classname || ! *classname ) { // sanity check
6260 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6267 const ClassInfo* conflict = search_alias( alias );
6269 osrfLogError( OSRF_LOG_MARK,
6270 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6271 MODULENAME, alias, conflict->class_name );
6275 ClassInfo* info = allocate_class_info();
6277 if( build_class_info( info, alias, classname ) ) {
6278 free_class_info( info );
6282 // Add the new ClassInfo to the join list of the current QueryFrame
6283 info->next = curr_query->join_list;
6284 curr_query->join_list = info;
6289 /* --------------------------------------------------------------------------
6290 Destroy all nodes on the query stack.
6291 ---------------------------------------------------------------------------*/
6292 static void clear_query_stack( void ) {