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 const char* trans_id = getXactId( ctx );
824 if( NULL == trans_id ) {
825 osrfAppSessionStatus(
827 OSRF_STATUS_INTERNALSERVERERROR,
828 "osrfMethodException",
830 "No active transaction -- required for savepoints"
835 // Get the savepoint name from the method params
836 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
838 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
842 "%s: Error creating savepoint %s in transaction %s",
847 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
848 "osrfMethodException", ctx->request, "Error creating savepoint" );
851 jsonObject* ret = jsonNewObject(spName);
852 osrfAppRespondComplete( ctx, ret );
859 @brief Implement the savepoint.release method.
860 @param ctx Pointer to the method context.
861 @return Zero if successful, or -1 if not.
863 Issue a RELEASE SAVEPOINT to the database server.
866 - authkey (PCRUD only)
869 Return to client: Savepoint name
871 int releaseSavepoint ( osrfMethodContext* ctx ) {
872 if(osrfMethodVerifyContext( ctx )) {
873 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
880 jsonObject* user = verifyUserPCRUD( ctx );
883 jsonObjectFree(user);
886 // Verify that a transaction is pending
887 const char* trans_id = getXactId( ctx );
888 if( NULL == trans_id ) {
889 osrfAppSessionStatus(
891 OSRF_STATUS_INTERNALSERVERERROR,
892 "osrfMethodException",
894 "No active transaction -- required for savepoints"
899 // Get the savepoint name from the method params
900 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
902 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
906 "%s: Error releasing savepoint %s in transaction %s",
911 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
912 "osrfMethodException", ctx->request, "Error releasing savepoint" );
915 jsonObject* ret = jsonNewObject(spName);
916 osrfAppRespondComplete( ctx, ret );
923 @brief Implement the savepoint.rollback method.
924 @param ctx Pointer to the method context.
925 @return Zero if successful, or -1 if not.
927 Issue a ROLLBACK TO SAVEPOINT to the database server.
930 - authkey (PCRUD only)
933 Return to client: Savepoint name
935 int rollbackSavepoint ( osrfMethodContext* ctx ) {
936 if(osrfMethodVerifyContext( ctx )) {
937 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
944 jsonObject* user = verifyUserPCRUD( ctx );
947 jsonObjectFree(user);
950 // Verify that a transaction is pending
951 const char* trans_id = getXactId( ctx );
952 if( NULL == trans_id ) {
953 osrfAppSessionStatus(
955 OSRF_STATUS_INTERNALSERVERERROR,
956 "osrfMethodException",
958 "No active transaction -- required for savepoints"
963 // Get the savepoint name from the method params
964 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
966 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
970 "%s: Error rolling back savepoint %s in transaction %s",
975 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
976 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
979 jsonObject* ret = jsonNewObject(spName);
980 osrfAppRespondComplete( ctx, ret );
987 @brief Implement the transaction.commit method.
988 @param ctx Pointer to the method context.
989 @return Zero if successful, or -1 if not.
991 Issue a COMMIT to the database server.
994 - authkey (PCRUD only)
996 Return to client: Transaction ID.
998 int commitTransaction ( osrfMethodContext* ctx ) {
999 if(osrfMethodVerifyContext( ctx )) {
1000 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1005 jsonObject* user = verifyUserPCRUD( ctx );
1008 jsonObjectFree(user);
1011 // Verify that a transaction is pending
1012 const char* trans_id = getXactId( ctx );
1013 if( NULL == trans_id ) {
1014 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1015 "osrfMethodException", ctx->request, "No active transaction to commit" );
1019 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
1021 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
1022 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1023 "osrfMethodException", ctx->request, "Error committing transaction" );
1026 jsonObject* ret = jsonNewObject( trans_id );
1027 osrfAppRespondComplete( ctx, ret );
1028 jsonObjectFree(ret);
1035 @brief Implement the transaction.rollback method.
1036 @param ctx Pointer to the method context.
1037 @return Zero if successful, or -1 if not.
1039 Issue a ROLLBACK to the database server.
1042 - authkey (PCRUD only)
1044 Return to client: Transaction ID
1046 int rollbackTransaction ( osrfMethodContext* ctx ) {
1047 if(osrfMethodVerifyContext( ctx )) {
1048 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1053 jsonObject* user = verifyUserPCRUD( ctx );
1056 jsonObjectFree(user);
1059 // Verify that a transaction is pending
1060 const char* trans_id = getXactId( ctx );
1061 if( NULL == trans_id ) {
1062 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1063 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1067 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
1069 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
1070 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1071 "osrfMethodException", ctx->request, "Error rolling back transaction" );
1074 jsonObject* ret = jsonNewObject( trans_id );
1075 osrfAppRespondComplete( ctx, ret );
1076 jsonObjectFree(ret);
1083 @brief Implement the class-specific methods.
1084 @param ctx Pointer to the method context.
1085 @return Zero if successful, or -1 if not.
1087 Branch on the method type: create, retrieve, update, delete, search, and id_list.
1089 The method parameters and the type of value returned to the client depend on the method
1090 type. However, for PCRUD methods, the first method parameter should always be an
1093 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
1094 if(osrfMethodVerifyContext( ctx )) {
1095 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1099 int err = 0; // to be returned to caller
1100 jsonObject * obj = NULL; // to be returned to client
1102 // Get the method type so that we can branch on it
1103 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1104 const char* methodtype = osrfHashGet( method_meta, "methodtype" );
1106 if (!strcmp(methodtype, "create")) {
1107 obj = doCreate(ctx, &err);
1108 osrfAppRespondComplete( ctx, obj );
1110 else if (!strcmp(methodtype, "retrieve")) {
1111 obj = doRetrieve(ctx, &err);
1112 osrfAppRespondComplete( ctx, obj );
1114 else if (!strcmp(methodtype, "update")) {
1115 obj = doUpdate(ctx, &err);
1116 osrfAppRespondComplete( ctx, obj );
1118 else if (!strcmp(methodtype, "delete")) {
1119 obj = doDelete(ctx, &err);
1120 osrfAppRespondComplete( ctx, obj );
1122 else if (!strcmp(methodtype, "search")) {
1124 // Implement search method: return rows of the specified class that satisfy
1125 // a specified WHERE clause.
1127 // Method parameters:
1128 // - authkey (PCRUD only)
1129 // - WHERE clause, as jsonObject
1130 // - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1132 jsonObject* where_clause;
1133 jsonObject* rest_of_query;
1136 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1137 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1139 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1140 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1144 osrfHash* class_obj = osrfHashGet( method_meta, "class" );
1145 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
1150 // Return each row to the client (except that some may be suppressed by PCRUD)
1151 jsonObject* cur = 0;
1152 unsigned long res_idx = 0;
1153 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1155 if(!verifyObjectPCRUD(ctx, cur))
1158 osrfAppRespond( ctx, cur );
1160 osrfAppRespondComplete( ctx, NULL );
1162 } else if (!strcmp(methodtype, "id_list")) {
1164 // Implement the id_list method. Return the primary key values for all rows of the
1165 // relevant class that satisfy a specified WHERE clause. This method relies on the
1166 // assumption that every class has a primary key consisting of a single column.
1168 // Method parameters:
1169 // - authkey (PCRUD only)
1170 // - WHERE clause, as jsonObject
1171 // - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1173 jsonObject* where_clause;
1174 jsonObject* rest_of_query;
1176 // We use the where clause without change. But we need to massage the rest of the
1177 // query, so we work with a copy of it instead of modifying the original.
1180 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1181 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1183 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1184 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1187 // Eliminate certain SQL clauses, if present
1188 if ( rest_of_query ) {
1189 jsonObjectRemoveKey( rest_of_query, "select" );
1190 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1191 jsonObjectRemoveKey( rest_of_query, "flesh" );
1192 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
1194 rest_of_query = jsonNewObjectType( JSON_HASH );
1197 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1199 // Get the class metadata
1200 osrfHash* class_obj = osrfHashGet( method_meta, "class" );
1202 // Build a SELECT list containing just the primary key,
1203 // i.e. like { "classname":["keyname"] }
1204 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1205 jsonObjectPush( col_list_obj, // Load array with name of primary key
1206 jsonNewObject( osrfHashGet( class_obj, "primarykey" ) ) );
1207 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1208 jsonObjectSetKey( select_clause, osrfHashGet( class_obj, "classname" ), col_list_obj );
1210 jsonObjectSetKey( rest_of_query, "select", select_clause );
1213 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
1215 jsonObjectFree( rest_of_query );
1219 // Return each primary key value to the client
1221 unsigned long res_idx = 0;
1222 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1224 if(!verifyObjectPCRUD(ctx, cur))
1227 osrfAppRespond( ctx,
1228 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
1231 osrfAppRespondComplete( ctx, NULL );
1234 osrfAppRespondComplete( ctx, obj ); // should be unreachable...
1237 jsonObjectFree(obj);
1242 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1245 osrfHash* meta = (osrfHash*) ctx->method->userData;
1246 osrfHash* class = osrfHashGet( meta, "class" );
1248 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1250 const char* temp_classname = param->classname;
1251 if( ! temp_classname )
1252 temp_classname = "(null)";
1254 growing_buffer* msg = buffer_init(128);
1257 "%s: %s method for type %s was passed a %s",
1259 osrfHashGet(meta, "methodtype"),
1260 osrfHashGet(class, "classname"),
1264 char* m = buffer_release(msg);
1265 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1274 ret = verifyObjectPCRUD( ctx, param );
1282 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1283 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1284 jsonObject* auth_object = jsonNewObject(auth);
1285 jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve",
1287 jsonObjectFree(auth_object);
1289 if (!user->classname || strcmp(user->classname, "au")) {
1291 growing_buffer* msg = buffer_init(128);
1294 "%s: permacrud received a bad auth token: %s",
1299 char* m = buffer_release(msg);
1300 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1304 jsonObjectFree(user);
1312 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1314 dbhandle = writehandle;
1316 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1317 osrfHash* class = osrfHashGet( method_metadata, "class" );
1318 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1321 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
1322 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1323 } else if ( *method_type == 'u' || *method_type == 'd' ) {
1324 fetch = 1; // MUST go to the db for the object for update and delete
1327 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
1330 // No permacrud for this method type on this class
1332 growing_buffer* msg = buffer_init(128);
1335 "%s: %s on class %s has no permacrud IDL entry",
1337 osrfHashGet(method_metadata, "methodtype"),
1338 osrfHashGet(class, "classname")
1341 char* m = buffer_release(msg);
1342 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1343 "osrfMethodException", ctx->request, m );
1350 jsonObject* user = verifyUserPCRUD( ctx );
1354 int userid = atoi( oilsFMGetString( user, "id" ) );
1355 jsonObjectFree(user);
1357 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1358 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1359 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1361 osrfStringArray* context_org_array = osrfNewStringArray(1);
1364 char* pkey_value = NULL;
1365 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1366 osrfLogDebug( OSRF_LOG_MARK,
1367 "global-level permissions required, fetching top of the org tree" );
1369 // check for perm at top of org tree
1370 char* org_tree_root_id = org_tree_root( ctx );
1371 if( org_tree_root_id ) {
1372 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1373 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1375 osrfStringArrayFree( context_org_array );
1380 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1381 "fetching context org ids" );
1382 const char* pkey = osrfHashGet(class, "primarykey");
1383 jsonObject *param = NULL;
1385 if (obj->classname) {
1386 pkey_value = oilsFMGetString( obj, pkey );
1388 param = jsonObjectClone(obj);
1389 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1392 pkey_value = jsonObjectToSimpleString( obj );
1394 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1395 "of %s and retrieving from the database", pkey_value );
1399 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1400 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1401 jsonObjectFree(_tmp_params);
1403 param = jsonObjectExtractIndex(_list, 0);
1404 jsonObjectFree(_list);
1408 osrfLogDebug( OSRF_LOG_MARK,
1409 "Object not found in the database with primary key %s of %s",
1412 growing_buffer* msg = buffer_init(128);
1415 "%s: no object found with primary key %s of %s",
1421 char* m = buffer_release(msg);
1422 osrfAppSessionStatus(
1424 OSRF_STATUS_INTERNALSERVERERROR,
1425 "osrfMethodException",
1431 if (pkey_value) free(pkey_value);
1436 if (local_context->size > 0) {
1437 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1438 local_context->size);
1440 const char* lcontext = NULL;
1441 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1442 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1445 "adding class-local field %s (value: %s) to the context org list",
1447 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1452 if (foreign_context) {
1453 unsigned long class_count = osrfHashGetCount( foreign_context );
1454 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1456 if (class_count > 0) {
1458 osrfHash* fcontext = NULL;
1459 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1460 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1461 const char* class_name = osrfHashIteratorKey( class_itr );
1462 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1466 "%d foreign context fields(s) specified for class %s",
1467 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1471 char* foreign_pkey = osrfHashGet(fcontext, "field");
1472 char* foreign_pkey_value =
1473 oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1475 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1477 jsonObject* _list = doFieldmapperSearch(
1478 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1480 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1481 jsonObjectFree(_tmp_params);
1482 jsonObjectFree(_list);
1484 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1486 if (_fparam && jump_list) {
1487 const char* flink = NULL;
1489 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1490 free(foreign_pkey_value);
1492 osrfHash* foreign_link_hash =
1493 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1495 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1496 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1498 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1500 _list = doFieldmapperSearch(
1502 osrfHashGet( oilsIDL(),
1503 osrfHashGet( foreign_link_hash, "class" ) ),
1509 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1510 jsonObjectFree(_tmp_params);
1511 jsonObjectFree(_list);
1517 growing_buffer* msg = buffer_init(128);
1520 "%s: no object found with primary key %s of %s",
1526 char* m = buffer_release(msg);
1527 osrfAppSessionStatus(
1529 OSRF_STATUS_INTERNALSERVERERROR,
1530 "osrfMethodException",
1536 osrfHashIteratorFree(class_itr);
1537 free(foreign_pkey_value);
1538 jsonObjectFree(param);
1543 free(foreign_pkey_value);
1546 const char* foreign_field = NULL;
1547 while ( (foreign_field = osrfStringArrayGetString(
1548 osrfHashGet(fcontext,"context" ), j++)) ) {
1549 osrfStringArrayAdd( context_org_array,
1550 oilsFMGetString( _fparam, foreign_field ) );
1553 "adding foreign class %s field %s (value: %s) to the context org list",
1556 osrfStringArrayGetString(
1557 context_org_array, context_org_array->size - 1)
1561 jsonObjectFree(_fparam);
1564 osrfHashIteratorFree( class_itr );
1568 jsonObjectFree(param);
1571 const char* context_org = NULL;
1572 const char* perm = NULL;
1575 if (permission->size == 0) {
1576 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1581 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1583 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1589 "Checking object permission [%s] for user %d "
1590 "on object %s (class %s) at org %d",
1594 osrfHashGet(class, "classname"),
1598 result = dbi_conn_queryf(
1600 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1603 osrfHashGet(class, "classname"),
1611 "Received a result for object permission [%s] "
1612 "for user %d on object %s (class %s) at org %d",
1616 osrfHashGet(class, "classname"),
1620 if (dbi_result_first_row(result)) {
1621 jsonObject* return_val = oilsMakeJSONFromResult( result );
1622 const char* has_perm = jsonObjectGetString(
1623 jsonObjectGetKeyConst(return_val, "has_perm") );
1627 "Status of object permission [%s] for user %d "
1628 "on object %s (class %s) at org %d is %s",
1632 osrfHashGet(class, "classname"),
1637 if ( *has_perm == 't' ) OK = 1;
1638 jsonObjectFree(return_val);
1641 dbi_result_free(result);
1647 osrfLogDebug( OSRF_LOG_MARK,
1648 "Checking non-object permission [%s] for user %d at org %d",
1649 perm, userid, atoi(context_org) );
1650 result = dbi_conn_queryf(
1652 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1659 osrfLogDebug( OSRF_LOG_MARK,
1660 "Received a result for permission [%s] for user %d at org %d",
1661 perm, userid, atoi(context_org) );
1662 if ( dbi_result_first_row(result) ) {
1663 jsonObject* return_val = oilsMakeJSONFromResult( result );
1664 const char* has_perm = jsonObjectGetString(
1665 jsonObjectGetKeyConst(return_val, "has_perm") );
1666 osrfLogDebug( OSRF_LOG_MARK,
1667 "Status of permission [%s] for user %d at org %d is [%s]",
1668 perm, userid, atoi(context_org), has_perm );
1669 if ( *has_perm == 't' )
1671 jsonObjectFree(return_val);
1674 dbi_result_free(result);
1683 if (pkey_value) free(pkey_value);
1684 osrfStringArrayFree(context_org_array);
1690 @brief Look up the root of the org_unit tree.
1691 @param ctx Pointer to the method context.
1692 @return The id of the root org unit, as a character string.
1694 Query actor.org_unit where parent_ou is null, and return the id as a string.
1696 This function assumes that there is only one root org unit, i.e. that we
1697 have a single tree, not a forest.
1699 The calling code is responsible for freeing the returned string.
1701 static char* org_tree_root( osrfMethodContext* ctx ) {
1703 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1704 static time_t last_lookup_time = 0;
1705 time_t current_time = time( NULL );
1707 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1708 // We successfully looked this up less than an hour ago.
1709 // It's not likely to have changed since then.
1710 return strdup( cached_root_id );
1712 last_lookup_time = current_time;
1715 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1716 jsonObject* result = doFieldmapperSearch(
1717 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1718 jsonObjectFree( where_clause );
1720 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1723 jsonObjectFree( result );
1725 growing_buffer* msg = buffer_init(128);
1726 OSRF_BUFFER_ADD( msg, MODULENAME );
1727 OSRF_BUFFER_ADD( msg,
1728 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1730 char* m = buffer_release(msg);
1731 osrfAppSessionStatus( ctx->session,
1732 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1735 cached_root_id[ 0 ] = '\0';
1739 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1740 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1742 jsonObjectFree( result );
1744 strcpy( cached_root_id, root_org_unit_id );
1745 return root_org_unit_id;
1749 @brief Create a JSON_HASH with a single key/value pair.
1750 @param key The key of the key/value pair.
1751 @param value the value of the key/value pair.
1752 @return Pointer to a newly created jsonObject of type JSON_HASH.
1754 The value of the key/value is either a string or (if @a value is NULL) a null.
1756 static jsonObject* single_hash( const char* key, const char* value ) {
1758 if( ! key ) key = "";
1760 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1761 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1767 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1769 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1771 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1772 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1774 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1775 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1778 if (!verifyObjectClass(ctx, target)) {
1783 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1785 const char* trans_id = getXactId( ctx );
1787 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1789 osrfAppSessionStatus(
1791 OSRF_STATUS_BADREQUEST,
1792 "osrfMethodException",
1794 "No active transaction -- required for CREATE"
1800 // The following test is harmless but redundant. If a class is
1801 // readonly, we don't register a create method for it.
1802 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1803 osrfAppSessionStatus(
1805 OSRF_STATUS_BADREQUEST,
1806 "osrfMethodException",
1808 "Cannot INSERT readonly class"
1814 // Set the last_xact_id
1815 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1817 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1818 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1821 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1823 dbhandle = writehandle;
1825 osrfHash* fields = osrfHashGet(meta, "fields");
1826 char* pkey = osrfHashGet(meta, "primarykey");
1827 char* seq = osrfHashGet(meta, "sequence");
1829 growing_buffer* table_buf = buffer_init(128);
1830 growing_buffer* col_buf = buffer_init(128);
1831 growing_buffer* val_buf = buffer_init(128);
1833 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1834 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1835 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1836 buffer_add(val_buf,"VALUES (");
1840 osrfHash* field = NULL;
1841 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1842 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1844 const char* field_name = osrfHashIteratorKey( field_itr );
1846 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1849 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1852 if (field_object && field_object->classname) {
1853 value = oilsFMGetString(
1855 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1857 } else if( field_object && JSON_BOOL == field_object->type ) {
1858 if( jsonBoolIsTrue( field_object ) )
1859 value = strdup( "t" );
1861 value = strdup( "f" );
1863 value = jsonObjectToSimpleString( field_object );
1869 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1870 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1873 buffer_add(col_buf, field_name);
1875 if (!field_object || field_object->type == JSON_NULL) {
1876 buffer_add( val_buf, "DEFAULT" );
1878 } else if ( !strcmp(get_primitive( field ), "number") ) {
1879 const char* numtype = get_datatype( field );
1880 if ( !strcmp( numtype, "INT8") ) {
1881 buffer_fadd( val_buf, "%lld", atoll(value) );
1883 } else if ( !strcmp( numtype, "INT") ) {
1884 buffer_fadd( val_buf, "%d", atoi(value) );
1886 } else if ( !strcmp( numtype, "NUMERIC") ) {
1887 buffer_fadd( val_buf, "%f", atof(value) );
1890 if ( dbi_conn_quote_string(writehandle, &value) ) {
1891 OSRF_BUFFER_ADD( val_buf, value );
1894 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1895 osrfAppSessionStatus(
1897 OSRF_STATUS_INTERNALSERVERERROR,
1898 "osrfMethodException",
1900 "Error quoting string -- please see the error log for more details"
1903 buffer_free(table_buf);
1904 buffer_free(col_buf);
1905 buffer_free(val_buf);
1915 osrfHashIteratorFree( field_itr );
1917 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1918 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1920 char* table_str = buffer_release(table_buf);
1921 char* col_str = buffer_release(col_buf);
1922 char* val_str = buffer_release(val_buf);
1923 growing_buffer* sql = buffer_init(128);
1924 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1929 char* query = buffer_release(sql);
1931 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1934 dbi_result result = dbi_conn_query(writehandle, query);
1936 jsonObject* obj = NULL;
1939 obj = jsonNewObject(NULL);
1942 "%s ERROR inserting %s object using query [%s]",
1944 osrfHashGet(meta, "fieldmapper"),
1947 osrfAppSessionStatus(
1949 OSRF_STATUS_INTERNALSERVERERROR,
1950 "osrfMethodException",
1952 "INSERT error -- please see the error log for more details"
1957 char* id = oilsFMGetString(target, pkey);
1959 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1960 growing_buffer* _id = buffer_init(10);
1961 buffer_fadd(_id, "%lld", new_id);
1962 id = buffer_release(_id);
1965 // Find quietness specification, if present
1966 const char* quiet_str = NULL;
1968 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1970 quiet_str = jsonObjectGetString( quiet_obj );
1973 if( str_is_true( quiet_str ) ) { // if quietness is specified
1974 obj = jsonNewObject(id);
1978 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1979 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1981 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1983 jsonObjectFree( where_clause );
1988 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1991 jsonObjectFree( list );
2004 * Fetch one row from a specified table, using a specified value
2005 * for the primary key
2007 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
2017 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2019 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos); // key value
2023 "%s retrieving %s object with primary key value of %s",
2025 osrfHashGet( class_def, "fieldmapper" ),
2026 jsonObjectGetString( id_obj )
2029 // Build a WHERE clause based on the key value
2030 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2033 osrfHashGet( class_def, "primarykey" ),
2034 jsonObjectClone( id_obj )
2037 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
2039 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
2041 jsonObjectFree( where_clause );
2045 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2046 jsonObjectFree( list );
2049 if(!verifyObjectPCRUD(ctx, obj)) {
2050 jsonObjectFree(obj);
2053 growing_buffer* msg = buffer_init(128);
2054 OSRF_BUFFER_ADD( msg, MODULENAME );
2055 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2057 char* m = buffer_release(msg);
2058 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
2069 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
2070 growing_buffer* val_buf = buffer_init(32);
2071 const char* numtype = get_datatype( field );
2073 if ( !strncmp( numtype, "INT", 3 ) ) {
2074 if (value->type == JSON_NUMBER)
2075 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2076 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2078 //const char* val_str = jsonObjectGetString( value );
2079 //buffer_fadd( val_buf, "%ld", atol(val_str) );
2080 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2083 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
2084 if (value->type == JSON_NUMBER)
2085 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
2086 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2088 //const char* val_str = jsonObjectGetString( value );
2089 //buffer_fadd( val_buf, "%f", atof(val_str) );
2090 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2094 // Presumably this was really intended ot be a string, so quote it
2095 char* str = jsonObjectToSimpleString( value );
2096 if ( dbi_conn_quote_string(dbhandle, &str) ) {
2097 OSRF_BUFFER_ADD( val_buf, str );
2100 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
2102 buffer_free(val_buf);
2107 return buffer_release(val_buf);
2110 static char* searchINPredicate (const char* class_alias, osrfHash* field,
2111 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2112 growing_buffer* sql_buf = buffer_init(32);
2118 osrfHashGet(field, "name")
2122 buffer_add(sql_buf, "IN (");
2123 } else if (!(strcasecmp(op,"not in"))) {
2124 buffer_add(sql_buf, "NOT IN (");
2126 buffer_add(sql_buf, "IN (");
2129 if (node->type == JSON_HASH) {
2130 // subquery predicate
2131 char* subpred = buildQuery( ctx, node, SUBSELECT );
2133 buffer_free( sql_buf );
2137 buffer_add(sql_buf, subpred);
2140 } else if (node->type == JSON_ARRAY) {
2141 // literal value list
2142 int in_item_index = 0;
2143 int in_item_first = 1;
2144 const jsonObject* in_item;
2145 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
2150 buffer_add(sql_buf, ", ");
2153 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2154 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
2155 MODULENAME, json_type( in_item->type ) );
2156 buffer_free(sql_buf);
2160 // Append the literal value -- quoted if not a number
2161 if ( JSON_NUMBER == in_item->type ) {
2162 char* val = jsonNumberToDBString( field, in_item );
2163 OSRF_BUFFER_ADD( sql_buf, val );
2166 } else if ( !strcmp( get_primitive( field ), "number") ) {
2167 char* val = jsonNumberToDBString( field, in_item );
2168 OSRF_BUFFER_ADD( sql_buf, val );
2172 char* key_string = jsonObjectToSimpleString(in_item);
2173 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
2174 OSRF_BUFFER_ADD( sql_buf, key_string );
2177 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
2179 buffer_free(sql_buf);
2185 if( in_item_first ) {
2186 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
2187 buffer_free( sql_buf );
2191 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2192 MODULENAME, json_type( node->type ) );
2193 buffer_free(sql_buf);
2197 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2199 return buffer_release(sql_buf);
2202 // Receive a JSON_ARRAY representing a function call. The first
2203 // entry in the array is the function name. The rest are parameters.
2204 static char* searchValueTransform( const jsonObject* array ) {
2206 if( array->size < 1 ) {
2207 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
2211 // Get the function name
2212 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2213 if( func_item->type != JSON_STRING ) {
2214 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2215 MODULENAME, json_type( func_item->type ) );
2219 growing_buffer* sql_buf = buffer_init(32);
2221 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2222 OSRF_BUFFER_ADD( sql_buf, "( " );
2224 // Get the parameters
2225 int func_item_index = 1; // We already grabbed the zeroth entry
2226 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2228 // Add a separator comma, if we need one
2229 if( func_item_index > 2 )
2230 buffer_add( sql_buf, ", " );
2232 // Add the current parameter
2233 if (func_item->type == JSON_NULL) {
2234 buffer_add( sql_buf, "NULL" );
2236 char* val = jsonObjectToSimpleString(func_item);
2237 if ( dbi_conn_quote_string(dbhandle, &val) ) {
2238 OSRF_BUFFER_ADD( sql_buf, val );
2241 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2242 buffer_free(sql_buf);
2249 buffer_add( sql_buf, " )" );
2251 return buffer_release(sql_buf);
2254 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
2255 const jsonObject* node, const char* op) {
2257 if( ! is_good_operator( op ) ) {
2258 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2262 char* val = searchValueTransform(node);
2266 growing_buffer* sql_buf = buffer_init(32);
2271 osrfHashGet(field, "name"),
2278 return buffer_release(sql_buf);
2281 // class_alias is a class name or other table alias
2282 // field is a field definition as stored in the IDL
2283 // node comes from the method parameter, and may represent an entry in the SELECT list
2284 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
2285 growing_buffer* sql_buf = buffer_init(32);
2287 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
2288 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
2290 if(transform_subcolumn) {
2291 if( ! is_identifier( transform_subcolumn ) ) {
2292 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2293 MODULENAME, transform_subcolumn );
2294 buffer_free( sql_buf );
2297 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2300 if (field_transform) {
2302 if( ! is_identifier( field_transform ) ) {
2303 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2304 MODULENAME, field_transform );
2305 buffer_free( sql_buf );
2309 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class_alias, osrfHashGet(field, "name"));
2310 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2313 if( array->type != JSON_ARRAY ) {
2314 osrfLogError( OSRF_LOG_MARK,
2315 "%s: Expected JSON_ARRAY for function params; found %s",
2316 MODULENAME, json_type( array->type ) );
2317 buffer_free( sql_buf );
2320 int func_item_index = 0;
2321 jsonObject* func_item;
2322 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2324 char* val = jsonObjectToSimpleString(func_item);
2327 buffer_add( sql_buf, ",NULL" );
2328 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
2329 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2330 OSRF_BUFFER_ADD( sql_buf, val );
2332 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2334 buffer_free(sql_buf);
2341 buffer_add( sql_buf, " )" );
2344 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2347 if (transform_subcolumn)
2348 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2350 return buffer_release(sql_buf);
2353 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2354 const jsonObject* node, const char* op ) {
2356 if( ! is_good_operator( op ) ) {
2357 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2361 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2362 if( ! field_transform )
2365 int extra_parens = 0; // boolean
2367 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2368 if ( ! value_obj ) {
2369 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2371 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
2372 free(field_transform);
2376 } else if ( value_obj->type == JSON_ARRAY ) {
2377 value = searchValueTransform( value_obj );
2379 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
2380 free( field_transform );
2383 } else if ( value_obj->type == JSON_HASH ) {
2384 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2386 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
2387 free(field_transform);
2391 } else if ( value_obj->type == JSON_NUMBER ) {
2392 value = jsonNumberToDBString( field, value_obj );
2393 } else if ( value_obj->type == JSON_NULL ) {
2394 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
2395 free(field_transform);
2397 } else if ( value_obj->type == JSON_BOOL ) {
2398 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
2399 free(field_transform);
2402 if ( !strcmp( get_primitive( field ), "number") ) {
2403 value = jsonNumberToDBString( field, value_obj );
2405 value = jsonObjectToSimpleString( value_obj );
2406 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2407 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
2409 free(field_transform);
2415 const char* left_parens = "";
2416 const char* right_parens = "";
2418 if( extra_parens ) {
2423 growing_buffer* sql_buf = buffer_init(32);
2427 "%s%s %s %s %s %s%s",
2438 free(field_transform);
2440 return buffer_release(sql_buf);
2443 static char* searchSimplePredicate (const char* op, const char* class_alias,
2444 osrfHash* field, const jsonObject* node) {
2446 if( ! is_good_operator( op ) ) {
2447 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2453 // Get the value to which we are comparing the specified column
2454 if (node->type != JSON_NULL) {
2455 if ( node->type == JSON_NUMBER ) {
2456 val = jsonNumberToDBString( field, node );
2457 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2458 val = jsonNumberToDBString( field, node );
2460 val = jsonObjectToSimpleString(node);
2465 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2466 // Value is not numeric; enclose it in quotes
2467 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2468 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2474 // Compare to a null value
2475 val = strdup( "NULL" );
2476 if (strcmp( op, "=" ))
2482 growing_buffer* sql_buf = buffer_init(32);
2483 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2484 char* pred = buffer_release( sql_buf );
2491 static char* searchBETWEENPredicate (const char* class_alias,
2492 osrfHash* field, const jsonObject* node) {
2494 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2495 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2497 if( NULL == y_node ) {
2498 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2501 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2502 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2509 if ( !strcmp( get_primitive( field ), "number") ) {
2510 x_string = jsonNumberToDBString(field, x_node);
2511 y_string = jsonNumberToDBString(field, y_node);
2514 x_string = jsonObjectToSimpleString(x_node);
2515 y_string = jsonObjectToSimpleString(y_node);
2516 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2517 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2518 MODULENAME, x_string, y_string);
2525 growing_buffer* sql_buf = buffer_init(32);
2526 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2527 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2531 return buffer_release(sql_buf);
2534 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2535 jsonObject* node, osrfMethodContext* ctx ) {
2538 if (node->type == JSON_ARRAY) { // equality IN search
2539 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2540 } else if (node->type == JSON_HASH) { // other search
2541 jsonIterator* pred_itr = jsonNewIterator( node );
2542 if( !jsonIteratorHasNext( pred_itr ) ) {
2543 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2544 MODULENAME, osrfHashGet(field, "name") );
2546 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2548 // Verify that there are no additional predicates
2549 if( jsonIteratorHasNext( pred_itr ) ) {
2550 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2551 MODULENAME, osrfHashGet(field, "name") );
2552 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2553 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2554 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2555 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
2556 else if ( pred_node->type == JSON_ARRAY )
2557 pred = searchFunctionPredicate( class_info->alias, field, pred_node, pred_itr->key );
2558 else if ( pred_node->type == JSON_HASH )
2559 pred = searchFieldTransformPredicate( class_info, field, pred_node, pred_itr->key );
2561 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2563 jsonIteratorFree(pred_itr);
2565 } else if (node->type == JSON_NULL) { // IS NULL search
2566 growing_buffer* _p = buffer_init(64);
2569 "\"%s\".%s IS NULL",
2570 class_info->class_name,
2571 osrfHashGet(field, "name")
2573 pred = buffer_release(_p);
2574 } else { // equality search
2575 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2594 field : call_number,
2610 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2612 const jsonObject* working_hash;
2613 jsonObject* freeable_hash = NULL;
2615 if (join_hash->type == JSON_HASH) {
2616 working_hash = join_hash;
2617 } else if (join_hash->type == JSON_STRING) {
2618 // turn it into a JSON_HASH by creating a wrapper
2619 // around a copy of the original
2620 const char* _tmp = jsonObjectGetString( join_hash );
2621 freeable_hash = jsonNewObjectType(JSON_HASH);
2622 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2623 working_hash = freeable_hash;
2627 "%s: JOIN failed; expected JSON object type not found",
2633 growing_buffer* join_buf = buffer_init(128);
2634 const char* leftclass = left_info->class_name;
2636 jsonObject* snode = NULL;
2637 jsonIterator* search_itr = jsonNewIterator( working_hash );
2639 while ( (snode = jsonIteratorNext( search_itr )) ) {
2640 const char* right_alias = search_itr->key;
2642 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2644 class = right_alias;
2646 const ClassInfo* right_info = add_joined_class( right_alias, class );
2650 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2654 jsonIteratorFree( search_itr );
2655 buffer_free( join_buf );
2657 jsonObjectFree( freeable_hash );
2660 osrfHash* links = right_info->links;
2661 const char* table = right_info->source_def;
2663 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2664 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2666 if (field && !fkey) {
2667 // Look up the corresponding join column in the IDL.
2668 // The link must be defined in the child table,
2669 // and point to the right parent table.
2670 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2671 const char* reltype = NULL;
2672 const char* other_class = NULL;
2673 reltype = osrfHashGet( idl_link, "reltype" );
2674 if( reltype && strcmp( reltype, "has_many" ) )
2675 other_class = osrfHashGet( idl_link, "class" );
2676 if( other_class && !strcmp( other_class, leftclass ) )
2677 fkey = osrfHashGet( idl_link, "key" );
2681 "%s: JOIN failed. No link defined from %s.%s to %s",
2687 buffer_free(join_buf);
2689 jsonObjectFree(freeable_hash);
2690 jsonIteratorFree(search_itr);
2694 } else if (!field && fkey) {
2695 // Look up the corresponding join column in the IDL.
2696 // The link must be defined in the child table,
2697 // and point to the right parent table.
2698 osrfHash* left_links = left_info->links;
2699 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2700 const char* reltype = NULL;
2701 const char* other_class = NULL;
2702 reltype = osrfHashGet( idl_link, "reltype" );
2703 if( reltype && strcmp( reltype, "has_many" ) )
2704 other_class = osrfHashGet( idl_link, "class" );
2705 if( other_class && !strcmp( other_class, class ) )
2706 field = osrfHashGet( idl_link, "key" );
2710 "%s: JOIN failed. No link defined from %s.%s to %s",
2716 buffer_free(join_buf);
2718 jsonObjectFree(freeable_hash);
2719 jsonIteratorFree(search_itr);
2723 } else if (!field && !fkey) {
2724 osrfHash* left_links = left_info->links;
2726 // For each link defined for the left class:
2727 // see if the link references the joined class
2728 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2729 osrfHash* curr_link = NULL;
2730 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2731 const char* other_class = osrfHashGet( curr_link, "class" );
2732 if( other_class && !strcmp( other_class, class ) ) {
2734 // In the IDL, the parent class doesn't know then names of the child
2735 // columns that are pointing to it, so don't use that end of the link
2736 const char* reltype = osrfHashGet( curr_link, "reltype" );
2737 if( reltype && strcmp( reltype, "has_many" ) ) {
2738 // Found a link between the classes
2739 fkey = osrfHashIteratorKey( itr );
2740 field = osrfHashGet( curr_link, "key" );
2745 osrfHashIteratorFree( itr );
2747 if (!field || !fkey) {
2748 // Do another such search, with the classes reversed
2750 // For each link defined for the joined class:
2751 // see if the link references the left class
2752 osrfHashIterator* itr = osrfNewHashIterator( links );
2753 osrfHash* curr_link = NULL;
2754 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2755 const char* other_class = osrfHashGet( curr_link, "class" );
2756 if( other_class && !strcmp( other_class, leftclass ) ) {
2758 // In the IDL, the parent class doesn't know then names of the child
2759 // columns that are pointing to it, so don't use that end of the link
2760 const char* reltype = osrfHashGet( curr_link, "reltype" );
2761 if( reltype && strcmp( reltype, "has_many" ) ) {
2762 // Found a link between the classes
2763 field = osrfHashIteratorKey( itr );
2764 fkey = osrfHashGet( curr_link, "key" );
2769 osrfHashIteratorFree( itr );
2772 if (!field || !fkey) {
2775 "%s: JOIN failed. No link defined between %s and %s",
2780 buffer_free(join_buf);
2782 jsonObjectFree(freeable_hash);
2783 jsonIteratorFree(search_itr);
2789 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2791 if ( !strcasecmp(type,"left") ) {
2792 buffer_add(join_buf, " LEFT JOIN");
2793 } else if ( !strcasecmp(type,"right") ) {
2794 buffer_add(join_buf, " RIGHT JOIN");
2795 } else if ( !strcasecmp(type,"full") ) {
2796 buffer_add(join_buf, " FULL JOIN");
2798 buffer_add(join_buf, " INNER JOIN");
2801 buffer_add(join_buf, " INNER JOIN");
2804 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2805 table, right_alias, right_alias, field, left_info->alias, fkey);
2807 // Add any other join conditions as specified by "filter"
2808 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2810 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2811 if ( filter_op && !strcasecmp("or",filter_op) ) {
2812 buffer_add( join_buf, " OR " );
2814 buffer_add( join_buf, " AND " );
2817 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2819 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2820 OSRF_BUFFER_ADD( join_buf, jpred );
2825 "%s: JOIN failed. Invalid conditional expression.",
2828 jsonIteratorFree( search_itr );
2829 buffer_free( join_buf );
2831 jsonObjectFree( freeable_hash );
2836 buffer_add(join_buf, " ) ");
2838 // Recursively add a nested join, if one is present
2839 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2841 char* jpred = searchJOIN( join_filter, right_info );
2843 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2844 OSRF_BUFFER_ADD( join_buf, jpred );
2847 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2848 jsonIteratorFree( search_itr );
2849 buffer_free( join_buf );
2851 jsonObjectFree( freeable_hash );
2858 jsonObjectFree(freeable_hash);
2859 jsonIteratorFree(search_itr);
2861 return buffer_release(join_buf);
2866 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2867 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2868 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2870 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2872 search_hash is the JSON expression of the conditions.
2873 meta is the class definition from the IDL, for the relevant table.
2874 opjoin_type indicates whether multiple conditions, if present, should be
2875 connected by AND or OR.
2876 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2877 to pass it to other functions -- and all they do with it is to use the session
2878 and request members to send error messages back to the client.
2882 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
2883 int opjoin_type, osrfMethodContext* ctx ) {
2887 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2890 class_info->class_def,
2895 growing_buffer* sql_buf = buffer_init(128);
2897 jsonObject* node = NULL;
2900 if ( search_hash->type == JSON_ARRAY ) {
2901 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2902 if( 0 == search_hash->size ) {
2905 "%s: Invalid predicate structure: empty JSON array",
2908 buffer_free( sql_buf );
2912 unsigned long i = 0;
2913 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
2917 if (opjoin_type == OR_OP_JOIN)
2918 buffer_add(sql_buf, " OR ");
2920 buffer_add(sql_buf, " AND ");
2923 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2925 buffer_free( sql_buf );
2929 buffer_fadd(sql_buf, "( %s )", subpred);
2933 } else if ( search_hash->type == JSON_HASH ) {
2934 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2935 jsonIterator* search_itr = jsonNewIterator( search_hash );
2936 if( !jsonIteratorHasNext( search_itr ) ) {
2939 "%s: Invalid predicate structure: empty JSON object",
2942 jsonIteratorFree( search_itr );
2943 buffer_free( sql_buf );
2947 while ( (node = jsonIteratorNext( search_itr )) ) {
2952 if (opjoin_type == OR_OP_JOIN)
2953 buffer_add(sql_buf, " OR ");
2955 buffer_add(sql_buf, " AND ");
2958 if ( '+' == search_itr->key[ 0 ] ) {
2960 // This plus sign prefixes a class name or other table alias;
2961 // make sure the table alias is in scope
2962 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2963 if( ! alias_info ) {
2966 "%s: Invalid table alias \"%s\" in WHERE clause",
2970 jsonIteratorFree( search_itr );
2971 buffer_free( sql_buf );
2975 if ( node->type == JSON_STRING ) {
2976 // It's the name of a column; make sure it belongs to the class
2977 const char* fieldname = jsonObjectGetString( node );
2978 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2981 "%s: Invalid column name \"%s\" in WHERE clause for table alias \"%s\"",
2986 jsonIteratorFree( search_itr );
2987 buffer_free( sql_buf );
2991 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
2993 // It's something more complicated
2994 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
2996 jsonIteratorFree( search_itr );
2997 buffer_free( sql_buf );
3001 buffer_fadd(sql_buf, "( %s )", subpred);
3004 } else if ( '-' == search_itr->key[ 0 ] ) {
3005 if ( !strcasecmp("-or",search_itr->key) ) {
3006 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3008 jsonIteratorFree( search_itr );
3009 buffer_free( sql_buf );
3013 buffer_fadd(sql_buf, "( %s )", subpred);
3015 } else if ( !strcasecmp("-and",search_itr->key) ) {
3016 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3018 jsonIteratorFree( search_itr );
3019 buffer_free( sql_buf );
3023 buffer_fadd(sql_buf, "( %s )", subpred);
3025 } else if ( !strcasecmp("-not",search_itr->key) ) {
3026 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3028 jsonIteratorFree( search_itr );
3029 buffer_free( sql_buf );
3033 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
3035 } else if ( !strcasecmp("-exists",search_itr->key) ) {
3036 char* subpred = buildQuery( ctx, node, SUBSELECT );
3038 jsonIteratorFree( search_itr );
3039 buffer_free( sql_buf );
3043 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
3045 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
3046 char* subpred = buildQuery( ctx, node, SUBSELECT );
3048 jsonIteratorFree( search_itr );
3049 buffer_free( sql_buf );
3053 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
3055 } else { // Invalid "minus" operator
3058 "%s: Invalid operator \"%s\" in WHERE clause",
3062 jsonIteratorFree( search_itr );
3063 buffer_free( sql_buf );
3069 const char* class = class_info->class_name;
3070 osrfHash* fields = class_info->fields;
3071 osrfHash* field = osrfHashGet( fields, search_itr->key );
3074 const char* table = class_info->source_def;
3077 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3080 table ? table : "?",
3083 jsonIteratorFree(search_itr);
3084 buffer_free(sql_buf);
3088 char* subpred = searchPredicate( class_info, field, node, ctx );
3090 buffer_free(sql_buf);
3091 jsonIteratorFree(search_itr);
3095 buffer_add( sql_buf, subpred );
3099 jsonIteratorFree(search_itr);
3102 // ERROR ... only hash and array allowed at this level
3103 char* predicate_string = jsonObjectToJSON( search_hash );
3106 "%s: Invalid predicate structure: %s",
3110 buffer_free(sql_buf);
3111 free(predicate_string);
3115 return buffer_release(sql_buf);
3118 /* Build a JSON_ARRAY of field names for a given table alias
3120 static jsonObject* defaultSelectList( const char* table_alias ) {
3125 ClassInfo* class_info = search_all_alias( table_alias );
3126 if( ! class_info ) {
3129 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3136 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3137 osrfHash* field_def = NULL;
3138 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3139 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3140 const char* field_name = osrfHashIteratorKey( field_itr );
3141 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3142 jsonObjectPush( array, jsonNewObject( field_name ) );
3145 osrfHashIteratorFree( field_itr );
3150 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3151 // The jsonObject must be a JSON_HASH with an single entry for "union",
3152 // "intersect", or "except". The data associated with this key must be an
3153 // array of hashes, each hash being a query.
3154 // Also allowed but currently ignored: entries for "order_by" and "alias".
3155 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3157 if( ! combo || combo->type != JSON_HASH )
3158 return NULL; // should be impossible; validated by caller
3160 const jsonObject* query_array = NULL; // array of subordinate queries
3161 const char* op = NULL; // name of operator, e.g. UNION
3162 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3163 int op_count = 0; // for detecting conflicting operators
3164 int excepting = 0; // boolean
3165 int all = 0; // boolean
3166 jsonObject* order_obj = NULL;
3168 // Identify the elements in the hash
3169 jsonIterator* query_itr = jsonNewIterator( combo );
3170 jsonObject* curr_obj = NULL;
3171 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3172 if( ! strcmp( "union", query_itr->key ) ) {
3175 query_array = curr_obj;
3176 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3179 query_array = curr_obj;
3180 } else if( ! strcmp( "except", query_itr->key ) ) {
3184 query_array = curr_obj;
3185 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3188 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3191 order_obj = curr_obj;
3192 } else if( ! strcmp( "alias", query_itr->key ) ) {
3193 if( curr_obj->type != JSON_STRING ) {
3194 jsonIteratorFree( query_itr );
3197 alias = jsonObjectGetString( curr_obj );
3198 } else if( ! strcmp( "all", query_itr->key ) ) {
3199 if( obj_is_true( curr_obj ) )
3203 osrfAppSessionStatus(
3205 OSRF_STATUS_INTERNALSERVERERROR,
3206 "osrfMethodException",
3208 "Malformed query; unexpected entry in query object"
3212 "%s: Unexpected entry for \"%s\" in%squery",
3217 jsonIteratorFree( query_itr );
3221 jsonIteratorFree( query_itr );
3223 // More sanity checks
3224 if( ! query_array ) {
3226 osrfAppSessionStatus(
3228 OSRF_STATUS_INTERNALSERVERERROR,
3229 "osrfMethodException",
3231 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3235 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3238 return NULL; // should be impossible...
3239 } else if( op_count > 1 ) {
3241 osrfAppSessionStatus(
3243 OSRF_STATUS_INTERNALSERVERERROR,
3244 "osrfMethodException",
3246 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3250 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3254 } if( query_array->type != JSON_ARRAY ) {
3256 osrfAppSessionStatus(
3258 OSRF_STATUS_INTERNALSERVERERROR,
3259 "osrfMethodException",
3261 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3265 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3268 json_type( query_array->type )
3271 } if( query_array->size < 2 ) {
3273 osrfAppSessionStatus(
3275 OSRF_STATUS_INTERNALSERVERERROR,
3276 "osrfMethodException",
3278 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3282 "%s:%srequires multiple queries as operands",
3287 } else if( excepting && query_array->size > 2 ) {
3289 osrfAppSessionStatus(
3291 OSRF_STATUS_INTERNALSERVERERROR,
3292 "osrfMethodException",
3294 "EXCEPT operator has too many queries as operands"
3298 "%s:EXCEPT operator has too many queries as operands",
3302 } else if( order_obj && ! alias ) {
3304 osrfAppSessionStatus(
3306 OSRF_STATUS_INTERNALSERVERERROR,
3307 "osrfMethodException",
3309 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3313 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3319 // So far so good. Now build the SQL.
3320 growing_buffer* sql = buffer_init( 256 );
3322 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3323 // Add a layer of parentheses
3324 if( flags & SUBCOMBO )
3325 OSRF_BUFFER_ADD( sql, "( " );
3327 // Traverse the query array. Each entry should be a hash.
3328 int first = 1; // boolean
3330 jsonObject* query = NULL;
3331 while((query = jsonObjectGetIndex( query_array, i++ ) )) {
3332 if( query->type != JSON_HASH ) {
3334 osrfAppSessionStatus(
3336 OSRF_STATUS_INTERNALSERVERERROR,
3337 "osrfMethodException",
3339 "Malformed query under UNION, INTERSECT or EXCEPT"
3343 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3346 json_type( query->type )
3355 OSRF_BUFFER_ADD( sql, op );
3357 OSRF_BUFFER_ADD( sql, "ALL " );
3360 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3364 "%s: Error building query under%s",
3372 OSRF_BUFFER_ADD( sql, query_str );
3375 if( flags & SUBCOMBO )
3376 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3378 if ( !(flags & SUBSELECT) )
3379 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3381 return buffer_release( sql );
3384 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3385 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3386 // or "except" to indicate the type of query.
3387 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3391 osrfAppSessionStatus(
3393 OSRF_STATUS_INTERNALSERVERERROR,
3394 "osrfMethodException",
3396 "Malformed query; no query object"
3398 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", MODULENAME );
3400 } else if( query->type != JSON_HASH ) {
3402 osrfAppSessionStatus(
3404 OSRF_STATUS_INTERNALSERVERERROR,
3405 "osrfMethodException",
3407 "Malformed query object"
3411 "%s: Query object is %s instead of JSON_HASH",
3413 json_type( query->type )
3418 // Determine what kind of query it purports to be, and dispatch accordingly.
3419 if( jsonObjectGetKey( query, "union" ) ||
3420 jsonObjectGetKey( query, "intersect" ) ||
3421 jsonObjectGetKey( query, "except" ) ) {
3422 return doCombo( ctx, query, flags );
3424 // It is presumably a SELECT query
3426 // Push a node onto the stack for the current query. Every level of
3427 // subquery gets its own QueryFrame on the Stack.
3430 // Build an SQL SELECT statement
3433 jsonObjectGetKey( query, "select" ),
3434 jsonObjectGetKey( query, "from" ),
3435 jsonObjectGetKey( query, "where" ),
3436 jsonObjectGetKey( query, "having" ),
3437 jsonObjectGetKey( query, "order_by" ),
3438 jsonObjectGetKey( query, "limit" ),
3439 jsonObjectGetKey( query, "offset" ),
3448 /* method context */ osrfMethodContext* ctx,
3450 /* SELECT */ jsonObject* selhash,
3451 /* FROM */ jsonObject* join_hash,
3452 /* WHERE */ jsonObject* search_hash,
3453 /* HAVING */ jsonObject* having_hash,
3454 /* ORDER BY */ jsonObject* order_hash,
3455 /* LIMIT */ jsonObject* limit,
3456 /* OFFSET */ jsonObject* offset,
3457 /* flags */ int flags
3459 const char* locale = osrf_message_get_last_locale();
3461 // general tmp objects
3462 const jsonObject* tmp_const;
3463 jsonObject* selclass = NULL;
3464 jsonObject* snode = NULL;
3465 jsonObject* onode = NULL;
3467 char* string = NULL;
3468 int from_function = 0;
3473 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3475 // punt if there's no FROM clause
3476 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3479 "%s: FROM clause is missing or empty",
3483 osrfAppSessionStatus(
3485 OSRF_STATUS_INTERNALSERVERERROR,
3486 "osrfMethodException",
3488 "FROM clause is missing or empty in JSON query"
3493 // the core search class
3494 const char* core_class = NULL;
3496 // get the core class -- the only key of the top level FROM clause, or a string
3497 if (join_hash->type == JSON_HASH) {
3498 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3499 snode = jsonIteratorNext( tmp_itr );
3501 // Populate the current QueryFrame with information
3502 // about the core class
3503 if( add_query_core( NULL, tmp_itr->key ) ) {
3505 osrfAppSessionStatus(
3507 OSRF_STATUS_INTERNALSERVERERROR,
3508 "osrfMethodException",
3510 "Unable to look up core class"
3514 core_class = curr_query->core.class_name;
3517 jsonObject* extra = jsonIteratorNext( tmp_itr );
3519 jsonIteratorFree( tmp_itr );
3522 // There shouldn't be more than one entry in join_hash
3526 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3530 osrfAppSessionStatus(
3532 OSRF_STATUS_INTERNALSERVERERROR,
3533 "osrfMethodException",
3535 "Malformed FROM clause in JSON query"
3537 return NULL; // Malformed join_hash; extra entry
3539 } else if (join_hash->type == JSON_ARRAY) {
3540 // We're selecting from a function, not from a table
3542 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
3545 } else if (join_hash->type == JSON_STRING) {
3546 // Populate the current QueryFrame with information
3547 // about the core class
3548 core_class = jsonObjectGetString( join_hash );
3550 if( add_query_core( NULL, core_class ) ) {
3552 osrfAppSessionStatus(
3554 OSRF_STATUS_INTERNALSERVERERROR,
3555 "osrfMethodException",
3557 "Unable to look up core class"
3565 "%s: FROM clause is unexpected JSON type: %s",
3567 json_type( join_hash->type )
3570 osrfAppSessionStatus(
3572 OSRF_STATUS_INTERNALSERVERERROR,
3573 "osrfMethodException",
3575 "Ill-formed FROM clause in JSON query"
3580 // Build the join clause, if any, while filling out the list
3581 // of joined classes in the current QueryFrame.
3582 char* join_clause = NULL;
3583 if( join_hash && ! from_function ) {
3585 join_clause = searchJOIN( join_hash, &curr_query->core );
3586 if( ! join_clause ) {
3588 osrfAppSessionStatus(
3590 OSRF_STATUS_INTERNALSERVERERROR,
3591 "osrfMethodException",
3593 "Unable to construct JOIN clause(s)"
3599 // For in case we don't get a select list
3600 jsonObject* defaultselhash = NULL;
3602 // if there is no select list, build a default select list ...
3603 if (!selhash && !from_function) {
3604 jsonObject* default_list = defaultSelectList( core_class );
3605 if( ! default_list ) {
3607 osrfAppSessionStatus(
3609 OSRF_STATUS_INTERNALSERVERERROR,
3610 "osrfMethodException",
3612 "Unable to build default SELECT clause in JSON query"
3614 free( join_clause );
3619 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3620 jsonObjectSetKey( selhash, core_class, default_list );
3623 // The SELECT clause can be encoded only by a hash
3624 if( !from_function && selhash->type != JSON_HASH ) {
3627 "%s: Expected JSON_HASH for SELECT clause; found %s",
3629 json_type( selhash->type )
3633 osrfAppSessionStatus(
3635 OSRF_STATUS_INTERNALSERVERERROR,
3636 "osrfMethodException",
3638 "Malformed SELECT clause in JSON query"
3640 free( join_clause );
3644 // If you see a null or wild card specifier for the core class, or an
3645 // empty array, replace it with a default SELECT list
3646 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3648 int default_needed = 0; // boolean
3649 if( JSON_STRING == tmp_const->type
3650 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3652 else if( JSON_NULL == tmp_const->type )
3655 if( default_needed ) {
3656 // Build a default SELECT list
3657 jsonObject* default_list = defaultSelectList( core_class );
3658 if( ! default_list ) {
3660 osrfAppSessionStatus(
3662 OSRF_STATUS_INTERNALSERVERERROR,
3663 "osrfMethodException",
3665 "Can't build default SELECT clause in JSON query"
3667 free( join_clause );
3672 jsonObjectSetKey( selhash, core_class, default_list );
3676 // temp buffers for the SELECT list and GROUP BY clause
3677 growing_buffer* select_buf = buffer_init(128);
3678 growing_buffer* group_buf = buffer_init(128);
3680 int aggregate_found = 0; // boolean
3682 // Build a select list
3683 if(from_function) // From a function we select everything
3684 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3687 // Build the SELECT list as SQL
3691 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3692 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3694 const char* cname = selclass_itr->key;
3696 // Make sure the target relation is in the FROM clause.
3698 // At this point join_hash is a step down from the join_hash we
3699 // received as a parameter. If the original was a JSON_STRING,
3700 // then json_hash is now NULL. If the original was a JSON_HASH,
3701 // then json_hash is now the first (and only) entry in it,
3702 // denoting the core class. We've already excluded the
3703 // possibility that the original was a JSON_ARRAY, because in
3704 // that case from_function would be non-NULL, and we wouldn't
3707 // If the current table alias isn't in scope, bail out
3708 ClassInfo* class_info = search_alias( cname );
3709 if( ! class_info ) {
3712 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3717 osrfAppSessionStatus(
3719 OSRF_STATUS_INTERNALSERVERERROR,
3720 "osrfMethodException",
3722 "Selected class not in FROM clause in JSON query"
3724 jsonIteratorFree( selclass_itr );
3725 buffer_free( select_buf );
3726 buffer_free( group_buf );
3727 if( defaultselhash ) jsonObjectFree( defaultselhash );
3728 free( join_clause );
3732 if( selclass->type != JSON_ARRAY ) {
3735 "%s: Malformed SELECT list for class \"%s\"; not an array",
3740 osrfAppSessionStatus(
3742 OSRF_STATUS_INTERNALSERVERERROR,
3743 "osrfMethodException",
3745 "Selected class not in FROM clause in JSON query"
3748 jsonIteratorFree( selclass_itr );
3749 buffer_free( select_buf );
3750 buffer_free( group_buf );
3751 if( defaultselhash ) jsonObjectFree( defaultselhash );
3752 free( join_clause );
3756 // Look up some attributes of the current class
3757 osrfHash* idlClass = class_info->class_def;
3758 osrfHash* class_field_set = class_info->fields;
3759 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3760 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3762 if( 0 == selclass->size ) {
3765 "%s: No columns selected from \"%s\"",
3771 // stitch together the column list for the current table alias...
3772 unsigned long field_idx = 0;
3773 jsonObject* selfield = NULL;
3774 while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3776 // If we need a separator comma, add one
3780 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3783 // if the field specification is a string, add it to the list
3784 if (selfield->type == JSON_STRING) {
3786 // Look up the field in the IDL
3787 const char* col_name = jsonObjectGetString( selfield );
3788 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3790 // No such field in current class
3793 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3799 osrfAppSessionStatus(
3801 OSRF_STATUS_INTERNALSERVERERROR,
3802 "osrfMethodException",
3804 "Selected column not defined in JSON query"
3806 jsonIteratorFree( selclass_itr );
3807 buffer_free( select_buf );
3808 buffer_free( group_buf );
3809 if( defaultselhash ) jsonObjectFree( defaultselhash );
3810 free( join_clause );
3812 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3813 // Virtual field not allowed
3816 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3822 osrfAppSessionStatus(
3824 OSRF_STATUS_INTERNALSERVERERROR,
3825 "osrfMethodException",
3827 "Selected column may not be virtual in JSON query"
3829 jsonIteratorFree( selclass_itr );
3830 buffer_free( select_buf );
3831 buffer_free( group_buf );
3832 if( defaultselhash ) jsonObjectFree( defaultselhash );
3833 free( join_clause );
3839 if (flags & DISABLE_I18N)
3842 i18n = osrfHashGet(field_def, "i18n");
3844 if( str_is_true( i18n ) ) {
3845 buffer_fadd( select_buf,
3846 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3847 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3849 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3852 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3855 // ... but it could be an object, in which case we check for a Field Transform
3856 } else if (selfield->type == JSON_HASH) {
3858 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3860 // Get the field definition from the IDL
3861 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3863 // No such field in current class
3866 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3872 osrfAppSessionStatus(
3874 OSRF_STATUS_INTERNALSERVERERROR,
3875 "osrfMethodException",
3877 "Selected column is not defined in JSON query"
3879 jsonIteratorFree( selclass_itr );
3880 buffer_free( select_buf );
3881 buffer_free( group_buf );
3882 if( defaultselhash ) jsonObjectFree( defaultselhash );
3883 free( join_clause );
3885 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3886 // No such field in current class
3889 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3895 osrfAppSessionStatus(
3897 OSRF_STATUS_INTERNALSERVERERROR,
3898 "osrfMethodException",
3900 "Selected column is virtual in JSON query"
3902 jsonIteratorFree( selclass_itr );
3903 buffer_free( select_buf );
3904 buffer_free( group_buf );
3905 if( defaultselhash ) jsonObjectFree( defaultselhash );
3906 free( join_clause );
3910 // Decide what to use as a column alias
3912 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3913 _alias = jsonObjectGetString( tmp_const );
3914 } else { // Use field name as the alias
3918 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3919 char* transform_str = searchFieldTransform(class_info->alias, field_def, selfield);
3920 if( transform_str ) {
3921 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3922 free(transform_str);
3925 osrfAppSessionStatus(
3927 OSRF_STATUS_INTERNALSERVERERROR,
3928 "osrfMethodException",
3930 "Unable to generate transform function in JSON query"
3932 jsonIteratorFree( selclass_itr );
3933 buffer_free( select_buf );
3934 buffer_free( group_buf );
3935 if( defaultselhash ) jsonObjectFree( defaultselhash );
3936 free( join_clause );
3943 if (flags & DISABLE_I18N)
3946 i18n = osrfHashGet(field_def, "i18n");
3948 if( str_is_true( i18n ) ) {
3949 buffer_fadd( select_buf,
3950 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3951 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3953 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3956 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3963 "%s: Selected item is unexpected JSON type: %s",
3965 json_type( selfield->type )
3968 osrfAppSessionStatus(
3970 OSRF_STATUS_INTERNALSERVERERROR,
3971 "osrfMethodException",
3973 "Ill-formed SELECT item in JSON query"
3975 jsonIteratorFree( selclass_itr );
3976 buffer_free( select_buf );
3977 buffer_free( group_buf );
3978 if( defaultselhash ) jsonObjectFree( defaultselhash );
3979 free( join_clause );
3983 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3984 if( obj_is_true( agg_obj ) )
3985 aggregate_found = 1;
3987 // Append a comma (except for the first one)
3988 // and add the column to a GROUP BY clause
3992 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3994 buffer_fadd(group_buf, " %d", sel_pos);
3998 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4000 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4001 if ( ! obj_is_true( aggregate_obj ) ) {
4005 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4008 buffer_fadd(group_buf, " %d", sel_pos);
4011 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4015 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4018 _column = searchFieldTransform(class_info->alias, field, selfield);
4019 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4020 OSRF_BUFFER_ADD(group_buf, _column);
4021 _column = searchFieldTransform(class_info->alias, field, selfield);
4028 } // end while -- iterating across SELECT columns
4030 } // end while -- iterating across classes
4032 jsonIteratorFree(selclass_itr);
4036 char* col_list = buffer_release(select_buf);
4038 // Make sure the SELECT list isn't empty. This can happen, for example,
4039 // if we try to build a default SELECT clause from a non-core table.
4042 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
4044 osrfAppSessionStatus(
4046 OSRF_STATUS_INTERNALSERVERERROR,
4047 "osrfMethodException",
4049 "SELECT list is empty"
4052 buffer_free( group_buf );
4053 if( defaultselhash ) jsonObjectFree( defaultselhash );
4054 free( join_clause );
4059 if (from_function) table = searchValueTransform(join_hash);
4060 else table = strdup( curr_query->core.source_def );
4064 osrfAppSessionStatus(
4066 OSRF_STATUS_INTERNALSERVERERROR,
4067 "osrfMethodException",
4069 "Unable to identify table for core class"
4072 buffer_free( group_buf );
4073 if( defaultselhash ) jsonObjectFree( defaultselhash );
4074 free( join_clause );
4078 // Put it all together
4079 growing_buffer* sql_buf = buffer_init(128);
4080 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4084 // Append the join clause, if any
4086 buffer_add(sql_buf, join_clause);
4090 char* order_by_list = NULL;
4091 char* having_buf = NULL;
4093 if (!from_function) {
4095 // Build a WHERE clause, if there is one
4096 if ( search_hash ) {
4097 buffer_add(sql_buf, " WHERE ");
4099 // and it's on the WHERE clause
4100 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4103 osrfAppSessionStatus(
4105 OSRF_STATUS_INTERNALSERVERERROR,
4106 "osrfMethodException",
4108 "Severe query error in WHERE predicate -- see error log for more details"
4111 buffer_free(group_buf);
4112 buffer_free(sql_buf);
4113 if (defaultselhash) jsonObjectFree(defaultselhash);
4117 buffer_add(sql_buf, pred);
4121 // Build a HAVING clause, if there is one
4122 if ( having_hash ) {
4124 // and it's on the the WHERE clause
4125 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4127 if( ! having_buf ) {
4129 osrfAppSessionStatus(
4131 OSRF_STATUS_INTERNALSERVERERROR,
4132 "osrfMethodException",
4134 "Severe query error in HAVING predicate -- see error log for more details"
4137 buffer_free(group_buf);
4138 buffer_free(sql_buf);
4139 if (defaultselhash) jsonObjectFree(defaultselhash);
4144 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4146 // Build an ORDER BY clause, if there is one
4147 if( NULL == order_hash )
4148 ; // No ORDER BY? do nothing
4149 else if( JSON_ARRAY == order_hash->type ) {
4150 // Array of field specifications, each specification being a
4151 // hash to define the class, field, and other details
4153 jsonObject* order_spec;
4154 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4156 if( JSON_HASH != order_spec->type ) {
4157 osrfLogError(OSRF_LOG_MARK,
4158 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4159 MODULENAME, json_type( order_spec->type ) );
4161 osrfAppSessionStatus(
4163 OSRF_STATUS_INTERNALSERVERERROR,
4164 "osrfMethodException",
4166 "Malformed ORDER BY clause -- see error log for more details"
4168 buffer_free( order_buf );
4170 buffer_free(group_buf);
4171 buffer_free(sql_buf);
4172 if (defaultselhash) jsonObjectFree(defaultselhash);
4176 const char* class_alias =
4177 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4179 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4182 OSRF_BUFFER_ADD(order_buf, ", ");
4184 order_buf = buffer_init(128);
4186 if( !field || !class_alias ) {
4187 osrfLogError(OSRF_LOG_MARK,
4188 "%s: Missing class or field name in field specification of ORDER BY clause",
4191 osrfAppSessionStatus(
4193 OSRF_STATUS_INTERNALSERVERERROR,
4194 "osrfMethodException",
4196 "Malformed ORDER BY clause -- see error log for more details"
4198 buffer_free( order_buf );
4200 buffer_free(group_buf);
4201 buffer_free(sql_buf);
4202 if (defaultselhash) jsonObjectFree(defaultselhash);
4206 ClassInfo* order_class_info = search_alias( class_alias );
4207 if( ! order_class_info ) {
4208 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4209 "not in FROM clause", MODULENAME, class_alias );
4211 osrfAppSessionStatus(
4213 OSRF_STATUS_INTERNALSERVERERROR,
4214 "osrfMethodException",
4216 "Invalid class referenced in ORDER BY clause -- see error log for more details"
4219 buffer_free(group_buf);
4220 buffer_free(sql_buf);
4221 if (defaultselhash) jsonObjectFree(defaultselhash);
4225 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4227 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4228 MODULENAME, class_alias, field );
4230 osrfAppSessionStatus(
4232 OSRF_STATUS_INTERNALSERVERERROR,
4233 "osrfMethodException",
4235 "Invalid field referenced in ORDER BY clause -- see error log for more details"
4238 buffer_free(group_buf);
4239 buffer_free(sql_buf);
4240 if (defaultselhash) jsonObjectFree(defaultselhash);
4242 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4243 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4244 MODULENAME, field );
4246 osrfAppSessionStatus(
4248 OSRF_STATUS_INTERNALSERVERERROR,
4249 "osrfMethodException",
4251 "Virtual field in ORDER BY clause -- see error log for more details"
4253 buffer_free( order_buf );
4255 buffer_free(group_buf);
4256 buffer_free(sql_buf);
4257 if (defaultselhash) jsonObjectFree(defaultselhash);
4261 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4262 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
4263 if( ! transform_str ) {
4265 osrfAppSessionStatus(
4267 OSRF_STATUS_INTERNALSERVERERROR,
4268 "osrfMethodException",
4270 "Severe query error in ORDER BY clause -- see error log for more details"
4272 buffer_free( order_buf );
4274 buffer_free(group_buf);
4275 buffer_free(sql_buf);
4276 if (defaultselhash) jsonObjectFree(defaultselhash);
4280 OSRF_BUFFER_ADD( order_buf, transform_str );
4281 free( transform_str );
4284 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4286 const char* direction =
4287 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4289 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4290 OSRF_BUFFER_ADD( order_buf, " DESC" );
4292 OSRF_BUFFER_ADD( order_buf, " ASC" );
4295 } else if( JSON_HASH == order_hash->type ) {
4296 // This hash is keyed on class alias. Each class has either
4297 // an array of field names or a hash keyed on field name.
4298 jsonIterator* class_itr = jsonNewIterator( order_hash );
4299 while ( (snode = jsonIteratorNext( class_itr )) ) {
4301 ClassInfo* order_class_info = search_alias( class_itr->key );
4302 if( ! order_class_info ) {
4303 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4304 MODULENAME, class_itr->key );
4306 osrfAppSessionStatus(
4308 OSRF_STATUS_INTERNALSERVERERROR,
4309 "osrfMethodException",
4311 "Invalid class referenced in ORDER BY clause -- see error log for more details"
4313 jsonIteratorFree( class_itr );
4314 buffer_free( order_buf );
4316 buffer_free(group_buf);
4317 buffer_free(sql_buf);
4318 if (defaultselhash) jsonObjectFree(defaultselhash);
4322 osrfHash* field_list_def = order_class_info->fields;
4324 if ( snode->type == JSON_HASH ) {
4326 // Hash is keyed on field names from the current class. For each field
4327 // there is another layer of hash to define the sorting details, if any,
4328 // or a string to indicate direction of sorting.
4329 jsonIterator* order_itr = jsonNewIterator( snode );
4330 while ( (onode = jsonIteratorNext( order_itr )) ) {
4332 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4334 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4335 MODULENAME, order_itr->key );
4337 osrfAppSessionStatus(
4339 OSRF_STATUS_INTERNALSERVERERROR,
4340 "osrfMethodException",
4342 "Invalid field in ORDER BY clause -- see error log for more details"
4344 jsonIteratorFree( order_itr );
4345 jsonIteratorFree( class_itr );
4346 buffer_free( order_buf );
4348 buffer_free(group_buf);
4349 buffer_free(sql_buf);
4350 if (defaultselhash) jsonObjectFree(defaultselhash);
4352 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4353 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4354 MODULENAME, order_itr->key );
4356 osrfAppSessionStatus(
4358 OSRF_STATUS_INTERNALSERVERERROR,
4359 "osrfMethodException",
4361 "Virtual field in ORDER BY clause -- see error log for more details"
4363 jsonIteratorFree( order_itr );
4364 jsonIteratorFree( class_itr );
4365 buffer_free( order_buf );
4367 buffer_free(group_buf);
4368 buffer_free(sql_buf);
4369 if (defaultselhash) jsonObjectFree(defaultselhash);
4373 const char* direction = NULL;
4374 if ( onode->type == JSON_HASH ) {
4375 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4376 string = searchFieldTransform(
4378 osrfHashGet( field_list_def, order_itr->key ),
4382 if( ctx ) osrfAppSessionStatus(
4384 OSRF_STATUS_INTERNALSERVERERROR,
4385 "osrfMethodException",
4387 "Severe query error in ORDER BY clause -- see error log for more details"
4389 jsonIteratorFree( order_itr );
4390 jsonIteratorFree( class_itr );
4392 buffer_free(group_buf);
4393 buffer_free(order_buf);
4394 buffer_free(sql_buf);
4395 if (defaultselhash) jsonObjectFree(defaultselhash);
4399 growing_buffer* field_buf = buffer_init(16);
4400 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4401 string = buffer_release(field_buf);
4404 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4405 const char* dir = jsonObjectGetString(tmp_const);
4406 if (!strncasecmp(dir, "d", 1)) {
4407 direction = " DESC";
4413 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4414 osrfLogError( OSRF_LOG_MARK,
4415 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4416 MODULENAME, json_type( onode->type ) );
4418 osrfAppSessionStatus(
4420 OSRF_STATUS_INTERNALSERVERERROR,
4421 "osrfMethodException",
4423 "Malformed ORDER BY clause -- see error log for more details"
4425 jsonIteratorFree( order_itr );
4426 jsonIteratorFree( class_itr );
4428 buffer_free(group_buf);
4429 buffer_free(order_buf);
4430 buffer_free(sql_buf);
4431 if (defaultselhash) jsonObjectFree(defaultselhash);
4435 string = strdup(order_itr->key);
4436 const char* dir = jsonObjectGetString(onode);
4437 if (!strncasecmp(dir, "d", 1)) {
4438 direction = " DESC";
4445 OSRF_BUFFER_ADD(order_buf, ", ");
4447 order_buf = buffer_init(128);
4449 OSRF_BUFFER_ADD(order_buf, string);
4453 OSRF_BUFFER_ADD(order_buf, direction);
4457 jsonIteratorFree(order_itr);
4459 } else if ( snode->type == JSON_ARRAY ) {
4461 // Array is a list of fields from the current class
4462 unsigned long order_idx = 0;
4463 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4465 const char* _f = jsonObjectGetString( onode );
4467 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4469 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4472 osrfAppSessionStatus(
4474 OSRF_STATUS_INTERNALSERVERERROR,
4475 "osrfMethodException",
4477 "Invalid field in ORDER BY clause -- see error log for more details"
4479 jsonIteratorFree( class_itr );
4480 buffer_free( order_buf );
4482 buffer_free(group_buf);
4483 buffer_free(sql_buf);
4484 if (defaultselhash) jsonObjectFree(defaultselhash);
4486 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4487 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4490 osrfAppSessionStatus(
4492 OSRF_STATUS_INTERNALSERVERERROR,
4493 "osrfMethodException",
4495 "Virtual field in ORDER BY clause -- see error log for more details"
4497 jsonIteratorFree( class_itr );
4498 buffer_free( order_buf );
4500 buffer_free(group_buf);
4501 buffer_free(sql_buf);
4502 if (defaultselhash) jsonObjectFree(defaultselhash);
4507 OSRF_BUFFER_ADD(order_buf, ", ");
4509 order_buf = buffer_init(128);
4511 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
4515 // IT'S THE OOOOOOOOOOOLD STYLE!
4517 osrfLogError(OSRF_LOG_MARK,
4518 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
4520 osrfAppSessionStatus(
4522 OSRF_STATUS_INTERNALSERVERERROR,
4523 "osrfMethodException",
4525 "Severe query error -- see error log for more details"
4530 buffer_free(group_buf);
4531 buffer_free(order_buf);
4532 buffer_free(sql_buf);
4533 if (defaultselhash) jsonObjectFree(defaultselhash);
4534 jsonIteratorFree(class_itr);
4538 jsonIteratorFree( class_itr );
4540 osrfLogError(OSRF_LOG_MARK,
4541 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4542 MODULENAME, json_type( order_hash->type ) );
4544 osrfAppSessionStatus(
4546 OSRF_STATUS_INTERNALSERVERERROR,
4547 "osrfMethodException",
4549 "Malformed ORDER BY clause -- see error log for more details"
4551 buffer_free( order_buf );
4553 buffer_free(group_buf);
4554 buffer_free(sql_buf);
4555 if (defaultselhash) jsonObjectFree(defaultselhash);
4560 order_by_list = buffer_release( order_buf );
4564 string = buffer_release(group_buf);
4566 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4567 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4568 OSRF_BUFFER_ADD( sql_buf, string );
4573 if( having_buf && *having_buf ) {
4574 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4575 OSRF_BUFFER_ADD( sql_buf, having_buf );
4579 if( order_by_list ) {
4581 if ( *order_by_list ) {
4582 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4583 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4586 free( order_by_list );
4590 const char* str = jsonObjectGetString(limit);
4591 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
4595 const char* str = jsonObjectGetString(offset);
4596 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
4599 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4601 if (defaultselhash) jsonObjectFree(defaultselhash);
4603 return buffer_release(sql_buf);
4605 } // end of SELECT()
4607 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4609 const char* locale = osrf_message_get_last_locale();
4611 osrfHash* fields = osrfHashGet(meta, "fields");
4612 char* core_class = osrfHashGet(meta, "classname");
4614 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4616 jsonObject* node = NULL;
4617 jsonObject* snode = NULL;
4618 jsonObject* onode = NULL;
4619 const jsonObject* _tmp = NULL;
4620 jsonObject* selhash = NULL;
4621 jsonObject* defaultselhash = NULL;
4623 growing_buffer* sql_buf = buffer_init(128);
4624 growing_buffer* select_buf = buffer_init(128);
4626 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4627 defaultselhash = jsonNewObjectType(JSON_HASH);
4628 selhash = defaultselhash;
4631 // If there's no SELECT list for the core class, build one
4632 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4633 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4635 // Add every non-virtual field to the field list
4636 osrfHash* field_def = NULL;
4637 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4638 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4639 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4640 const char* field = osrfHashIteratorKey( field_itr );
4641 jsonObjectPush( field_list, jsonNewObject( field ) );
4644 osrfHashIteratorFree( field_itr );
4645 jsonObjectSetKey( selhash, core_class, field_list );
4649 jsonIterator* class_itr = jsonNewIterator( selhash );
4650 while ( (snode = jsonIteratorNext( class_itr )) ) {
4652 const char* cname = class_itr->key;
4653 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4654 if (!idlClass) continue;
4656 if (strcmp(core_class,class_itr->key)) {
4657 if (!join_hash) continue;
4659 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4661 jsonObjectFree(found);
4665 jsonObjectFree(found);
4668 jsonIterator* select_itr = jsonNewIterator( snode );
4669 while ( (node = jsonIteratorNext( select_itr )) ) {
4670 const char* item_str = jsonObjectGetString( node );
4671 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4672 char* fname = osrfHashGet(field, "name");
4674 if (!field) continue;
4679 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
4684 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4685 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4688 i18n = osrfHashGet(field, "i18n");
4690 if( str_is_true( i18n ) ) {
4691 char* pkey = osrfHashGet(idlClass, "primarykey");
4692 char* tname = osrfHashGet(idlClass, "tablename");
4694 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);
4696 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4699 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4703 jsonIteratorFree(select_itr);
4706 jsonIteratorFree(class_itr);
4708 char* col_list = buffer_release(select_buf);
4709 char* table = getRelation(meta);
4711 table = strdup( "(null)" );
4713 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4717 // Clear the query stack (as a fail-safe precaution against possible
4718 // leftover garbage); then push the first query frame onto the stack.
4719 clear_query_stack();
4721 if( add_query_core( NULL, core_class ) ) {
4723 osrfAppSessionStatus(
4725 OSRF_STATUS_INTERNALSERVERERROR,
4726 "osrfMethodException",
4728 "Unable to build query frame for core class"
4734 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4735 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
4736 OSRF_BUFFER_ADD(sql_buf, join_clause);
4740 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4741 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4743 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4745 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4747 osrfAppSessionStatus(
4749 OSRF_STATUS_INTERNALSERVERERROR,
4750 "osrfMethodException",
4752 "Severe query error -- see error log for more details"
4754 buffer_free(sql_buf);
4755 if(defaultselhash) jsonObjectFree(defaultselhash);
4756 clear_query_stack();
4759 buffer_add(sql_buf, pred);
4764 char* string = NULL;
4765 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4767 growing_buffer* order_buf = buffer_init(128);
4770 jsonIterator* class_itr = jsonNewIterator( _tmp );
4771 while ( (snode = jsonIteratorNext( class_itr )) ) {
4773 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4776 if ( snode->type == JSON_HASH ) {
4778 jsonIterator* order_itr = jsonNewIterator( snode );
4779 while ( (onode = jsonIteratorNext( order_itr )) ) {
4781 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4782 class_itr->key, order_itr->key );
4786 char* direction = NULL;
4787 if ( onode->type == JSON_HASH ) {
4788 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4789 string = searchFieldTransform( class_itr->key, field_def, onode );
4791 osrfAppSessionStatus(
4793 OSRF_STATUS_INTERNALSERVERERROR,
4794 "osrfMethodException",
4796 "Severe query error in ORDER BY clause -- see error log for more details"
4798 jsonIteratorFree( order_itr );
4799 jsonIteratorFree( class_itr );
4800 buffer_free( order_buf );
4801 buffer_free( sql_buf );
4802 if( defaultselhash ) jsonObjectFree( defaultselhash );
4803 clear_query_stack();
4807 growing_buffer* field_buf = buffer_init(16);
4808 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4809 string = buffer_release(field_buf);
4812 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4813 const char* dir = jsonObjectGetString(_tmp);
4814 if (!strncasecmp(dir, "d", 1)) {
4815 direction = " DESC";
4822 string = strdup(order_itr->key);
4823 const char* dir = jsonObjectGetString(onode);
4824 if (!strncasecmp(dir, "d", 1)) {
4825 direction = " DESC";
4834 buffer_add(order_buf, ", ");
4837 buffer_add(order_buf, string);
4841 buffer_add(order_buf, direction);
4846 jsonIteratorFree(order_itr);
4849 const char* str = jsonObjectGetString(snode);
4850 buffer_add(order_buf, str);
4856 jsonIteratorFree(class_itr);
4858 string = buffer_release(order_buf);
4861 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4862 OSRF_BUFFER_ADD( sql_buf, string );
4868 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4869 const char* str = jsonObjectGetString(_tmp);
4877 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4879 const char* str = jsonObjectGetString(_tmp);
4888 if (defaultselhash) jsonObjectFree(defaultselhash);
4889 clear_query_stack();
4891 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4892 return buffer_release(sql_buf);
4895 int doJSONSearch ( osrfMethodContext* ctx ) {
4896 if(osrfMethodVerifyContext( ctx )) {
4897 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4901 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
4906 dbhandle = writehandle;
4908 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4912 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4913 flags |= SELECT_DISTINCT;
4915 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4916 flags |= DISABLE_I18N;
4918 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4919 clear_query_stack(); // a possibly needless precaution
4920 char* sql = buildQuery( ctx, hash, flags );
4921 clear_query_stack();
4928 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4929 dbi_result result = dbi_conn_query(dbhandle, sql);
4932 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4934 if (dbi_result_first_row(result)) {
4935 /* JSONify the result */
4936 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4939 jsonObject* return_val = oilsMakeJSONFromResult( result );
4940 osrfAppRespond( ctx, return_val );
4941 jsonObjectFree( return_val );
4942 } while (dbi_result_next_row(result));
4945 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4948 osrfAppRespondComplete( ctx, NULL );
4950 /* clean up the query */
4951 dbi_result_free(result);
4955 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4956 osrfAppSessionStatus(
4958 OSRF_STATUS_INTERNALSERVERERROR,
4959 "osrfMethodException",
4961 "Severe query error -- see error log for more details"
4969 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4970 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4973 dbhandle = writehandle;
4975 osrfHash* links = osrfHashGet(meta, "links");
4976 osrfHash* fields = osrfHashGet(meta, "fields");
4977 char* core_class = osrfHashGet(meta, "classname");
4978 char* pkey = osrfHashGet(meta, "primarykey");
4980 const jsonObject* _tmp;
4983 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4985 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4990 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4992 dbi_result result = dbi_conn_query(dbhandle, sql);
4993 if( NULL == result ) {
4994 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4995 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4996 osrfAppSessionStatus(
4998 OSRF_STATUS_INTERNALSERVERERROR,
4999 "osrfMethodException",
5001 "Severe query error -- see error log for more details"
5008 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
5011 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
5012 osrfHash* dedup = osrfNewHash();
5014 if (dbi_result_first_row(result)) {
5015 /* JSONify the result */
5016 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
5018 obj = oilsMakeFieldmapperFromResult( result, meta );
5019 char* pkey_val = oilsFMGetString( obj, pkey );
5020 if ( osrfHashGet( dedup, pkey_val ) ) {
5021 jsonObjectFree(obj);
5024 osrfHashSet( dedup, pkey_val, pkey_val );
5025 jsonObjectPush(res_list, obj);
5027 } while (dbi_result_next_row(result));
5029 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
5033 osrfHashFree(dedup);
5034 /* clean up the query */
5035 dbi_result_free(result);
5038 if (res_list->size && query_hash) {
5039 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5041 int x = (int)jsonObjectGetNumber(_tmp);
5042 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
5044 const jsonObject* temp_blob;
5045 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
5047 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5048 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5050 osrfStringArray* link_fields = NULL;
5053 if (flesh_fields->size == 1) {
5054 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
5055 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
5060 link_fields = osrfNewStringArray(1);
5061 jsonIterator* _i = jsonNewIterator( flesh_fields );
5062 while ((_f = jsonIteratorNext( _i ))) {
5063 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5065 jsonIteratorFree(_i);
5070 unsigned long res_idx = 0;
5071 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5074 const char* link_field;
5076 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5078 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
5080 osrfHash* kid_link = osrfHashGet(links, link_field);
5081 if (!kid_link) continue;
5083 osrfHash* field = osrfHashGet(fields, link_field);
5084 if (!field) continue;
5086 osrfHash* value_field = field;
5088 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
5089 if (!kid_idl) continue;
5091 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
5092 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
5095 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
5096 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
5099 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5101 if (link_map->size > 0) {
5102 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
5105 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5110 osrfHashGet(kid_link, "class"),
5117 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5118 osrfHashGet(kid_link, "field"),
5119 osrfHashGet(kid_link, "class"),
5120 osrfHashGet(kid_link, "key"),
5121 osrfHashGet(kid_link, "reltype")
5124 const char* search_key = jsonObjectGetString(
5127 atoi( osrfHashGet(value_field, "array_position") )
5132 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
5136 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
5138 // construct WHERE clause
5139 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
5142 osrfHashGet(kid_link, "key"),
5143 jsonNewObject( search_key )
5146 // construct the rest of the query
5147 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
5148 jsonObjectSetKey( rest_of_query, "flesh",
5149 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
5153 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
5155 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
5156 jsonObjectSetKey( rest_of_query, "order_by",
5157 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
5161 if (jsonObjectGetKeyConst(query_hash, "select")) {
5162 jsonObjectSetKey( rest_of_query, "select",
5163 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
5167 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5168 where_clause, rest_of_query, err);
5170 jsonObjectFree( where_clause );
5171 jsonObjectFree( rest_of_query );
5174 osrfStringArrayFree(link_fields);
5175 jsonObjectFree(res_list);
5176 jsonObjectFree(flesh_blob);
5180 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
5182 jsonObject* X = NULL;
5183 if ( link_map->size > 0 && kids->size > 0 ) {
5185 kids = jsonNewObjectType(JSON_ARRAY);
5187 jsonObject* _k_node;
5188 unsigned long res_idx = 0;
5189 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5195 (unsigned long)atoi(
5201 osrfHashGet(kid_link, "class")
5205 osrfStringArrayGetString( link_map, 0 )
5213 } // end while loop traversing X
5216 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
5217 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
5220 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5221 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
5225 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
5226 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
5229 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5230 jsonObjectClone( kids )
5235 jsonObjectFree(kids);
5239 jsonObjectFree( kids );
5241 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
5242 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
5245 } // end while loop traversing res_list
5246 jsonObjectFree( flesh_blob );
5247 osrfStringArrayFree(link_fields);
5256 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
5258 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5260 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
5262 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
5265 if (!verifyObjectClass(ctx, target)) {
5270 if( getXactId( ctx ) == NULL ) {
5271 osrfAppSessionStatus(
5273 OSRF_STATUS_BADREQUEST,
5274 "osrfMethodException",
5276 "No active transaction -- required for UPDATE"
5282 // The following test is harmless but redundant. If a class is
5283 // readonly, we don't register an update method for it.
5284 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5285 osrfAppSessionStatus(
5287 OSRF_STATUS_BADREQUEST,
5288 "osrfMethodException",
5290 "Cannot UPDATE readonly class"
5296 dbhandle = writehandle;
5297 const char* trans_id = getXactId( ctx );
5299 // Set the last_xact_id
5300 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5302 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5303 trans_id, target->classname, index);
5304 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
5307 char* pkey = osrfHashGet(meta, "primarykey");
5308 osrfHash* fields = osrfHashGet(meta, "fields");
5310 char* id = oilsFMGetString( target, pkey );
5314 "%s updating %s object with %s = %s",
5316 osrfHashGet(meta, "fieldmapper"),
5321 growing_buffer* sql = buffer_init(128);
5322 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
5325 osrfHash* field_def = NULL;
5326 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5327 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
5329 // Skip virtual fields, and the primary key
5330 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5333 const char* field_name = osrfHashIteratorKey( field_itr );
5334 if( ! strcmp( field_name, pkey ) )
5337 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5339 int value_is_numeric = 0; // boolean
5341 if (field_object && field_object->classname) {
5342 value = oilsFMGetString(
5344 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
5346 } else if( field_object && JSON_BOOL == field_object->type ) {
5347 if( jsonBoolIsTrue( field_object ) )
5348 value = strdup( "t" );
5350 value = strdup( "f" );
5352 value = jsonObjectToSimpleString( field_object );
5353 if( field_object && JSON_NUMBER == field_object->type )
5354 value_is_numeric = 1;
5357 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5358 osrfHashGet(meta, "fieldmapper"), field_name, value);
5360 if (!field_object || field_object->type == JSON_NULL) {
5361 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
5362 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5363 if (first) first = 0;
5364 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5365 buffer_fadd( sql, " %s = NULL", field_name );
5368 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5369 if (first) first = 0;
5370 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5372 const char* numtype = get_datatype( field_def );
5373 if ( !strncmp( numtype, "INT", 3 ) ) {
5374 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
5375 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
5376 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
5378 // Must really be intended as a string, so quote it
5379 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5380 buffer_fadd( sql, " %s = %s", field_name, value );
5382 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5383 osrfAppSessionStatus(
5385 OSRF_STATUS_INTERNALSERVERERROR,
5386 "osrfMethodException",
5388 "Error quoting string -- please see the error log for more details"
5392 osrfHashIteratorFree( field_itr );
5399 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5402 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5403 if (first) first = 0;
5404 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5405 buffer_fadd( sql, " %s = %s", field_name, value );
5408 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5409 osrfAppSessionStatus(
5411 OSRF_STATUS_INTERNALSERVERERROR,
5412 "osrfMethodException",
5414 "Error quoting string -- please see the error log for more details"
5418 osrfHashIteratorFree( field_itr );
5429 osrfHashIteratorFree( field_itr );
5431 jsonObject* obj = jsonNewObject(id);
5433 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5434 dbi_conn_quote_string(dbhandle, &id);
5436 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5438 char* query = buffer_release(sql);
5439 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
5441 dbi_result result = dbi_conn_query(dbhandle, query);
5445 jsonObjectFree(obj);
5446 obj = jsonNewObject(NULL);
5449 "%s ERROR updating %s object with %s = %s",
5451 osrfHashGet(meta, "fieldmapper"),
5462 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
5464 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5466 if( getXactId( ctx ) == NULL ) {
5467 osrfAppSessionStatus(
5469 OSRF_STATUS_BADREQUEST,
5470 "osrfMethodException",
5472 "No active transaction -- required for DELETE"
5478 // The following test is harmless but redundant. If a class is
5479 // readonly, we don't register a delete method for it.
5480 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5481 osrfAppSessionStatus(
5483 OSRF_STATUS_BADREQUEST,
5484 "osrfMethodException",
5486 "Cannot DELETE readonly class"
5492 dbhandle = writehandle;
5496 char* pkey = osrfHashGet(meta, "primarykey");
5504 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
5505 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5510 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5513 if (!verifyObjectPCRUD( ctx, NULL )) {
5518 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
5523 "%s deleting %s object with %s = %s",
5525 osrfHashGet(meta, "fieldmapper"),
5530 obj = jsonNewObject(id);
5532 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5533 dbi_conn_quote_string(writehandle, &id);
5535 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
5538 jsonObjectFree(obj);
5539 obj = jsonNewObject(NULL);
5542 "%s ERROR deleting %s object with %s = %s",
5544 osrfHashGet(meta, "fieldmapper"),
5557 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5558 if(!(result && meta)) return jsonNULL;
5560 jsonObject* object = jsonNewObject(NULL);
5561 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
5563 osrfHash* fields = osrfHashGet(meta, "fields");
5565 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
5569 char dt_string[256];
5573 int columnIndex = 1;
5575 unsigned short type;
5576 const char* columnName;
5578 /* cycle through the column list */
5579 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5581 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5583 fmIndex = -1; // reset the position
5585 /* determine the field type and storage attributes */
5586 type = dbi_result_get_field_type_idx(result, columnIndex);
5587 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5589 /* fetch the fieldmapper index */
5590 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
5592 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
5595 const char* pos = (char*)osrfHashGet(_f, "array_position");
5596 if ( !pos ) continue;
5598 fmIndex = atoi( pos );
5599 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
5604 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5605 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
5610 case DBI_TYPE_INTEGER :
5612 if( attr & DBI_INTEGER_SIZE8 )
5613 jsonObjectSetIndex( object, fmIndex,
5614 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
5616 jsonObjectSetIndex( object, fmIndex,
5617 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
5621 case DBI_TYPE_DECIMAL :
5622 jsonObjectSetIndex( object, fmIndex,
5623 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
5626 case DBI_TYPE_STRING :
5632 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
5637 case DBI_TYPE_DATETIME :
5639 memset(dt_string, '\0', sizeof(dt_string));
5640 memset(&gmdt, '\0', sizeof(gmdt));
5642 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5645 if (!(attr & DBI_DATETIME_DATE)) {
5646 gmtime_r( &_tmp_dt, &gmdt );
5647 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5648 } else if (!(attr & DBI_DATETIME_TIME)) {
5649 localtime_r( &_tmp_dt, &gmdt );
5650 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5652 localtime_r( &_tmp_dt, &gmdt );
5653 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5656 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
5660 case DBI_TYPE_BINARY :
5661 osrfLogError( OSRF_LOG_MARK,
5662 "Can't do binary at column %s : index %d", columnName, columnIndex);
5671 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5672 if(!result) return jsonNULL;
5674 jsonObject* object = jsonNewObject(NULL);
5677 char dt_string[256];
5681 int columnIndex = 1;
5683 unsigned short type;
5684 const char* columnName;
5686 /* cycle through the column list */
5687 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5689 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5691 fmIndex = -1; // reset the position
5693 /* determine the field type and storage attributes */
5694 type = dbi_result_get_field_type_idx(result, columnIndex);
5695 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5697 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5698 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
5703 case DBI_TYPE_INTEGER :
5705 if( attr & DBI_INTEGER_SIZE8 )
5706 jsonObjectSetKey( object, columnName,
5707 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
5709 jsonObjectSetKey( object, columnName,
5710 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
5713 case DBI_TYPE_DECIMAL :
5714 jsonObjectSetKey( object, columnName,
5715 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
5718 case DBI_TYPE_STRING :
5719 jsonObjectSetKey( object, columnName,
5720 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
5723 case DBI_TYPE_DATETIME :
5725 memset(dt_string, '\0', sizeof(dt_string));
5726 memset(&gmdt, '\0', sizeof(gmdt));
5728 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5731 if (!(attr & DBI_DATETIME_DATE)) {
5732 gmtime_r( &_tmp_dt, &gmdt );
5733 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5734 } else if (!(attr & DBI_DATETIME_TIME)) {
5735 localtime_r( &_tmp_dt, &gmdt );
5736 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5738 localtime_r( &_tmp_dt, &gmdt );
5739 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5742 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5745 case DBI_TYPE_BINARY :
5746 osrfLogError( OSRF_LOG_MARK,
5747 "Can't do binary at column %s : index %d", columnName, columnIndex );
5751 } // end while loop traversing result
5756 // Interpret a string as true or false
5757 static int str_is_true( const char* str ) {
5758 if( NULL == str || strcasecmp( str, "true" ) )
5764 // Interpret a jsonObject as true or false
5765 static int obj_is_true( const jsonObject* obj ) {
5768 else switch( obj->type )
5776 if( strcasecmp( obj->value.s, "true" ) )
5780 case JSON_NUMBER : // Support 1/0 for perl's sake
5781 if( jsonObjectGetNumber( obj ) == 1.0 )
5790 // Translate a numeric code into a text string identifying a type of
5791 // jsonObject. To be used for building error messages.
5792 static const char* json_type( int code ) {
5798 return "JSON_ARRAY";
5800 return "JSON_STRING";
5802 return "JSON_NUMBER";
5808 return "(unrecognized)";
5812 // Extract the "primitive" attribute from an IDL field definition.
5813 // If we haven't initialized the app, then we must be running in
5814 // some kind of testbed. In that case, default to "string".
5815 static const char* get_primitive( osrfHash* field ) {
5816 const char* s = osrfHashGet( field, "primitive" );
5818 if( child_initialized )
5821 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5823 osrfHashGet( field, "name" )
5831 // Extract the "datatype" attribute from an IDL field definition.
5832 // If we haven't initialized the app, then we must be running in
5833 // some kind of testbed. In that case, default to to NUMERIC,
5834 // since we look at the datatype only for numbers.
5835 static const char* get_datatype( osrfHash* field ) {
5836 const char* s = osrfHashGet( field, "datatype" );
5838 if( child_initialized )
5841 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5843 osrfHashGet( field, "name" )
5852 If the input string is potentially a valid SQL identifier, return 1.
5855 Purpose: to prevent certain kinds of SQL injection. To that end we
5856 don't necessarily need to follow all the rules exactly, such as requiring
5857 that the first character not be a digit.
5859 We allow leading and trailing white space. In between, we do not allow
5860 punctuation (except for underscores and dollar signs), control
5861 characters, or embedded white space.
5863 More pedantically we should allow quoted identifiers containing arbitrary
5864 characters, but for the foreseeable future such quoted identifiers are not
5865 likely to be an issue.
5867 static int is_identifier( const char* s) {
5871 // Skip leading white space
5872 while( isspace( (unsigned char) *s ) )
5876 return 0; // Nothing but white space? Not okay.
5878 // Check each character until we reach white space or
5879 // end-of-string. Letters, digits, underscores, and
5880 // dollar signs are okay. With the exception of periods
5881 // (as in schema.identifier), control characters and other
5882 // punctuation characters are not okay. Anything else
5883 // is okay -- it could for example be part of a multibyte
5884 // UTF8 character such as a letter with diacritical marks,
5885 // and those are allowed.
5887 if( isalnum( (unsigned char) *s )
5891 ; // Fine; keep going
5892 else if( ispunct( (unsigned char) *s )
5893 || iscntrl( (unsigned char) *s ) )
5896 } while( *s && ! isspace( (unsigned char) *s ) );
5898 // If we found any white space in the above loop,
5899 // the rest had better be all white space.
5901 while( isspace( (unsigned char) *s ) )
5905 return 0; // White space was embedded within non-white space
5911 Determine whether to accept a character string as a comparison operator.
5912 Return 1 if it's good, or 0 if it's bad.
5914 We don't validate it for real. We just make sure that it doesn't contain
5915 any semicolons or white space (with special exceptions for a few specific
5916 operators). The idea is to block certain kinds of SQL injection. If it
5917 has no semicolons or white space but it's still not a valid operator, then
5918 the database will complain.
5920 Another approach would be to compare the string against a short list of
5921 approved operators. We don't do that because we want to allow custom
5922 operators like ">100*", which would be difficult or impossible to
5923 express otherwise in a JSON query.
5925 static int is_good_operator( const char* op ) {
5926 if( !op ) return 0; // Sanity check
5930 if( isspace( (unsigned char) *s ) ) {
5931 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
5932 // and IS NOT DISTINCT FROM.
5933 if( !strcasecmp( op, "similar to" ) )
5935 else if( !strcasecmp( op, "is distinct from" ) )
5937 else if( !strcasecmp( op, "is not distinct from" ) )
5942 else if( ';' == *s )
5949 /* ----------------------------------------------------------------------------------
5950 The following machinery supports a stack of query frames for use by SELECT().
5952 A query frame caches information about one level of a SELECT query. When we enter
5953 a subquery, we push another query frame onto the stack, and pop it off when we leave.
5955 The query frame stores information about the core class, and about any joined classes
5958 The main purpose is to map table aliases to classes and tables, so that a query can
5959 join to the same table more than once. A secondary goal is to reduce the number of
5960 lookups in the IDL by caching the results.
5961 ----------------------------------------------------------------------------------*/
5963 #define STATIC_CLASS_INFO_COUNT 3
5965 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
5967 /* ---------------------------------------------------------------------------
5968 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5970 ---------------------------------------------------------------------------*/
5971 static ClassInfo* allocate_class_info( void ) {
5972 // In order to reduce the number of mallocs and frees, we return a static
5973 // instance of ClassInfo, if we can find one that we're not already using.
5974 // We rely on the fact that the compiler will implicitly initialize the
5975 // static instances so that in_use == 0.
5978 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5979 if( ! static_class_info[ i ].in_use ) {
5980 static_class_info[ i ].in_use = 1;
5981 return static_class_info + i;
5985 // The static ones are all in use. Malloc one.
5987 return safe_malloc( sizeof( ClassInfo ) );
5990 /* --------------------------------------------------------------------------
5991 Free any malloc'd memory owned by a ClassInfo; return it to a pristine state
5992 ---------------------------------------------------------------------------*/
5993 static void clear_class_info( ClassInfo* info ) {
5998 // Free any malloc'd strings
6000 if( info->alias != info->alias_store )
6001 free( info->alias );
6003 if( info->class_name != info->class_name_store )
6004 free( info->class_name );
6006 free( info->source_def );
6008 info->alias = info->class_name = info->source_def = NULL;
6012 /* --------------------------------------------------------------------------
6013 Deallocate a ClassInfo and everything it owns
6014 ---------------------------------------------------------------------------*/
6015 static void free_class_info( ClassInfo* info ) {
6020 clear_class_info( info );
6022 // If it's one of the static instances, just mark it as not in use
6025 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6026 if( info == static_class_info + i ) {
6027 static_class_info[ i ].in_use = 0;
6032 // Otherwise it must have been malloc'd, so free it
6037 /* --------------------------------------------------------------------------
6038 Populate an already-allocated ClassInfo. Return 0 if successful, 1 if not.
6039 ---------------------------------------------------------------------------*/
6040 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6043 osrfLogError( OSRF_LOG_MARK,
6044 "%s ERROR: No ClassInfo available to populate", MODULENAME );
6045 info->alias = info->class_name = info->source_def = NULL;
6046 info->class_def = info->fields = info->links = NULL;
6051 osrfLogError( OSRF_LOG_MARK,
6052 "%s ERROR: No class name provided for lookup", MODULENAME );
6053 info->alias = info->class_name = info->source_def = NULL;
6054 info->class_def = info->fields = info->links = NULL;
6058 // Alias defaults to class name if not supplied
6059 if( ! alias || ! alias[ 0 ] )
6062 // Look up class info in the IDL
6063 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6065 osrfLogError( OSRF_LOG_MARK,
6066 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
6067 info->alias = info->class_name = info->source_def = NULL;
6068 info->class_def = info->fields = info->links = NULL;
6070 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6071 osrfLogError( OSRF_LOG_MARK,
6072 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
6073 info->alias = info->class_name = info->source_def = NULL;
6074 info->class_def = info->fields = info->links = NULL;
6078 osrfHash* links = osrfHashGet( class_def, "links" );
6080 osrfLogError( OSRF_LOG_MARK,
6081 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
6082 info->alias = info->class_name = info->source_def = NULL;
6083 info->class_def = info->fields = info->links = NULL;
6087 osrfHash* fields = osrfHashGet( class_def, "fields" );
6089 osrfLogError( OSRF_LOG_MARK,
6090 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
6091 info->alias = info->class_name = info->source_def = NULL;
6092 info->class_def = info->fields = info->links = NULL;
6096 char* source_def = getRelation( class_def );
6100 // We got everything we need, so populate the ClassInfo
6101 if( strlen( alias ) > ALIAS_STORE_SIZE )
6102 info->alias = strdup( alias );
6104 strcpy( info->alias_store, alias );
6105 info->alias = info->alias_store;
6108 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6109 info->class_name = strdup( class );
6111 strcpy( info->class_name_store, class );
6112 info->class_name = info->class_name_store;
6115 info->source_def = source_def;
6117 info->class_def = class_def;
6118 info->links = links;
6119 info->fields = fields;
6124 #define STATIC_FRAME_COUNT 3
6126 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6128 /* ---------------------------------------------------------------------------
6129 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
6131 ---------------------------------------------------------------------------*/
6132 static QueryFrame* allocate_frame( void ) {
6133 // In order to reduce the number of mallocs and frees, we return a static
6134 // instance of QueryFrame, if we can find one that we're not already using.
6135 // We rely on the fact that the compiler will implicitly initialize the
6136 // static instances so that in_use == 0.
6139 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6140 if( ! static_frame[ i ].in_use ) {
6141 static_frame[ i ].in_use = 1;
6142 return static_frame + i;
6146 // The static ones are all in use. Malloc one.
6148 return safe_malloc( sizeof( QueryFrame ) );
6151 /* --------------------------------------------------------------------------
6152 Free a QueryFrame, and all the memory it owns.
6153 ---------------------------------------------------------------------------*/
6154 static void free_query_frame( QueryFrame* frame ) {
6159 clear_class_info( &frame->core );
6161 // Free the join list
6163 ClassInfo* info = frame->join_list;
6166 free_class_info( info );
6170 frame->join_list = NULL;
6173 // If the frame is a static instance, just mark it as unused
6175 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6176 if( frame == static_frame + i ) {
6177 static_frame[ i ].in_use = 0;
6182 // Otherwise it must have been malloc'd, so free it
6187 /* --------------------------------------------------------------------------
6188 Search a given QueryFrame for a specified alias. If you find it, return
6189 a pointer to the corresponding ClassInfo. Otherwise return NULL.
6190 ---------------------------------------------------------------------------*/
6191 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6192 if( ! frame || ! target ) {
6196 ClassInfo* found_class = NULL;
6198 if( !strcmp( target, frame->core.alias ) )
6199 return &(frame->core);
6201 ClassInfo* curr_class = frame->join_list;
6202 while( curr_class ) {
6203 if( strcmp( target, curr_class->alias ) )
6204 curr_class = curr_class->next;
6206 found_class = curr_class;
6215 /* --------------------------------------------------------------------------
6216 Push a new (blank) QueryFrame onto the stack.
6217 ---------------------------------------------------------------------------*/
6218 static void push_query_frame( void ) {
6219 QueryFrame* frame = allocate_frame();
6220 frame->join_list = NULL;
6221 frame->next = curr_query;
6223 // Initialize the ClassInfo for the core class
6224 ClassInfo* core = &frame->core;
6225 core->alias = core->class_name = core->source_def = NULL;
6226 core->class_def = core->fields = core->links = NULL;
6231 /* --------------------------------------------------------------------------
6232 Pop a QueryFrame off the stack and destroy it
6233 ---------------------------------------------------------------------------*/
6234 static void pop_query_frame( void ) {
6239 QueryFrame* popped = curr_query;
6240 curr_query = popped->next;
6242 free_query_frame( popped );
6245 /* --------------------------------------------------------------------------
6246 Populate the ClassInfo for the core class. Return 0 if successful, 1 if not.
6247 ---------------------------------------------------------------------------*/
6248 static int add_query_core( const char* alias, const char* class_name ) {
6251 if( ! curr_query ) {
6252 osrfLogError( OSRF_LOG_MARK,
6253 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
6255 } else if( curr_query->core.alias ) {
6256 osrfLogError( OSRF_LOG_MARK,
6257 "%s ERROR: Core class %s already populated as %s",
6258 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
6262 build_class_info( &curr_query->core, alias, class_name );
6263 if( curr_query->core.alias )
6266 osrfLogError( OSRF_LOG_MARK,
6267 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
6272 /* --------------------------------------------------------------------------
6273 Search the current QueryFrame for a specified alias. If you find it,
6274 return a pointer to the corresponding ClassInfo. Otherwise return NULL.
6275 ---------------------------------------------------------------------------*/
6276 static ClassInfo* search_alias( const char* target ) {
6277 return search_alias_in_frame( curr_query, target );
6280 /* --------------------------------------------------------------------------
6281 Search all levels of query for a specified alias, starting with the
6282 current query. If you find it, return a pointer to the corresponding
6283 ClassInfo. Otherwise return NULL.
6284 ---------------------------------------------------------------------------*/
6285 static ClassInfo* search_all_alias( const char* target ) {
6286 ClassInfo* found_class = NULL;
6287 QueryFrame* curr_frame = curr_query;
6289 while( curr_frame ) {
6290 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6293 curr_frame = curr_frame->next;
6299 /* --------------------------------------------------------------------------
6300 Add a class to the list of classes joined to the current query.
6301 ---------------------------------------------------------------------------*/
6302 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6304 if( ! classname || ! *classname ) { // sanity check
6305 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6312 const ClassInfo* conflict = search_alias( alias );
6314 osrfLogError( OSRF_LOG_MARK,
6315 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6316 MODULENAME, alias, conflict->class_name );
6320 ClassInfo* info = allocate_class_info();
6322 if( build_class_info( info, alias, classname ) ) {
6323 free_class_info( info );
6327 // Add the new ClassInfo to the join list of the current QueryFrame
6328 info->next = curr_query->join_list;
6329 curr_query->join_list = info;
6334 /* --------------------------------------------------------------------------
6335 Destroy all nodes on the query stack.
6336 ---------------------------------------------------------------------------*/
6337 static void clear_query_stack( void ) {