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);
1243 @brief Verify that we have a valid class reference.
1244 @param ctx Pointer to the method context.
1245 @param param Pointer to the method parameters.
1246 @return 1 if the class reference is valid, or zero if it isn't.
1248 The class of the method params must match the class to which the method id devoted.
1249 For PCRUD there are additional restrictions.
1251 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1253 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1254 osrfHash* class = osrfHashGet( method_meta, "class" );
1256 // Compare the method's class to the parameters' class
1257 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1259 // Oops -- they don't match. Complain.
1260 growing_buffer* msg = buffer_init(128);
1263 "%s: %s method for type %s was passed a %s",
1265 osrfHashGet(method_meta, "methodtype"),
1266 osrfHashGet(class, "classname"),
1267 param->classname ? param->classname : "(null)"
1270 char* m = buffer_release(msg);
1271 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1280 ret = verifyObjectPCRUD( ctx, param );
1289 @brief (PCRUD only) Verify that the user is properly logged in.
1290 @param ctx Pointer to the method context.
1291 @return If the user is logged in, a pointer to the user object from the authentication
1292 server; otherwise NULL.
1294 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1296 // Get the authkey (the first method parameter)
1297 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1298 jsonObject* auth_object = jsonNewObject(auth);
1300 // Fetch the user object from the authentication server
1301 jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
1303 jsonObjectFree(auth_object);
1305 if (!user->classname || strcmp(user->classname, "au")) {
1307 growing_buffer* msg = buffer_init(128);
1310 "%s: permacrud received a bad auth token: %s",
1315 char* m = buffer_release(msg);
1316 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1320 jsonObjectFree(user);
1327 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1329 dbhandle = writehandle;
1331 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1332 osrfHash* class = osrfHashGet( method_metadata, "class" );
1333 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1336 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
1337 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1338 } else if ( *method_type == 'u' || *method_type == 'd' ) {
1339 fetch = 1; // MUST go to the db for the object for update and delete
1342 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
1345 // No permacrud for this method type on this class
1347 growing_buffer* msg = buffer_init(128);
1350 "%s: %s on class %s has no permacrud IDL entry",
1352 osrfHashGet(method_metadata, "methodtype"),
1353 osrfHashGet(class, "classname")
1356 char* m = buffer_release(msg);
1357 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1358 "osrfMethodException", ctx->request, m );
1365 jsonObject* user = verifyUserPCRUD( ctx );
1369 int userid = atoi( oilsFMGetString( user, "id" ) );
1370 jsonObjectFree(user);
1372 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1373 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1374 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1376 osrfStringArray* context_org_array = osrfNewStringArray(1);
1379 char* pkey_value = NULL;
1380 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1381 osrfLogDebug( OSRF_LOG_MARK,
1382 "global-level permissions required, fetching top of the org tree" );
1384 // check for perm at top of org tree
1385 char* org_tree_root_id = org_tree_root( ctx );
1386 if( org_tree_root_id ) {
1387 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1388 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1390 osrfStringArrayFree( context_org_array );
1395 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1396 "fetching context org ids" );
1397 const char* pkey = osrfHashGet(class, "primarykey");
1398 jsonObject *param = NULL;
1400 if (obj->classname) {
1401 pkey_value = oilsFMGetString( obj, pkey );
1403 param = jsonObjectClone(obj);
1404 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1407 pkey_value = jsonObjectToSimpleString( obj );
1409 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1410 "of %s and retrieving from the database", pkey_value );
1414 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1415 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1416 jsonObjectFree(_tmp_params);
1418 param = jsonObjectExtractIndex(_list, 0);
1419 jsonObjectFree(_list);
1423 osrfLogDebug( OSRF_LOG_MARK,
1424 "Object not found in the database with primary key %s of %s",
1427 growing_buffer* msg = buffer_init(128);
1430 "%s: no object found with primary key %s of %s",
1436 char* m = buffer_release(msg);
1437 osrfAppSessionStatus(
1439 OSRF_STATUS_INTERNALSERVERERROR,
1440 "osrfMethodException",
1446 if (pkey_value) free(pkey_value);
1451 if (local_context->size > 0) {
1452 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1453 local_context->size);
1455 const char* lcontext = NULL;
1456 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1457 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1460 "adding class-local field %s (value: %s) to the context org list",
1462 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1467 if (foreign_context) {
1468 unsigned long class_count = osrfHashGetCount( foreign_context );
1469 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1471 if (class_count > 0) {
1473 osrfHash* fcontext = NULL;
1474 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1475 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1476 const char* class_name = osrfHashIteratorKey( class_itr );
1477 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1481 "%d foreign context fields(s) specified for class %s",
1482 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1486 char* foreign_pkey = osrfHashGet(fcontext, "field");
1487 char* foreign_pkey_value =
1488 oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1490 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1492 jsonObject* _list = doFieldmapperSearch(
1493 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1495 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1496 jsonObjectFree(_tmp_params);
1497 jsonObjectFree(_list);
1499 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1501 if (_fparam && jump_list) {
1502 const char* flink = NULL;
1504 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1505 free(foreign_pkey_value);
1507 osrfHash* foreign_link_hash =
1508 oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1510 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1511 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1513 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1515 _list = doFieldmapperSearch(
1517 osrfHashGet( oilsIDL(),
1518 osrfHashGet( foreign_link_hash, "class" ) ),
1524 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1525 jsonObjectFree(_tmp_params);
1526 jsonObjectFree(_list);
1532 growing_buffer* msg = buffer_init(128);
1535 "%s: no object found with primary key %s of %s",
1541 char* m = buffer_release(msg);
1542 osrfAppSessionStatus(
1544 OSRF_STATUS_INTERNALSERVERERROR,
1545 "osrfMethodException",
1551 osrfHashIteratorFree(class_itr);
1552 free(foreign_pkey_value);
1553 jsonObjectFree(param);
1558 free(foreign_pkey_value);
1561 const char* foreign_field = NULL;
1562 while ( (foreign_field = osrfStringArrayGetString(
1563 osrfHashGet(fcontext,"context" ), j++)) ) {
1564 osrfStringArrayAdd( context_org_array,
1565 oilsFMGetString( _fparam, foreign_field ) );
1568 "adding foreign class %s field %s (value: %s) to the context org list",
1571 osrfStringArrayGetString(
1572 context_org_array, context_org_array->size - 1)
1576 jsonObjectFree(_fparam);
1579 osrfHashIteratorFree( class_itr );
1583 jsonObjectFree(param);
1586 const char* context_org = NULL;
1587 const char* perm = NULL;
1590 if (permission->size == 0) {
1591 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1596 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1598 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1604 "Checking object permission [%s] for user %d "
1605 "on object %s (class %s) at org %d",
1609 osrfHashGet(class, "classname"),
1613 result = dbi_conn_queryf(
1615 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1618 osrfHashGet(class, "classname"),
1626 "Received a result for object permission [%s] "
1627 "for user %d on object %s (class %s) at org %d",
1631 osrfHashGet(class, "classname"),
1635 if (dbi_result_first_row(result)) {
1636 jsonObject* return_val = oilsMakeJSONFromResult( result );
1637 const char* has_perm = jsonObjectGetString(
1638 jsonObjectGetKeyConst(return_val, "has_perm") );
1642 "Status of object permission [%s] for user %d "
1643 "on object %s (class %s) at org %d is %s",
1647 osrfHashGet(class, "classname"),
1652 if ( *has_perm == 't' ) OK = 1;
1653 jsonObjectFree(return_val);
1656 dbi_result_free(result);
1662 osrfLogDebug( OSRF_LOG_MARK,
1663 "Checking non-object permission [%s] for user %d at org %d",
1664 perm, userid, atoi(context_org) );
1665 result = dbi_conn_queryf(
1667 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1674 osrfLogDebug( OSRF_LOG_MARK,
1675 "Received a result for permission [%s] for user %d at org %d",
1676 perm, userid, atoi(context_org) );
1677 if ( dbi_result_first_row(result) ) {
1678 jsonObject* return_val = oilsMakeJSONFromResult( result );
1679 const char* has_perm = jsonObjectGetString(
1680 jsonObjectGetKeyConst(return_val, "has_perm") );
1681 osrfLogDebug( OSRF_LOG_MARK,
1682 "Status of permission [%s] for user %d at org %d is [%s]",
1683 perm, userid, atoi(context_org), has_perm );
1684 if ( *has_perm == 't' )
1686 jsonObjectFree(return_val);
1689 dbi_result_free(result);
1698 if (pkey_value) free(pkey_value);
1699 osrfStringArrayFree(context_org_array);
1705 @brief Look up the root of the org_unit tree.
1706 @param ctx Pointer to the method context.
1707 @return The id of the root org unit, as a character string.
1709 Query actor.org_unit where parent_ou is null, and return the id as a string.
1711 This function assumes that there is only one root org unit, i.e. that we
1712 have a single tree, not a forest.
1714 The calling code is responsible for freeing the returned string.
1716 static char* org_tree_root( osrfMethodContext* ctx ) {
1718 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1719 static time_t last_lookup_time = 0;
1720 time_t current_time = time( NULL );
1722 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1723 // We successfully looked this up less than an hour ago.
1724 // It's not likely to have changed since then.
1725 return strdup( cached_root_id );
1727 last_lookup_time = current_time;
1730 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1731 jsonObject* result = doFieldmapperSearch(
1732 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1733 jsonObjectFree( where_clause );
1735 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1738 jsonObjectFree( result );
1740 growing_buffer* msg = buffer_init(128);
1741 OSRF_BUFFER_ADD( msg, MODULENAME );
1742 OSRF_BUFFER_ADD( msg,
1743 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1745 char* m = buffer_release(msg);
1746 osrfAppSessionStatus( ctx->session,
1747 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1750 cached_root_id[ 0 ] = '\0';
1754 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1755 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1757 jsonObjectFree( result );
1759 strcpy( cached_root_id, root_org_unit_id );
1760 return root_org_unit_id;
1764 @brief Create a JSON_HASH with a single key/value pair.
1765 @param key The key of the key/value pair.
1766 @param value the value of the key/value pair.
1767 @return Pointer to a newly created jsonObject of type JSON_HASH.
1769 The value of the key/value is either a string or (if @a value is NULL) a null.
1771 static jsonObject* single_hash( const char* key, const char* value ) {
1773 if( ! key ) key = "";
1775 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1776 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1782 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1784 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1786 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1787 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1789 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1790 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1793 if (!verifyObjectClass(ctx, target)) {
1798 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1800 const char* trans_id = getXactId( ctx );
1802 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1804 osrfAppSessionStatus(
1806 OSRF_STATUS_BADREQUEST,
1807 "osrfMethodException",
1809 "No active transaction -- required for CREATE"
1815 // The following test is harmless but redundant. If a class is
1816 // readonly, we don't register a create method for it.
1817 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1818 osrfAppSessionStatus(
1820 OSRF_STATUS_BADREQUEST,
1821 "osrfMethodException",
1823 "Cannot INSERT readonly class"
1829 // Set the last_xact_id
1830 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1832 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1833 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1836 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1838 dbhandle = writehandle;
1840 osrfHash* fields = osrfHashGet(meta, "fields");
1841 char* pkey = osrfHashGet(meta, "primarykey");
1842 char* seq = osrfHashGet(meta, "sequence");
1844 growing_buffer* table_buf = buffer_init(128);
1845 growing_buffer* col_buf = buffer_init(128);
1846 growing_buffer* val_buf = buffer_init(128);
1848 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1849 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1850 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1851 buffer_add(val_buf,"VALUES (");
1855 osrfHash* field = NULL;
1856 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1857 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1859 const char* field_name = osrfHashIteratorKey( field_itr );
1861 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1864 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1867 if (field_object && field_object->classname) {
1868 value = oilsFMGetString(
1870 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1872 } else if( field_object && JSON_BOOL == field_object->type ) {
1873 if( jsonBoolIsTrue( field_object ) )
1874 value = strdup( "t" );
1876 value = strdup( "f" );
1878 value = jsonObjectToSimpleString( field_object );
1884 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1885 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1888 buffer_add(col_buf, field_name);
1890 if (!field_object || field_object->type == JSON_NULL) {
1891 buffer_add( val_buf, "DEFAULT" );
1893 } else if ( !strcmp(get_primitive( field ), "number") ) {
1894 const char* numtype = get_datatype( field );
1895 if ( !strcmp( numtype, "INT8") ) {
1896 buffer_fadd( val_buf, "%lld", atoll(value) );
1898 } else if ( !strcmp( numtype, "INT") ) {
1899 buffer_fadd( val_buf, "%d", atoi(value) );
1901 } else if ( !strcmp( numtype, "NUMERIC") ) {
1902 buffer_fadd( val_buf, "%f", atof(value) );
1905 if ( dbi_conn_quote_string(writehandle, &value) ) {
1906 OSRF_BUFFER_ADD( val_buf, value );
1909 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1910 osrfAppSessionStatus(
1912 OSRF_STATUS_INTERNALSERVERERROR,
1913 "osrfMethodException",
1915 "Error quoting string -- please see the error log for more details"
1918 buffer_free(table_buf);
1919 buffer_free(col_buf);
1920 buffer_free(val_buf);
1930 osrfHashIteratorFree( field_itr );
1932 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1933 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1935 char* table_str = buffer_release(table_buf);
1936 char* col_str = buffer_release(col_buf);
1937 char* val_str = buffer_release(val_buf);
1938 growing_buffer* sql = buffer_init(128);
1939 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1944 char* query = buffer_release(sql);
1946 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1949 dbi_result result = dbi_conn_query(writehandle, query);
1951 jsonObject* obj = NULL;
1954 obj = jsonNewObject(NULL);
1957 "%s ERROR inserting %s object using query [%s]",
1959 osrfHashGet(meta, "fieldmapper"),
1962 osrfAppSessionStatus(
1964 OSRF_STATUS_INTERNALSERVERERROR,
1965 "osrfMethodException",
1967 "INSERT error -- please see the error log for more details"
1972 char* id = oilsFMGetString(target, pkey);
1974 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1975 growing_buffer* _id = buffer_init(10);
1976 buffer_fadd(_id, "%lld", new_id);
1977 id = buffer_release(_id);
1980 // Find quietness specification, if present
1981 const char* quiet_str = NULL;
1983 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1985 quiet_str = jsonObjectGetString( quiet_obj );
1988 if( str_is_true( quiet_str ) ) { // if quietness is specified
1989 obj = jsonNewObject(id);
1993 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1994 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1996 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1998 jsonObjectFree( where_clause );
2003 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
2006 jsonObjectFree( list );
2019 * Fetch one row from a specified table, using a specified value
2020 * for the primary key
2022 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
2032 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2034 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos); // key value
2038 "%s retrieving %s object with primary key value of %s",
2040 osrfHashGet( class_def, "fieldmapper" ),
2041 jsonObjectGetString( id_obj )
2044 // Build a WHERE clause based on the key value
2045 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2048 osrfHashGet( class_def, "primarykey" ),
2049 jsonObjectClone( id_obj )
2052 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
2054 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
2056 jsonObjectFree( where_clause );
2060 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2061 jsonObjectFree( list );
2064 if(!verifyObjectPCRUD(ctx, obj)) {
2065 jsonObjectFree(obj);
2068 growing_buffer* msg = buffer_init(128);
2069 OSRF_BUFFER_ADD( msg, MODULENAME );
2070 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2072 char* m = buffer_release(msg);
2073 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
2084 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
2085 growing_buffer* val_buf = buffer_init(32);
2086 const char* numtype = get_datatype( field );
2088 if ( !strncmp( numtype, "INT", 3 ) ) {
2089 if (value->type == JSON_NUMBER)
2090 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
2091 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2093 //const char* val_str = jsonObjectGetString( value );
2094 //buffer_fadd( val_buf, "%ld", atol(val_str) );
2095 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2098 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
2099 if (value->type == JSON_NUMBER)
2100 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
2101 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2103 //const char* val_str = jsonObjectGetString( value );
2104 //buffer_fadd( val_buf, "%f", atof(val_str) );
2105 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2109 // Presumably this was really intended ot be a string, so quote it
2110 char* str = jsonObjectToSimpleString( value );
2111 if ( dbi_conn_quote_string(dbhandle, &str) ) {
2112 OSRF_BUFFER_ADD( val_buf, str );
2115 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
2117 buffer_free(val_buf);
2122 return buffer_release(val_buf);
2125 static char* searchINPredicate (const char* class_alias, osrfHash* field,
2126 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2127 growing_buffer* sql_buf = buffer_init(32);
2133 osrfHashGet(field, "name")
2137 buffer_add(sql_buf, "IN (");
2138 } else if (!(strcasecmp(op,"not in"))) {
2139 buffer_add(sql_buf, "NOT IN (");
2141 buffer_add(sql_buf, "IN (");
2144 if (node->type == JSON_HASH) {
2145 // subquery predicate
2146 char* subpred = buildQuery( ctx, node, SUBSELECT );
2148 buffer_free( sql_buf );
2152 buffer_add(sql_buf, subpred);
2155 } else if (node->type == JSON_ARRAY) {
2156 // literal value list
2157 int in_item_index = 0;
2158 int in_item_first = 1;
2159 const jsonObject* in_item;
2160 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
2165 buffer_add(sql_buf, ", ");
2168 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2169 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
2170 MODULENAME, json_type( in_item->type ) );
2171 buffer_free(sql_buf);
2175 // Append the literal value -- quoted if not a number
2176 if ( JSON_NUMBER == in_item->type ) {
2177 char* val = jsonNumberToDBString( field, in_item );
2178 OSRF_BUFFER_ADD( sql_buf, val );
2181 } else if ( !strcmp( get_primitive( field ), "number") ) {
2182 char* val = jsonNumberToDBString( field, in_item );
2183 OSRF_BUFFER_ADD( sql_buf, val );
2187 char* key_string = jsonObjectToSimpleString(in_item);
2188 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
2189 OSRF_BUFFER_ADD( sql_buf, key_string );
2192 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
2194 buffer_free(sql_buf);
2200 if( in_item_first ) {
2201 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
2202 buffer_free( sql_buf );
2206 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2207 MODULENAME, json_type( node->type ) );
2208 buffer_free(sql_buf);
2212 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2214 return buffer_release(sql_buf);
2217 // Receive a JSON_ARRAY representing a function call. The first
2218 // entry in the array is the function name. The rest are parameters.
2219 static char* searchValueTransform( const jsonObject* array ) {
2221 if( array->size < 1 ) {
2222 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
2226 // Get the function name
2227 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2228 if( func_item->type != JSON_STRING ) {
2229 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2230 MODULENAME, json_type( func_item->type ) );
2234 growing_buffer* sql_buf = buffer_init(32);
2236 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2237 OSRF_BUFFER_ADD( sql_buf, "( " );
2239 // Get the parameters
2240 int func_item_index = 1; // We already grabbed the zeroth entry
2241 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2243 // Add a separator comma, if we need one
2244 if( func_item_index > 2 )
2245 buffer_add( sql_buf, ", " );
2247 // Add the current parameter
2248 if (func_item->type == JSON_NULL) {
2249 buffer_add( sql_buf, "NULL" );
2251 char* val = jsonObjectToSimpleString(func_item);
2252 if ( dbi_conn_quote_string(dbhandle, &val) ) {
2253 OSRF_BUFFER_ADD( sql_buf, val );
2256 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2257 buffer_free(sql_buf);
2264 buffer_add( sql_buf, " )" );
2266 return buffer_release(sql_buf);
2269 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
2270 const jsonObject* node, const char* op) {
2272 if( ! is_good_operator( op ) ) {
2273 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2277 char* val = searchValueTransform(node);
2281 growing_buffer* sql_buf = buffer_init(32);
2286 osrfHashGet(field, "name"),
2293 return buffer_release(sql_buf);
2296 // class_alias is a class name or other table alias
2297 // field is a field definition as stored in the IDL
2298 // node comes from the method parameter, and may represent an entry in the SELECT list
2299 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
2300 growing_buffer* sql_buf = buffer_init(32);
2302 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
2303 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
2305 if(transform_subcolumn) {
2306 if( ! is_identifier( transform_subcolumn ) ) {
2307 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2308 MODULENAME, transform_subcolumn );
2309 buffer_free( sql_buf );
2312 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2315 if (field_transform) {
2317 if( ! is_identifier( field_transform ) ) {
2318 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2319 MODULENAME, field_transform );
2320 buffer_free( sql_buf );
2324 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class_alias, osrfHashGet(field, "name"));
2325 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2328 if( array->type != JSON_ARRAY ) {
2329 osrfLogError( OSRF_LOG_MARK,
2330 "%s: Expected JSON_ARRAY for function params; found %s",
2331 MODULENAME, json_type( array->type ) );
2332 buffer_free( sql_buf );
2335 int func_item_index = 0;
2336 jsonObject* func_item;
2337 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2339 char* val = jsonObjectToSimpleString(func_item);
2342 buffer_add( sql_buf, ",NULL" );
2343 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
2344 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2345 OSRF_BUFFER_ADD( sql_buf, val );
2347 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2349 buffer_free(sql_buf);
2356 buffer_add( sql_buf, " )" );
2359 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2362 if (transform_subcolumn)
2363 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2365 return buffer_release(sql_buf);
2368 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2369 const jsonObject* node, const char* op ) {
2371 if( ! is_good_operator( op ) ) {
2372 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2376 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2377 if( ! field_transform )
2380 int extra_parens = 0; // boolean
2382 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2383 if ( ! value_obj ) {
2384 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2386 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
2387 free(field_transform);
2391 } else if ( value_obj->type == JSON_ARRAY ) {
2392 value = searchValueTransform( value_obj );
2394 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
2395 free( field_transform );
2398 } else if ( value_obj->type == JSON_HASH ) {
2399 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2401 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
2402 free(field_transform);
2406 } else if ( value_obj->type == JSON_NUMBER ) {
2407 value = jsonNumberToDBString( field, value_obj );
2408 } else if ( value_obj->type == JSON_NULL ) {
2409 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
2410 free(field_transform);
2412 } else if ( value_obj->type == JSON_BOOL ) {
2413 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
2414 free(field_transform);
2417 if ( !strcmp( get_primitive( field ), "number") ) {
2418 value = jsonNumberToDBString( field, value_obj );
2420 value = jsonObjectToSimpleString( value_obj );
2421 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2422 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
2424 free(field_transform);
2430 const char* left_parens = "";
2431 const char* right_parens = "";
2433 if( extra_parens ) {
2438 growing_buffer* sql_buf = buffer_init(32);
2442 "%s%s %s %s %s %s%s",
2453 free(field_transform);
2455 return buffer_release(sql_buf);
2458 static char* searchSimplePredicate (const char* op, const char* class_alias,
2459 osrfHash* field, const jsonObject* node) {
2461 if( ! is_good_operator( op ) ) {
2462 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2468 // Get the value to which we are comparing the specified column
2469 if (node->type != JSON_NULL) {
2470 if ( node->type == JSON_NUMBER ) {
2471 val = jsonNumberToDBString( field, node );
2472 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2473 val = jsonNumberToDBString( field, node );
2475 val = jsonObjectToSimpleString(node);
2480 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2481 // Value is not numeric; enclose it in quotes
2482 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2483 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2489 // Compare to a null value
2490 val = strdup( "NULL" );
2491 if (strcmp( op, "=" ))
2497 growing_buffer* sql_buf = buffer_init(32);
2498 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2499 char* pred = buffer_release( sql_buf );
2506 static char* searchBETWEENPredicate (const char* class_alias,
2507 osrfHash* field, const jsonObject* node) {
2509 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2510 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2512 if( NULL == y_node ) {
2513 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2516 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2517 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2524 if ( !strcmp( get_primitive( field ), "number") ) {
2525 x_string = jsonNumberToDBString(field, x_node);
2526 y_string = jsonNumberToDBString(field, y_node);
2529 x_string = jsonObjectToSimpleString(x_node);
2530 y_string = jsonObjectToSimpleString(y_node);
2531 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2532 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2533 MODULENAME, x_string, y_string);
2540 growing_buffer* sql_buf = buffer_init(32);
2541 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2542 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2546 return buffer_release(sql_buf);
2549 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2550 jsonObject* node, osrfMethodContext* ctx ) {
2553 if (node->type == JSON_ARRAY) { // equality IN search
2554 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2555 } else if (node->type == JSON_HASH) { // other search
2556 jsonIterator* pred_itr = jsonNewIterator( node );
2557 if( !jsonIteratorHasNext( pred_itr ) ) {
2558 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2559 MODULENAME, osrfHashGet(field, "name") );
2561 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2563 // Verify that there are no additional predicates
2564 if( jsonIteratorHasNext( pred_itr ) ) {
2565 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2566 MODULENAME, osrfHashGet(field, "name") );
2567 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2568 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2569 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2570 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
2571 else if ( pred_node->type == JSON_ARRAY )
2572 pred = searchFunctionPredicate( class_info->alias, field, pred_node, pred_itr->key );
2573 else if ( pred_node->type == JSON_HASH )
2574 pred = searchFieldTransformPredicate( class_info, field, pred_node, pred_itr->key );
2576 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2578 jsonIteratorFree(pred_itr);
2580 } else if (node->type == JSON_NULL) { // IS NULL search
2581 growing_buffer* _p = buffer_init(64);
2584 "\"%s\".%s IS NULL",
2585 class_info->class_name,
2586 osrfHashGet(field, "name")
2588 pred = buffer_release(_p);
2589 } else { // equality search
2590 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2609 field : call_number,
2625 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2627 const jsonObject* working_hash;
2628 jsonObject* freeable_hash = NULL;
2630 if (join_hash->type == JSON_HASH) {
2631 working_hash = join_hash;
2632 } else if (join_hash->type == JSON_STRING) {
2633 // turn it into a JSON_HASH by creating a wrapper
2634 // around a copy of the original
2635 const char* _tmp = jsonObjectGetString( join_hash );
2636 freeable_hash = jsonNewObjectType(JSON_HASH);
2637 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2638 working_hash = freeable_hash;
2642 "%s: JOIN failed; expected JSON object type not found",
2648 growing_buffer* join_buf = buffer_init(128);
2649 const char* leftclass = left_info->class_name;
2651 jsonObject* snode = NULL;
2652 jsonIterator* search_itr = jsonNewIterator( working_hash );
2654 while ( (snode = jsonIteratorNext( search_itr )) ) {
2655 const char* right_alias = search_itr->key;
2657 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2659 class = right_alias;
2661 const ClassInfo* right_info = add_joined_class( right_alias, class );
2665 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2669 jsonIteratorFree( search_itr );
2670 buffer_free( join_buf );
2672 jsonObjectFree( freeable_hash );
2675 osrfHash* links = right_info->links;
2676 const char* table = right_info->source_def;
2678 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2679 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2681 if (field && !fkey) {
2682 // Look up the corresponding join column in the IDL.
2683 // The link must be defined in the child table,
2684 // and point to the right parent table.
2685 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2686 const char* reltype = NULL;
2687 const char* other_class = NULL;
2688 reltype = osrfHashGet( idl_link, "reltype" );
2689 if( reltype && strcmp( reltype, "has_many" ) )
2690 other_class = osrfHashGet( idl_link, "class" );
2691 if( other_class && !strcmp( other_class, leftclass ) )
2692 fkey = osrfHashGet( idl_link, "key" );
2696 "%s: JOIN failed. No link defined from %s.%s to %s",
2702 buffer_free(join_buf);
2704 jsonObjectFree(freeable_hash);
2705 jsonIteratorFree(search_itr);
2709 } else if (!field && fkey) {
2710 // Look up the corresponding join column in the IDL.
2711 // The link must be defined in the child table,
2712 // and point to the right parent table.
2713 osrfHash* left_links = left_info->links;
2714 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2715 const char* reltype = NULL;
2716 const char* other_class = NULL;
2717 reltype = osrfHashGet( idl_link, "reltype" );
2718 if( reltype && strcmp( reltype, "has_many" ) )
2719 other_class = osrfHashGet( idl_link, "class" );
2720 if( other_class && !strcmp( other_class, class ) )
2721 field = osrfHashGet( idl_link, "key" );
2725 "%s: JOIN failed. No link defined from %s.%s to %s",
2731 buffer_free(join_buf);
2733 jsonObjectFree(freeable_hash);
2734 jsonIteratorFree(search_itr);
2738 } else if (!field && !fkey) {
2739 osrfHash* left_links = left_info->links;
2741 // For each link defined for the left class:
2742 // see if the link references the joined class
2743 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2744 osrfHash* curr_link = NULL;
2745 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2746 const char* other_class = osrfHashGet( curr_link, "class" );
2747 if( other_class && !strcmp( other_class, class ) ) {
2749 // In the IDL, the parent class doesn't know then names of the child
2750 // columns that are pointing to it, so don't use that end of the link
2751 const char* reltype = osrfHashGet( curr_link, "reltype" );
2752 if( reltype && strcmp( reltype, "has_many" ) ) {
2753 // Found a link between the classes
2754 fkey = osrfHashIteratorKey( itr );
2755 field = osrfHashGet( curr_link, "key" );
2760 osrfHashIteratorFree( itr );
2762 if (!field || !fkey) {
2763 // Do another such search, with the classes reversed
2765 // For each link defined for the joined class:
2766 // see if the link references the left class
2767 osrfHashIterator* itr = osrfNewHashIterator( links );
2768 osrfHash* curr_link = NULL;
2769 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2770 const char* other_class = osrfHashGet( curr_link, "class" );
2771 if( other_class && !strcmp( other_class, leftclass ) ) {
2773 // In the IDL, the parent class doesn't know then names of the child
2774 // columns that are pointing to it, so don't use that end of the link
2775 const char* reltype = osrfHashGet( curr_link, "reltype" );
2776 if( reltype && strcmp( reltype, "has_many" ) ) {
2777 // Found a link between the classes
2778 field = osrfHashIteratorKey( itr );
2779 fkey = osrfHashGet( curr_link, "key" );
2784 osrfHashIteratorFree( itr );
2787 if (!field || !fkey) {
2790 "%s: JOIN failed. No link defined between %s and %s",
2795 buffer_free(join_buf);
2797 jsonObjectFree(freeable_hash);
2798 jsonIteratorFree(search_itr);
2804 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2806 if ( !strcasecmp(type,"left") ) {
2807 buffer_add(join_buf, " LEFT JOIN");
2808 } else if ( !strcasecmp(type,"right") ) {
2809 buffer_add(join_buf, " RIGHT JOIN");
2810 } else if ( !strcasecmp(type,"full") ) {
2811 buffer_add(join_buf, " FULL JOIN");
2813 buffer_add(join_buf, " INNER JOIN");
2816 buffer_add(join_buf, " INNER JOIN");
2819 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2820 table, right_alias, right_alias, field, left_info->alias, fkey);
2822 // Add any other join conditions as specified by "filter"
2823 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2825 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2826 if ( filter_op && !strcasecmp("or",filter_op) ) {
2827 buffer_add( join_buf, " OR " );
2829 buffer_add( join_buf, " AND " );
2832 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2834 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2835 OSRF_BUFFER_ADD( join_buf, jpred );
2840 "%s: JOIN failed. Invalid conditional expression.",
2843 jsonIteratorFree( search_itr );
2844 buffer_free( join_buf );
2846 jsonObjectFree( freeable_hash );
2851 buffer_add(join_buf, " ) ");
2853 // Recursively add a nested join, if one is present
2854 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2856 char* jpred = searchJOIN( join_filter, right_info );
2858 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2859 OSRF_BUFFER_ADD( join_buf, jpred );
2862 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2863 jsonIteratorFree( search_itr );
2864 buffer_free( join_buf );
2866 jsonObjectFree( freeable_hash );
2873 jsonObjectFree(freeable_hash);
2874 jsonIteratorFree(search_itr);
2876 return buffer_release(join_buf);
2881 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2882 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2883 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2885 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2887 search_hash is the JSON expression of the conditions.
2888 meta is the class definition from the IDL, for the relevant table.
2889 opjoin_type indicates whether multiple conditions, if present, should be
2890 connected by AND or OR.
2891 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2892 to pass it to other functions -- and all they do with it is to use the session
2893 and request members to send error messages back to the client.
2897 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
2898 int opjoin_type, osrfMethodContext* ctx ) {
2902 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2905 class_info->class_def,
2910 growing_buffer* sql_buf = buffer_init(128);
2912 jsonObject* node = NULL;
2915 if ( search_hash->type == JSON_ARRAY ) {
2916 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2917 if( 0 == search_hash->size ) {
2920 "%s: Invalid predicate structure: empty JSON array",
2923 buffer_free( sql_buf );
2927 unsigned long i = 0;
2928 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
2932 if (opjoin_type == OR_OP_JOIN)
2933 buffer_add(sql_buf, " OR ");
2935 buffer_add(sql_buf, " AND ");
2938 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2940 buffer_free( sql_buf );
2944 buffer_fadd(sql_buf, "( %s )", subpred);
2948 } else if ( search_hash->type == JSON_HASH ) {
2949 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2950 jsonIterator* search_itr = jsonNewIterator( search_hash );
2951 if( !jsonIteratorHasNext( search_itr ) ) {
2954 "%s: Invalid predicate structure: empty JSON object",
2957 jsonIteratorFree( search_itr );
2958 buffer_free( sql_buf );
2962 while ( (node = jsonIteratorNext( search_itr )) ) {
2967 if (opjoin_type == OR_OP_JOIN)
2968 buffer_add(sql_buf, " OR ");
2970 buffer_add(sql_buf, " AND ");
2973 if ( '+' == search_itr->key[ 0 ] ) {
2975 // This plus sign prefixes a class name or other table alias;
2976 // make sure the table alias is in scope
2977 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2978 if( ! alias_info ) {
2981 "%s: Invalid table alias \"%s\" in WHERE clause",
2985 jsonIteratorFree( search_itr );
2986 buffer_free( sql_buf );
2990 if ( node->type == JSON_STRING ) {
2991 // It's the name of a column; make sure it belongs to the class
2992 const char* fieldname = jsonObjectGetString( node );
2993 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2996 "%s: Invalid column name \"%s\" in WHERE clause for table alias \"%s\"",
3001 jsonIteratorFree( search_itr );
3002 buffer_free( sql_buf );
3006 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
3008 // It's something more complicated
3009 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
3011 jsonIteratorFree( search_itr );
3012 buffer_free( sql_buf );
3016 buffer_fadd(sql_buf, "( %s )", subpred);
3019 } else if ( '-' == search_itr->key[ 0 ] ) {
3020 if ( !strcasecmp("-or",search_itr->key) ) {
3021 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
3023 jsonIteratorFree( search_itr );
3024 buffer_free( sql_buf );
3028 buffer_fadd(sql_buf, "( %s )", subpred);
3030 } else if ( !strcasecmp("-and",search_itr->key) ) {
3031 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3033 jsonIteratorFree( search_itr );
3034 buffer_free( sql_buf );
3038 buffer_fadd(sql_buf, "( %s )", subpred);
3040 } else if ( !strcasecmp("-not",search_itr->key) ) {
3041 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
3043 jsonIteratorFree( search_itr );
3044 buffer_free( sql_buf );
3048 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
3050 } else if ( !strcasecmp("-exists",search_itr->key) ) {
3051 char* subpred = buildQuery( ctx, node, SUBSELECT );
3053 jsonIteratorFree( search_itr );
3054 buffer_free( sql_buf );
3058 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
3060 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
3061 char* subpred = buildQuery( ctx, node, SUBSELECT );
3063 jsonIteratorFree( search_itr );
3064 buffer_free( sql_buf );
3068 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
3070 } else { // Invalid "minus" operator
3073 "%s: Invalid operator \"%s\" in WHERE clause",
3077 jsonIteratorFree( search_itr );
3078 buffer_free( sql_buf );
3084 const char* class = class_info->class_name;
3085 osrfHash* fields = class_info->fields;
3086 osrfHash* field = osrfHashGet( fields, search_itr->key );
3089 const char* table = class_info->source_def;
3092 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
3095 table ? table : "?",
3098 jsonIteratorFree(search_itr);
3099 buffer_free(sql_buf);
3103 char* subpred = searchPredicate( class_info, field, node, ctx );
3105 buffer_free(sql_buf);
3106 jsonIteratorFree(search_itr);
3110 buffer_add( sql_buf, subpred );
3114 jsonIteratorFree(search_itr);
3117 // ERROR ... only hash and array allowed at this level
3118 char* predicate_string = jsonObjectToJSON( search_hash );
3121 "%s: Invalid predicate structure: %s",
3125 buffer_free(sql_buf);
3126 free(predicate_string);
3130 return buffer_release(sql_buf);
3133 /* Build a JSON_ARRAY of field names for a given table alias
3135 static jsonObject* defaultSelectList( const char* table_alias ) {
3140 ClassInfo* class_info = search_all_alias( table_alias );
3141 if( ! class_info ) {
3144 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
3151 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
3152 osrfHash* field_def = NULL;
3153 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
3154 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
3155 const char* field_name = osrfHashIteratorKey( field_itr );
3156 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3157 jsonObjectPush( array, jsonNewObject( field_name ) );
3160 osrfHashIteratorFree( field_itr );
3165 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
3166 // The jsonObject must be a JSON_HASH with an single entry for "union",
3167 // "intersect", or "except". The data associated with this key must be an
3168 // array of hashes, each hash being a query.
3169 // Also allowed but currently ignored: entries for "order_by" and "alias".
3170 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
3172 if( ! combo || combo->type != JSON_HASH )
3173 return NULL; // should be impossible; validated by caller
3175 const jsonObject* query_array = NULL; // array of subordinate queries
3176 const char* op = NULL; // name of operator, e.g. UNION
3177 const char* alias = NULL; // alias for the query (needed for ORDER BY)
3178 int op_count = 0; // for detecting conflicting operators
3179 int excepting = 0; // boolean
3180 int all = 0; // boolean
3181 jsonObject* order_obj = NULL;
3183 // Identify the elements in the hash
3184 jsonIterator* query_itr = jsonNewIterator( combo );
3185 jsonObject* curr_obj = NULL;
3186 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
3187 if( ! strcmp( "union", query_itr->key ) ) {
3190 query_array = curr_obj;
3191 } else if( ! strcmp( "intersect", query_itr->key ) ) {
3194 query_array = curr_obj;
3195 } else if( ! strcmp( "except", query_itr->key ) ) {
3199 query_array = curr_obj;
3200 } else if( ! strcmp( "order_by", query_itr->key ) ) {
3203 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
3206 order_obj = curr_obj;
3207 } else if( ! strcmp( "alias", query_itr->key ) ) {
3208 if( curr_obj->type != JSON_STRING ) {
3209 jsonIteratorFree( query_itr );
3212 alias = jsonObjectGetString( curr_obj );
3213 } else if( ! strcmp( "all", query_itr->key ) ) {
3214 if( obj_is_true( curr_obj ) )
3218 osrfAppSessionStatus(
3220 OSRF_STATUS_INTERNALSERVERERROR,
3221 "osrfMethodException",
3223 "Malformed query; unexpected entry in query object"
3227 "%s: Unexpected entry for \"%s\" in%squery",
3232 jsonIteratorFree( query_itr );
3236 jsonIteratorFree( query_itr );
3238 // More sanity checks
3239 if( ! query_array ) {
3241 osrfAppSessionStatus(
3243 OSRF_STATUS_INTERNALSERVERERROR,
3244 "osrfMethodException",
3246 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3250 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3253 return NULL; // should be impossible...
3254 } else if( op_count > 1 ) {
3256 osrfAppSessionStatus(
3258 OSRF_STATUS_INTERNALSERVERERROR,
3259 "osrfMethodException",
3261 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3265 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3269 } if( query_array->type != JSON_ARRAY ) {
3271 osrfAppSessionStatus(
3273 OSRF_STATUS_INTERNALSERVERERROR,
3274 "osrfMethodException",
3276 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3280 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3283 json_type( query_array->type )
3286 } if( query_array->size < 2 ) {
3288 osrfAppSessionStatus(
3290 OSRF_STATUS_INTERNALSERVERERROR,
3291 "osrfMethodException",
3293 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3297 "%s:%srequires multiple queries as operands",
3302 } else if( excepting && query_array->size > 2 ) {
3304 osrfAppSessionStatus(
3306 OSRF_STATUS_INTERNALSERVERERROR,
3307 "osrfMethodException",
3309 "EXCEPT operator has too many queries as operands"
3313 "%s:EXCEPT operator has too many queries as operands",
3317 } else if( order_obj && ! alias ) {
3319 osrfAppSessionStatus(
3321 OSRF_STATUS_INTERNALSERVERERROR,
3322 "osrfMethodException",
3324 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3328 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3334 // So far so good. Now build the SQL.
3335 growing_buffer* sql = buffer_init( 256 );
3337 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3338 // Add a layer of parentheses
3339 if( flags & SUBCOMBO )
3340 OSRF_BUFFER_ADD( sql, "( " );
3342 // Traverse the query array. Each entry should be a hash.
3343 int first = 1; // boolean
3345 jsonObject* query = NULL;
3346 while((query = jsonObjectGetIndex( query_array, i++ ) )) {
3347 if( query->type != JSON_HASH ) {
3349 osrfAppSessionStatus(
3351 OSRF_STATUS_INTERNALSERVERERROR,
3352 "osrfMethodException",
3354 "Malformed query under UNION, INTERSECT or EXCEPT"
3358 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3361 json_type( query->type )
3370 OSRF_BUFFER_ADD( sql, op );
3372 OSRF_BUFFER_ADD( sql, "ALL " );
3375 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3379 "%s: Error building query under%s",
3387 OSRF_BUFFER_ADD( sql, query_str );
3390 if( flags & SUBCOMBO )
3391 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3393 if ( !(flags & SUBSELECT) )
3394 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3396 return buffer_release( sql );
3399 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3400 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3401 // or "except" to indicate the type of query.
3402 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3406 osrfAppSessionStatus(
3408 OSRF_STATUS_INTERNALSERVERERROR,
3409 "osrfMethodException",
3411 "Malformed query; no query object"
3413 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", MODULENAME );
3415 } else if( query->type != JSON_HASH ) {
3417 osrfAppSessionStatus(
3419 OSRF_STATUS_INTERNALSERVERERROR,
3420 "osrfMethodException",
3422 "Malformed query object"
3426 "%s: Query object is %s instead of JSON_HASH",
3428 json_type( query->type )
3433 // Determine what kind of query it purports to be, and dispatch accordingly.
3434 if( jsonObjectGetKey( query, "union" ) ||
3435 jsonObjectGetKey( query, "intersect" ) ||
3436 jsonObjectGetKey( query, "except" ) ) {
3437 return doCombo( ctx, query, flags );
3439 // It is presumably a SELECT query
3441 // Push a node onto the stack for the current query. Every level of
3442 // subquery gets its own QueryFrame on the Stack.
3445 // Build an SQL SELECT statement
3448 jsonObjectGetKey( query, "select" ),
3449 jsonObjectGetKey( query, "from" ),
3450 jsonObjectGetKey( query, "where" ),
3451 jsonObjectGetKey( query, "having" ),
3452 jsonObjectGetKey( query, "order_by" ),
3453 jsonObjectGetKey( query, "limit" ),
3454 jsonObjectGetKey( query, "offset" ),
3463 /* method context */ osrfMethodContext* ctx,
3465 /* SELECT */ jsonObject* selhash,
3466 /* FROM */ jsonObject* join_hash,
3467 /* WHERE */ jsonObject* search_hash,
3468 /* HAVING */ jsonObject* having_hash,
3469 /* ORDER BY */ jsonObject* order_hash,
3470 /* LIMIT */ jsonObject* limit,
3471 /* OFFSET */ jsonObject* offset,
3472 /* flags */ int flags
3474 const char* locale = osrf_message_get_last_locale();
3476 // general tmp objects
3477 const jsonObject* tmp_const;
3478 jsonObject* selclass = NULL;
3479 jsonObject* snode = NULL;
3480 jsonObject* onode = NULL;
3482 char* string = NULL;
3483 int from_function = 0;
3488 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3490 // punt if there's no FROM clause
3491 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3494 "%s: FROM clause is missing or empty",
3498 osrfAppSessionStatus(
3500 OSRF_STATUS_INTERNALSERVERERROR,
3501 "osrfMethodException",
3503 "FROM clause is missing or empty in JSON query"
3508 // the core search class
3509 const char* core_class = NULL;
3511 // get the core class -- the only key of the top level FROM clause, or a string
3512 if (join_hash->type == JSON_HASH) {
3513 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3514 snode = jsonIteratorNext( tmp_itr );
3516 // Populate the current QueryFrame with information
3517 // about the core class
3518 if( add_query_core( NULL, tmp_itr->key ) ) {
3520 osrfAppSessionStatus(
3522 OSRF_STATUS_INTERNALSERVERERROR,
3523 "osrfMethodException",
3525 "Unable to look up core class"
3529 core_class = curr_query->core.class_name;
3532 jsonObject* extra = jsonIteratorNext( tmp_itr );
3534 jsonIteratorFree( tmp_itr );
3537 // There shouldn't be more than one entry in join_hash
3541 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3545 osrfAppSessionStatus(
3547 OSRF_STATUS_INTERNALSERVERERROR,
3548 "osrfMethodException",
3550 "Malformed FROM clause in JSON query"
3552 return NULL; // Malformed join_hash; extra entry
3554 } else if (join_hash->type == JSON_ARRAY) {
3555 // We're selecting from a function, not from a table
3557 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
3560 } else if (join_hash->type == JSON_STRING) {
3561 // Populate the current QueryFrame with information
3562 // about the core class
3563 core_class = jsonObjectGetString( join_hash );
3565 if( add_query_core( NULL, core_class ) ) {
3567 osrfAppSessionStatus(
3569 OSRF_STATUS_INTERNALSERVERERROR,
3570 "osrfMethodException",
3572 "Unable to look up core class"
3580 "%s: FROM clause is unexpected JSON type: %s",
3582 json_type( join_hash->type )
3585 osrfAppSessionStatus(
3587 OSRF_STATUS_INTERNALSERVERERROR,
3588 "osrfMethodException",
3590 "Ill-formed FROM clause in JSON query"
3595 // Build the join clause, if any, while filling out the list
3596 // of joined classes in the current QueryFrame.
3597 char* join_clause = NULL;
3598 if( join_hash && ! from_function ) {
3600 join_clause = searchJOIN( join_hash, &curr_query->core );
3601 if( ! join_clause ) {
3603 osrfAppSessionStatus(
3605 OSRF_STATUS_INTERNALSERVERERROR,
3606 "osrfMethodException",
3608 "Unable to construct JOIN clause(s)"
3614 // For in case we don't get a select list
3615 jsonObject* defaultselhash = NULL;
3617 // if there is no select list, build a default select list ...
3618 if (!selhash && !from_function) {
3619 jsonObject* default_list = defaultSelectList( core_class );
3620 if( ! default_list ) {
3622 osrfAppSessionStatus(
3624 OSRF_STATUS_INTERNALSERVERERROR,
3625 "osrfMethodException",
3627 "Unable to build default SELECT clause in JSON query"
3629 free( join_clause );
3634 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3635 jsonObjectSetKey( selhash, core_class, default_list );
3638 // The SELECT clause can be encoded only by a hash
3639 if( !from_function && selhash->type != JSON_HASH ) {
3642 "%s: Expected JSON_HASH for SELECT clause; found %s",
3644 json_type( selhash->type )
3648 osrfAppSessionStatus(
3650 OSRF_STATUS_INTERNALSERVERERROR,
3651 "osrfMethodException",
3653 "Malformed SELECT clause in JSON query"
3655 free( join_clause );
3659 // If you see a null or wild card specifier for the core class, or an
3660 // empty array, replace it with a default SELECT list
3661 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3663 int default_needed = 0; // boolean
3664 if( JSON_STRING == tmp_const->type
3665 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3667 else if( JSON_NULL == tmp_const->type )
3670 if( default_needed ) {
3671 // Build a default SELECT list
3672 jsonObject* default_list = defaultSelectList( core_class );
3673 if( ! default_list ) {
3675 osrfAppSessionStatus(
3677 OSRF_STATUS_INTERNALSERVERERROR,
3678 "osrfMethodException",
3680 "Can't build default SELECT clause in JSON query"
3682 free( join_clause );
3687 jsonObjectSetKey( selhash, core_class, default_list );
3691 // temp buffers for the SELECT list and GROUP BY clause
3692 growing_buffer* select_buf = buffer_init(128);
3693 growing_buffer* group_buf = buffer_init(128);
3695 int aggregate_found = 0; // boolean
3697 // Build a select list
3698 if(from_function) // From a function we select everything
3699 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3702 // Build the SELECT list as SQL
3706 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3707 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3709 const char* cname = selclass_itr->key;
3711 // Make sure the target relation is in the FROM clause.
3713 // At this point join_hash is a step down from the join_hash we
3714 // received as a parameter. If the original was a JSON_STRING,
3715 // then json_hash is now NULL. If the original was a JSON_HASH,
3716 // then json_hash is now the first (and only) entry in it,
3717 // denoting the core class. We've already excluded the
3718 // possibility that the original was a JSON_ARRAY, because in
3719 // that case from_function would be non-NULL, and we wouldn't
3722 // If the current table alias isn't in scope, bail out
3723 ClassInfo* class_info = search_alias( cname );
3724 if( ! class_info ) {
3727 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3732 osrfAppSessionStatus(
3734 OSRF_STATUS_INTERNALSERVERERROR,
3735 "osrfMethodException",
3737 "Selected class not in FROM clause in JSON query"
3739 jsonIteratorFree( selclass_itr );
3740 buffer_free( select_buf );
3741 buffer_free( group_buf );
3742 if( defaultselhash ) jsonObjectFree( defaultselhash );
3743 free( join_clause );
3747 if( selclass->type != JSON_ARRAY ) {
3750 "%s: Malformed SELECT list for class \"%s\"; not an array",
3755 osrfAppSessionStatus(
3757 OSRF_STATUS_INTERNALSERVERERROR,
3758 "osrfMethodException",
3760 "Selected class not in FROM clause in JSON query"
3763 jsonIteratorFree( selclass_itr );
3764 buffer_free( select_buf );
3765 buffer_free( group_buf );
3766 if( defaultselhash ) jsonObjectFree( defaultselhash );
3767 free( join_clause );
3771 // Look up some attributes of the current class
3772 osrfHash* idlClass = class_info->class_def;
3773 osrfHash* class_field_set = class_info->fields;
3774 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3775 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3777 if( 0 == selclass->size ) {
3780 "%s: No columns selected from \"%s\"",
3786 // stitch together the column list for the current table alias...
3787 unsigned long field_idx = 0;
3788 jsonObject* selfield = NULL;
3789 while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3791 // If we need a separator comma, add one
3795 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3798 // if the field specification is a string, add it to the list
3799 if (selfield->type == JSON_STRING) {
3801 // Look up the field in the IDL
3802 const char* col_name = jsonObjectGetString( selfield );
3803 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3805 // No such field in current class
3808 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3814 osrfAppSessionStatus(
3816 OSRF_STATUS_INTERNALSERVERERROR,
3817 "osrfMethodException",
3819 "Selected column not defined in JSON query"
3821 jsonIteratorFree( selclass_itr );
3822 buffer_free( select_buf );
3823 buffer_free( group_buf );
3824 if( defaultselhash ) jsonObjectFree( defaultselhash );
3825 free( join_clause );
3827 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3828 // Virtual field not allowed
3831 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3837 osrfAppSessionStatus(
3839 OSRF_STATUS_INTERNALSERVERERROR,
3840 "osrfMethodException",
3842 "Selected column may not be virtual in JSON query"
3844 jsonIteratorFree( selclass_itr );
3845 buffer_free( select_buf );
3846 buffer_free( group_buf );
3847 if( defaultselhash ) jsonObjectFree( defaultselhash );
3848 free( join_clause );
3854 if (flags & DISABLE_I18N)
3857 i18n = osrfHashGet(field_def, "i18n");
3859 if( str_is_true( i18n ) ) {
3860 buffer_fadd( select_buf,
3861 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3862 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3864 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3867 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3870 // ... but it could be an object, in which case we check for a Field Transform
3871 } else if (selfield->type == JSON_HASH) {
3873 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3875 // Get the field definition from the IDL
3876 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3878 // No such field in current class
3881 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3887 osrfAppSessionStatus(
3889 OSRF_STATUS_INTERNALSERVERERROR,
3890 "osrfMethodException",
3892 "Selected column is not defined in JSON query"
3894 jsonIteratorFree( selclass_itr );
3895 buffer_free( select_buf );
3896 buffer_free( group_buf );
3897 if( defaultselhash ) jsonObjectFree( defaultselhash );
3898 free( join_clause );
3900 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3901 // No such field in current class
3904 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3910 osrfAppSessionStatus(
3912 OSRF_STATUS_INTERNALSERVERERROR,
3913 "osrfMethodException",
3915 "Selected column is virtual in JSON query"
3917 jsonIteratorFree( selclass_itr );
3918 buffer_free( select_buf );
3919 buffer_free( group_buf );
3920 if( defaultselhash ) jsonObjectFree( defaultselhash );
3921 free( join_clause );
3925 // Decide what to use as a column alias
3927 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3928 _alias = jsonObjectGetString( tmp_const );
3929 } else { // Use field name as the alias
3933 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3934 char* transform_str = searchFieldTransform(class_info->alias, field_def, selfield);
3935 if( transform_str ) {
3936 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3937 free(transform_str);
3940 osrfAppSessionStatus(
3942 OSRF_STATUS_INTERNALSERVERERROR,
3943 "osrfMethodException",
3945 "Unable to generate transform function in JSON query"
3947 jsonIteratorFree( selclass_itr );
3948 buffer_free( select_buf );
3949 buffer_free( group_buf );
3950 if( defaultselhash ) jsonObjectFree( defaultselhash );
3951 free( join_clause );
3958 if (flags & DISABLE_I18N)
3961 i18n = osrfHashGet(field_def, "i18n");
3963 if( str_is_true( i18n ) ) {
3964 buffer_fadd( select_buf,
3965 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3966 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3968 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3971 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3978 "%s: Selected item is unexpected JSON type: %s",
3980 json_type( selfield->type )
3983 osrfAppSessionStatus(
3985 OSRF_STATUS_INTERNALSERVERERROR,
3986 "osrfMethodException",
3988 "Ill-formed SELECT item in JSON query"
3990 jsonIteratorFree( selclass_itr );
3991 buffer_free( select_buf );
3992 buffer_free( group_buf );
3993 if( defaultselhash ) jsonObjectFree( defaultselhash );
3994 free( join_clause );
3998 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3999 if( obj_is_true( agg_obj ) )
4000 aggregate_found = 1;
4002 // Append a comma (except for the first one)
4003 // and add the column to a GROUP BY clause
4007 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4009 buffer_fadd(group_buf, " %d", sel_pos);
4013 if (is_agg->size || (flags & SELECT_DISTINCT)) {
4015 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
4016 if ( ! obj_is_true( aggregate_obj ) ) {
4020 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4023 buffer_fadd(group_buf, " %d", sel_pos);
4026 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
4030 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
4033 _column = searchFieldTransform(class_info->alias, field, selfield);
4034 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
4035 OSRF_BUFFER_ADD(group_buf, _column);
4036 _column = searchFieldTransform(class_info->alias, field, selfield);
4043 } // end while -- iterating across SELECT columns
4045 } // end while -- iterating across classes
4047 jsonIteratorFree(selclass_itr);
4051 char* col_list = buffer_release(select_buf);
4053 // Make sure the SELECT list isn't empty. This can happen, for example,
4054 // if we try to build a default SELECT clause from a non-core table.
4057 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
4059 osrfAppSessionStatus(
4061 OSRF_STATUS_INTERNALSERVERERROR,
4062 "osrfMethodException",
4064 "SELECT list is empty"
4067 buffer_free( group_buf );
4068 if( defaultselhash ) jsonObjectFree( defaultselhash );
4069 free( join_clause );
4074 if (from_function) table = searchValueTransform(join_hash);
4075 else table = strdup( curr_query->core.source_def );
4079 osrfAppSessionStatus(
4081 OSRF_STATUS_INTERNALSERVERERROR,
4082 "osrfMethodException",
4084 "Unable to identify table for core class"
4087 buffer_free( group_buf );
4088 if( defaultselhash ) jsonObjectFree( defaultselhash );
4089 free( join_clause );
4093 // Put it all together
4094 growing_buffer* sql_buf = buffer_init(128);
4095 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
4099 // Append the join clause, if any
4101 buffer_add(sql_buf, join_clause);
4105 char* order_by_list = NULL;
4106 char* having_buf = NULL;
4108 if (!from_function) {
4110 // Build a WHERE clause, if there is one
4111 if ( search_hash ) {
4112 buffer_add(sql_buf, " WHERE ");
4114 // and it's on the WHERE clause
4115 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4118 osrfAppSessionStatus(
4120 OSRF_STATUS_INTERNALSERVERERROR,
4121 "osrfMethodException",
4123 "Severe query error in WHERE predicate -- see error log for more details"
4126 buffer_free(group_buf);
4127 buffer_free(sql_buf);
4128 if (defaultselhash) jsonObjectFree(defaultselhash);
4132 buffer_add(sql_buf, pred);
4136 // Build a HAVING clause, if there is one
4137 if ( having_hash ) {
4139 // and it's on the the WHERE clause
4140 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
4142 if( ! having_buf ) {
4144 osrfAppSessionStatus(
4146 OSRF_STATUS_INTERNALSERVERERROR,
4147 "osrfMethodException",
4149 "Severe query error in HAVING predicate -- see error log for more details"
4152 buffer_free(group_buf);
4153 buffer_free(sql_buf);
4154 if (defaultselhash) jsonObjectFree(defaultselhash);
4159 growing_buffer* order_buf = NULL; // to collect ORDER BY list
4161 // Build an ORDER BY clause, if there is one
4162 if( NULL == order_hash )
4163 ; // No ORDER BY? do nothing
4164 else if( JSON_ARRAY == order_hash->type ) {
4165 // Array of field specifications, each specification being a
4166 // hash to define the class, field, and other details
4168 jsonObject* order_spec;
4169 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
4171 if( JSON_HASH != order_spec->type ) {
4172 osrfLogError(OSRF_LOG_MARK,
4173 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
4174 MODULENAME, json_type( order_spec->type ) );
4176 osrfAppSessionStatus(
4178 OSRF_STATUS_INTERNALSERVERERROR,
4179 "osrfMethodException",
4181 "Malformed ORDER BY clause -- see error log for more details"
4183 buffer_free( order_buf );
4185 buffer_free(group_buf);
4186 buffer_free(sql_buf);
4187 if (defaultselhash) jsonObjectFree(defaultselhash);
4191 const char* class_alias =
4192 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
4194 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
4197 OSRF_BUFFER_ADD(order_buf, ", ");
4199 order_buf = buffer_init(128);
4201 if( !field || !class_alias ) {
4202 osrfLogError(OSRF_LOG_MARK,
4203 "%s: Missing class or field name in field specification of ORDER BY clause",
4206 osrfAppSessionStatus(
4208 OSRF_STATUS_INTERNALSERVERERROR,
4209 "osrfMethodException",
4211 "Malformed ORDER BY clause -- see error log for more details"
4213 buffer_free( order_buf );
4215 buffer_free(group_buf);
4216 buffer_free(sql_buf);
4217 if (defaultselhash) jsonObjectFree(defaultselhash);
4221 ClassInfo* order_class_info = search_alias( class_alias );
4222 if( ! order_class_info ) {
4223 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4224 "not in FROM clause", MODULENAME, class_alias );
4226 osrfAppSessionStatus(
4228 OSRF_STATUS_INTERNALSERVERERROR,
4229 "osrfMethodException",
4231 "Invalid class referenced in ORDER BY clause -- see error log for more details"
4234 buffer_free(group_buf);
4235 buffer_free(sql_buf);
4236 if (defaultselhash) jsonObjectFree(defaultselhash);
4240 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4242 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4243 MODULENAME, class_alias, field );
4245 osrfAppSessionStatus(
4247 OSRF_STATUS_INTERNALSERVERERROR,
4248 "osrfMethodException",
4250 "Invalid field referenced in ORDER BY clause -- see error log for more details"
4253 buffer_free(group_buf);
4254 buffer_free(sql_buf);
4255 if (defaultselhash) jsonObjectFree(defaultselhash);
4257 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4258 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4259 MODULENAME, field );
4261 osrfAppSessionStatus(
4263 OSRF_STATUS_INTERNALSERVERERROR,
4264 "osrfMethodException",
4266 "Virtual field in ORDER BY clause -- see error log for more details"
4268 buffer_free( order_buf );
4270 buffer_free(group_buf);
4271 buffer_free(sql_buf);
4272 if (defaultselhash) jsonObjectFree(defaultselhash);
4276 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4277 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
4278 if( ! transform_str ) {
4280 osrfAppSessionStatus(
4282 OSRF_STATUS_INTERNALSERVERERROR,
4283 "osrfMethodException",
4285 "Severe query error in ORDER BY clause -- see error log for more details"
4287 buffer_free( order_buf );
4289 buffer_free(group_buf);
4290 buffer_free(sql_buf);
4291 if (defaultselhash) jsonObjectFree(defaultselhash);
4295 OSRF_BUFFER_ADD( order_buf, transform_str );
4296 free( transform_str );
4299 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4301 const char* direction =
4302 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4304 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4305 OSRF_BUFFER_ADD( order_buf, " DESC" );
4307 OSRF_BUFFER_ADD( order_buf, " ASC" );
4310 } else if( JSON_HASH == order_hash->type ) {
4311 // This hash is keyed on class alias. Each class has either
4312 // an array of field names or a hash keyed on field name.
4313 jsonIterator* class_itr = jsonNewIterator( order_hash );
4314 while ( (snode = jsonIteratorNext( class_itr )) ) {
4316 ClassInfo* order_class_info = search_alias( class_itr->key );
4317 if( ! order_class_info ) {
4318 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4319 MODULENAME, class_itr->key );
4321 osrfAppSessionStatus(
4323 OSRF_STATUS_INTERNALSERVERERROR,
4324 "osrfMethodException",
4326 "Invalid class referenced in ORDER BY clause -- see error log for more details"
4328 jsonIteratorFree( class_itr );
4329 buffer_free( order_buf );
4331 buffer_free(group_buf);
4332 buffer_free(sql_buf);
4333 if (defaultselhash) jsonObjectFree(defaultselhash);
4337 osrfHash* field_list_def = order_class_info->fields;
4339 if ( snode->type == JSON_HASH ) {
4341 // Hash is keyed on field names from the current class. For each field
4342 // there is another layer of hash to define the sorting details, if any,
4343 // or a string to indicate direction of sorting.
4344 jsonIterator* order_itr = jsonNewIterator( snode );
4345 while ( (onode = jsonIteratorNext( order_itr )) ) {
4347 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4349 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4350 MODULENAME, order_itr->key );
4352 osrfAppSessionStatus(
4354 OSRF_STATUS_INTERNALSERVERERROR,
4355 "osrfMethodException",
4357 "Invalid field in ORDER BY clause -- see error log for more details"
4359 jsonIteratorFree( order_itr );
4360 jsonIteratorFree( class_itr );
4361 buffer_free( order_buf );
4363 buffer_free(group_buf);
4364 buffer_free(sql_buf);
4365 if (defaultselhash) jsonObjectFree(defaultselhash);
4367 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4368 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4369 MODULENAME, order_itr->key );
4371 osrfAppSessionStatus(
4373 OSRF_STATUS_INTERNALSERVERERROR,
4374 "osrfMethodException",
4376 "Virtual field in ORDER BY clause -- see error log for more details"
4378 jsonIteratorFree( order_itr );
4379 jsonIteratorFree( class_itr );
4380 buffer_free( order_buf );
4382 buffer_free(group_buf);
4383 buffer_free(sql_buf);
4384 if (defaultselhash) jsonObjectFree(defaultselhash);
4388 const char* direction = NULL;
4389 if ( onode->type == JSON_HASH ) {
4390 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4391 string = searchFieldTransform(
4393 osrfHashGet( field_list_def, order_itr->key ),
4397 if( ctx ) osrfAppSessionStatus(
4399 OSRF_STATUS_INTERNALSERVERERROR,
4400 "osrfMethodException",
4402 "Severe query error in ORDER BY clause -- see error log for more details"
4404 jsonIteratorFree( order_itr );
4405 jsonIteratorFree( class_itr );
4407 buffer_free(group_buf);
4408 buffer_free(order_buf);
4409 buffer_free(sql_buf);
4410 if (defaultselhash) jsonObjectFree(defaultselhash);
4414 growing_buffer* field_buf = buffer_init(16);
4415 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4416 string = buffer_release(field_buf);
4419 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4420 const char* dir = jsonObjectGetString(tmp_const);
4421 if (!strncasecmp(dir, "d", 1)) {
4422 direction = " DESC";
4428 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4429 osrfLogError( OSRF_LOG_MARK,
4430 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4431 MODULENAME, json_type( onode->type ) );
4433 osrfAppSessionStatus(
4435 OSRF_STATUS_INTERNALSERVERERROR,
4436 "osrfMethodException",
4438 "Malformed ORDER BY clause -- see error log for more details"
4440 jsonIteratorFree( order_itr );
4441 jsonIteratorFree( class_itr );
4443 buffer_free(group_buf);
4444 buffer_free(order_buf);
4445 buffer_free(sql_buf);
4446 if (defaultselhash) jsonObjectFree(defaultselhash);
4450 string = strdup(order_itr->key);
4451 const char* dir = jsonObjectGetString(onode);
4452 if (!strncasecmp(dir, "d", 1)) {
4453 direction = " DESC";
4460 OSRF_BUFFER_ADD(order_buf, ", ");
4462 order_buf = buffer_init(128);
4464 OSRF_BUFFER_ADD(order_buf, string);
4468 OSRF_BUFFER_ADD(order_buf, direction);
4472 jsonIteratorFree(order_itr);
4474 } else if ( snode->type == JSON_ARRAY ) {
4476 // Array is a list of fields from the current class
4477 unsigned long order_idx = 0;
4478 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4480 const char* _f = jsonObjectGetString( onode );
4482 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4484 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4487 osrfAppSessionStatus(
4489 OSRF_STATUS_INTERNALSERVERERROR,
4490 "osrfMethodException",
4492 "Invalid field in ORDER BY clause -- see error log for more details"
4494 jsonIteratorFree( class_itr );
4495 buffer_free( order_buf );
4497 buffer_free(group_buf);
4498 buffer_free(sql_buf);
4499 if (defaultselhash) jsonObjectFree(defaultselhash);
4501 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4502 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4505 osrfAppSessionStatus(
4507 OSRF_STATUS_INTERNALSERVERERROR,
4508 "osrfMethodException",
4510 "Virtual field in ORDER BY clause -- see error log for more details"
4512 jsonIteratorFree( class_itr );
4513 buffer_free( order_buf );
4515 buffer_free(group_buf);
4516 buffer_free(sql_buf);
4517 if (defaultselhash) jsonObjectFree(defaultselhash);
4522 OSRF_BUFFER_ADD(order_buf, ", ");
4524 order_buf = buffer_init(128);
4526 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
4530 // IT'S THE OOOOOOOOOOOLD STYLE!
4532 osrfLogError(OSRF_LOG_MARK,
4533 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
4535 osrfAppSessionStatus(
4537 OSRF_STATUS_INTERNALSERVERERROR,
4538 "osrfMethodException",
4540 "Severe query error -- see error log for more details"
4545 buffer_free(group_buf);
4546 buffer_free(order_buf);
4547 buffer_free(sql_buf);
4548 if (defaultselhash) jsonObjectFree(defaultselhash);
4549 jsonIteratorFree(class_itr);
4553 jsonIteratorFree( class_itr );
4555 osrfLogError(OSRF_LOG_MARK,
4556 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4557 MODULENAME, json_type( order_hash->type ) );
4559 osrfAppSessionStatus(
4561 OSRF_STATUS_INTERNALSERVERERROR,
4562 "osrfMethodException",
4564 "Malformed ORDER BY clause -- see error log for more details"
4566 buffer_free( order_buf );
4568 buffer_free(group_buf);
4569 buffer_free(sql_buf);
4570 if (defaultselhash) jsonObjectFree(defaultselhash);
4575 order_by_list = buffer_release( order_buf );
4579 string = buffer_release(group_buf);
4581 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4582 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4583 OSRF_BUFFER_ADD( sql_buf, string );
4588 if( having_buf && *having_buf ) {
4589 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4590 OSRF_BUFFER_ADD( sql_buf, having_buf );
4594 if( order_by_list ) {
4596 if ( *order_by_list ) {
4597 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4598 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4601 free( order_by_list );
4605 const char* str = jsonObjectGetString(limit);
4606 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
4610 const char* str = jsonObjectGetString(offset);
4611 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
4614 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4616 if (defaultselhash) jsonObjectFree(defaultselhash);
4618 return buffer_release(sql_buf);
4620 } // end of SELECT()
4622 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4624 const char* locale = osrf_message_get_last_locale();
4626 osrfHash* fields = osrfHashGet(meta, "fields");
4627 char* core_class = osrfHashGet(meta, "classname");
4629 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4631 jsonObject* node = NULL;
4632 jsonObject* snode = NULL;
4633 jsonObject* onode = NULL;
4634 const jsonObject* _tmp = NULL;
4635 jsonObject* selhash = NULL;
4636 jsonObject* defaultselhash = NULL;
4638 growing_buffer* sql_buf = buffer_init(128);
4639 growing_buffer* select_buf = buffer_init(128);
4641 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4642 defaultselhash = jsonNewObjectType(JSON_HASH);
4643 selhash = defaultselhash;
4646 // If there's no SELECT list for the core class, build one
4647 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4648 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4650 // Add every non-virtual field to the field list
4651 osrfHash* field_def = NULL;
4652 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4653 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4654 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4655 const char* field = osrfHashIteratorKey( field_itr );
4656 jsonObjectPush( field_list, jsonNewObject( field ) );
4659 osrfHashIteratorFree( field_itr );
4660 jsonObjectSetKey( selhash, core_class, field_list );
4664 jsonIterator* class_itr = jsonNewIterator( selhash );
4665 while ( (snode = jsonIteratorNext( class_itr )) ) {
4667 const char* cname = class_itr->key;
4668 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4669 if (!idlClass) continue;
4671 if (strcmp(core_class,class_itr->key)) {
4672 if (!join_hash) continue;
4674 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4676 jsonObjectFree(found);
4680 jsonObjectFree(found);
4683 jsonIterator* select_itr = jsonNewIterator( snode );
4684 while ( (node = jsonIteratorNext( select_itr )) ) {
4685 const char* item_str = jsonObjectGetString( node );
4686 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4687 char* fname = osrfHashGet(field, "name");
4689 if (!field) continue;
4694 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
4699 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4700 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4703 i18n = osrfHashGet(field, "i18n");
4705 if( str_is_true( i18n ) ) {
4706 char* pkey = osrfHashGet(idlClass, "primarykey");
4707 char* tname = osrfHashGet(idlClass, "tablename");
4709 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);
4711 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4714 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4718 jsonIteratorFree(select_itr);
4721 jsonIteratorFree(class_itr);
4723 char* col_list = buffer_release(select_buf);
4724 char* table = getRelation(meta);
4726 table = strdup( "(null)" );
4728 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4732 // Clear the query stack (as a fail-safe precaution against possible
4733 // leftover garbage); then push the first query frame onto the stack.
4734 clear_query_stack();
4736 if( add_query_core( NULL, core_class ) ) {
4738 osrfAppSessionStatus(
4740 OSRF_STATUS_INTERNALSERVERERROR,
4741 "osrfMethodException",
4743 "Unable to build query frame for core class"
4749 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4750 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
4751 OSRF_BUFFER_ADD(sql_buf, join_clause);
4755 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4756 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4758 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4760 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4762 osrfAppSessionStatus(
4764 OSRF_STATUS_INTERNALSERVERERROR,
4765 "osrfMethodException",
4767 "Severe query error -- see error log for more details"
4769 buffer_free(sql_buf);
4770 if(defaultselhash) jsonObjectFree(defaultselhash);
4771 clear_query_stack();
4774 buffer_add(sql_buf, pred);
4779 char* string = NULL;
4780 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4782 growing_buffer* order_buf = buffer_init(128);
4785 jsonIterator* class_itr = jsonNewIterator( _tmp );
4786 while ( (snode = jsonIteratorNext( class_itr )) ) {
4788 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4791 if ( snode->type == JSON_HASH ) {
4793 jsonIterator* order_itr = jsonNewIterator( snode );
4794 while ( (onode = jsonIteratorNext( order_itr )) ) {
4796 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4797 class_itr->key, order_itr->key );
4801 char* direction = NULL;
4802 if ( onode->type == JSON_HASH ) {
4803 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4804 string = searchFieldTransform( class_itr->key, field_def, onode );
4806 osrfAppSessionStatus(
4808 OSRF_STATUS_INTERNALSERVERERROR,
4809 "osrfMethodException",
4811 "Severe query error in ORDER BY clause -- see error log for more details"
4813 jsonIteratorFree( order_itr );
4814 jsonIteratorFree( class_itr );
4815 buffer_free( order_buf );
4816 buffer_free( sql_buf );
4817 if( defaultselhash ) jsonObjectFree( defaultselhash );
4818 clear_query_stack();
4822 growing_buffer* field_buf = buffer_init(16);
4823 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4824 string = buffer_release(field_buf);
4827 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4828 const char* dir = jsonObjectGetString(_tmp);
4829 if (!strncasecmp(dir, "d", 1)) {
4830 direction = " DESC";
4837 string = strdup(order_itr->key);
4838 const char* dir = jsonObjectGetString(onode);
4839 if (!strncasecmp(dir, "d", 1)) {
4840 direction = " DESC";
4849 buffer_add(order_buf, ", ");
4852 buffer_add(order_buf, string);
4856 buffer_add(order_buf, direction);
4861 jsonIteratorFree(order_itr);
4864 const char* str = jsonObjectGetString(snode);
4865 buffer_add(order_buf, str);
4871 jsonIteratorFree(class_itr);
4873 string = buffer_release(order_buf);
4876 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4877 OSRF_BUFFER_ADD( sql_buf, string );
4883 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4884 const char* str = jsonObjectGetString(_tmp);
4892 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4894 const char* str = jsonObjectGetString(_tmp);
4903 if (defaultselhash) jsonObjectFree(defaultselhash);
4904 clear_query_stack();
4906 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4907 return buffer_release(sql_buf);
4910 int doJSONSearch ( osrfMethodContext* ctx ) {
4911 if(osrfMethodVerifyContext( ctx )) {
4912 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4916 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
4921 dbhandle = writehandle;
4923 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4927 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4928 flags |= SELECT_DISTINCT;
4930 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4931 flags |= DISABLE_I18N;
4933 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4934 clear_query_stack(); // a possibly needless precaution
4935 char* sql = buildQuery( ctx, hash, flags );
4936 clear_query_stack();
4943 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4944 dbi_result result = dbi_conn_query(dbhandle, sql);
4947 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4949 if (dbi_result_first_row(result)) {
4950 /* JSONify the result */
4951 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4954 jsonObject* return_val = oilsMakeJSONFromResult( result );
4955 osrfAppRespond( ctx, return_val );
4956 jsonObjectFree( return_val );
4957 } while (dbi_result_next_row(result));
4960 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4963 osrfAppRespondComplete( ctx, NULL );
4965 /* clean up the query */
4966 dbi_result_free(result);
4970 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4971 osrfAppSessionStatus(
4973 OSRF_STATUS_INTERNALSERVERERROR,
4974 "osrfMethodException",
4976 "Severe query error -- see error log for more details"
4984 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4985 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4988 dbhandle = writehandle;
4990 osrfHash* links = osrfHashGet(meta, "links");
4991 osrfHash* fields = osrfHashGet(meta, "fields");
4992 char* core_class = osrfHashGet(meta, "classname");
4993 char* pkey = osrfHashGet(meta, "primarykey");
4995 const jsonObject* _tmp;
4998 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
5000 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
5005 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
5007 dbi_result result = dbi_conn_query(dbhandle, sql);
5008 if( NULL == result ) {
5009 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
5010 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
5011 osrfAppSessionStatus(
5013 OSRF_STATUS_INTERNALSERVERERROR,
5014 "osrfMethodException",
5016 "Severe query error -- see error log for more details"
5023 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
5026 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
5027 osrfHash* dedup = osrfNewHash();
5029 if (dbi_result_first_row(result)) {
5030 /* JSONify the result */
5031 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
5033 obj = oilsMakeFieldmapperFromResult( result, meta );
5034 char* pkey_val = oilsFMGetString( obj, pkey );
5035 if ( osrfHashGet( dedup, pkey_val ) ) {
5036 jsonObjectFree(obj);
5039 osrfHashSet( dedup, pkey_val, pkey_val );
5040 jsonObjectPush(res_list, obj);
5042 } while (dbi_result_next_row(result));
5044 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
5048 osrfHashFree(dedup);
5049 /* clean up the query */
5050 dbi_result_free(result);
5053 if (res_list->size && query_hash) {
5054 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
5056 int x = (int)jsonObjectGetNumber(_tmp);
5057 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
5059 const jsonObject* temp_blob;
5060 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
5062 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
5063 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
5065 osrfStringArray* link_fields = NULL;
5068 if (flesh_fields->size == 1) {
5069 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
5070 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
5075 link_fields = osrfNewStringArray(1);
5076 jsonIterator* _i = jsonNewIterator( flesh_fields );
5077 while ((_f = jsonIteratorNext( _i ))) {
5078 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
5080 jsonIteratorFree(_i);
5085 unsigned long res_idx = 0;
5086 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
5089 const char* link_field;
5091 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
5093 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
5095 osrfHash* kid_link = osrfHashGet(links, link_field);
5096 if (!kid_link) continue;
5098 osrfHash* field = osrfHashGet(fields, link_field);
5099 if (!field) continue;
5101 osrfHash* value_field = field;
5103 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
5104 if (!kid_idl) continue;
5106 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
5107 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
5110 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
5111 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
5114 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
5116 if (link_map->size > 0) {
5117 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
5120 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
5125 osrfHashGet(kid_link, "class"),
5132 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
5133 osrfHashGet(kid_link, "field"),
5134 osrfHashGet(kid_link, "class"),
5135 osrfHashGet(kid_link, "key"),
5136 osrfHashGet(kid_link, "reltype")
5139 const char* search_key = jsonObjectGetString(
5142 atoi( osrfHashGet(value_field, "array_position") )
5147 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
5151 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
5153 // construct WHERE clause
5154 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
5157 osrfHashGet(kid_link, "key"),
5158 jsonNewObject( search_key )
5161 // construct the rest of the query
5162 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
5163 jsonObjectSetKey( rest_of_query, "flesh",
5164 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
5168 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
5170 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
5171 jsonObjectSetKey( rest_of_query, "order_by",
5172 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
5176 if (jsonObjectGetKeyConst(query_hash, "select")) {
5177 jsonObjectSetKey( rest_of_query, "select",
5178 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
5182 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
5183 where_clause, rest_of_query, err);
5185 jsonObjectFree( where_clause );
5186 jsonObjectFree( rest_of_query );
5189 osrfStringArrayFree(link_fields);
5190 jsonObjectFree(res_list);
5191 jsonObjectFree(flesh_blob);
5195 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
5197 jsonObject* X = NULL;
5198 if ( link_map->size > 0 && kids->size > 0 ) {
5200 kids = jsonNewObjectType(JSON_ARRAY);
5202 jsonObject* _k_node;
5203 unsigned long res_idx = 0;
5204 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
5210 (unsigned long)atoi(
5216 osrfHashGet(kid_link, "class")
5220 osrfStringArrayGetString( link_map, 0 )
5228 } // end while loop traversing X
5231 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
5232 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
5235 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5236 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
5240 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
5241 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
5244 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5245 jsonObjectClone( kids )
5250 jsonObjectFree(kids);
5254 jsonObjectFree( kids );
5256 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
5257 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
5260 } // end while loop traversing res_list
5261 jsonObjectFree( flesh_blob );
5262 osrfStringArrayFree(link_fields);
5271 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
5273 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5275 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
5277 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
5280 if (!verifyObjectClass(ctx, target)) {
5285 if( getXactId( ctx ) == NULL ) {
5286 osrfAppSessionStatus(
5288 OSRF_STATUS_BADREQUEST,
5289 "osrfMethodException",
5291 "No active transaction -- required for UPDATE"
5297 // The following test is harmless but redundant. If a class is
5298 // readonly, we don't register an update method for it.
5299 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5300 osrfAppSessionStatus(
5302 OSRF_STATUS_BADREQUEST,
5303 "osrfMethodException",
5305 "Cannot UPDATE readonly class"
5311 dbhandle = writehandle;
5312 const char* trans_id = getXactId( ctx );
5314 // Set the last_xact_id
5315 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5317 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5318 trans_id, target->classname, index);
5319 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
5322 char* pkey = osrfHashGet(meta, "primarykey");
5323 osrfHash* fields = osrfHashGet(meta, "fields");
5325 char* id = oilsFMGetString( target, pkey );
5329 "%s updating %s object with %s = %s",
5331 osrfHashGet(meta, "fieldmapper"),
5336 growing_buffer* sql = buffer_init(128);
5337 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
5340 osrfHash* field_def = NULL;
5341 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5342 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
5344 // Skip virtual fields, and the primary key
5345 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5348 const char* field_name = osrfHashIteratorKey( field_itr );
5349 if( ! strcmp( field_name, pkey ) )
5352 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5354 int value_is_numeric = 0; // boolean
5356 if (field_object && field_object->classname) {
5357 value = oilsFMGetString(
5359 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
5361 } else if( field_object && JSON_BOOL == field_object->type ) {
5362 if( jsonBoolIsTrue( field_object ) )
5363 value = strdup( "t" );
5365 value = strdup( "f" );
5367 value = jsonObjectToSimpleString( field_object );
5368 if( field_object && JSON_NUMBER == field_object->type )
5369 value_is_numeric = 1;
5372 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5373 osrfHashGet(meta, "fieldmapper"), field_name, value);
5375 if (!field_object || field_object->type == JSON_NULL) {
5376 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
5377 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5378 if (first) first = 0;
5379 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5380 buffer_fadd( sql, " %s = NULL", field_name );
5383 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5384 if (first) first = 0;
5385 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5387 const char* numtype = get_datatype( field_def );
5388 if ( !strncmp( numtype, "INT", 3 ) ) {
5389 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
5390 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
5391 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
5393 // Must really be intended as a string, so quote it
5394 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5395 buffer_fadd( sql, " %s = %s", field_name, value );
5397 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5398 osrfAppSessionStatus(
5400 OSRF_STATUS_INTERNALSERVERERROR,
5401 "osrfMethodException",
5403 "Error quoting string -- please see the error log for more details"
5407 osrfHashIteratorFree( field_itr );
5414 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5417 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5418 if (first) first = 0;
5419 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5420 buffer_fadd( sql, " %s = %s", field_name, value );
5423 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5424 osrfAppSessionStatus(
5426 OSRF_STATUS_INTERNALSERVERERROR,
5427 "osrfMethodException",
5429 "Error quoting string -- please see the error log for more details"
5433 osrfHashIteratorFree( field_itr );
5444 osrfHashIteratorFree( field_itr );
5446 jsonObject* obj = jsonNewObject(id);
5448 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5449 dbi_conn_quote_string(dbhandle, &id);
5451 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5453 char* query = buffer_release(sql);
5454 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
5456 dbi_result result = dbi_conn_query(dbhandle, query);
5460 jsonObjectFree(obj);
5461 obj = jsonNewObject(NULL);
5464 "%s ERROR updating %s object with %s = %s",
5466 osrfHashGet(meta, "fieldmapper"),
5477 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
5479 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5481 if( getXactId( ctx ) == NULL ) {
5482 osrfAppSessionStatus(
5484 OSRF_STATUS_BADREQUEST,
5485 "osrfMethodException",
5487 "No active transaction -- required for DELETE"
5493 // The following test is harmless but redundant. If a class is
5494 // readonly, we don't register a delete method for it.
5495 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5496 osrfAppSessionStatus(
5498 OSRF_STATUS_BADREQUEST,
5499 "osrfMethodException",
5501 "Cannot DELETE readonly class"
5507 dbhandle = writehandle;
5511 char* pkey = osrfHashGet(meta, "primarykey");
5519 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
5520 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5525 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5528 if (!verifyObjectPCRUD( ctx, NULL )) {
5533 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
5538 "%s deleting %s object with %s = %s",
5540 osrfHashGet(meta, "fieldmapper"),
5545 obj = jsonNewObject(id);
5547 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5548 dbi_conn_quote_string(writehandle, &id);
5550 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
5553 jsonObjectFree(obj);
5554 obj = jsonNewObject(NULL);
5557 "%s ERROR deleting %s object with %s = %s",
5559 osrfHashGet(meta, "fieldmapper"),
5572 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5573 if(!(result && meta)) return jsonNULL;
5575 jsonObject* object = jsonNewObject(NULL);
5576 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
5578 osrfHash* fields = osrfHashGet(meta, "fields");
5580 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
5584 char dt_string[256];
5588 int columnIndex = 1;
5590 unsigned short type;
5591 const char* columnName;
5593 /* cycle through the column list */
5594 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5596 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5598 fmIndex = -1; // reset the position
5600 /* determine the field type and storage attributes */
5601 type = dbi_result_get_field_type_idx(result, columnIndex);
5602 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5604 /* fetch the fieldmapper index */
5605 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
5607 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
5610 const char* pos = (char*)osrfHashGet(_f, "array_position");
5611 if ( !pos ) continue;
5613 fmIndex = atoi( pos );
5614 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
5619 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5620 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
5625 case DBI_TYPE_INTEGER :
5627 if( attr & DBI_INTEGER_SIZE8 )
5628 jsonObjectSetIndex( object, fmIndex,
5629 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
5631 jsonObjectSetIndex( object, fmIndex,
5632 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
5636 case DBI_TYPE_DECIMAL :
5637 jsonObjectSetIndex( object, fmIndex,
5638 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
5641 case DBI_TYPE_STRING :
5647 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
5652 case DBI_TYPE_DATETIME :
5654 memset(dt_string, '\0', sizeof(dt_string));
5655 memset(&gmdt, '\0', sizeof(gmdt));
5657 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5660 if (!(attr & DBI_DATETIME_DATE)) {
5661 gmtime_r( &_tmp_dt, &gmdt );
5662 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5663 } else if (!(attr & DBI_DATETIME_TIME)) {
5664 localtime_r( &_tmp_dt, &gmdt );
5665 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5667 localtime_r( &_tmp_dt, &gmdt );
5668 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5671 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
5675 case DBI_TYPE_BINARY :
5676 osrfLogError( OSRF_LOG_MARK,
5677 "Can't do binary at column %s : index %d", columnName, columnIndex);
5686 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5687 if(!result) return jsonNULL;
5689 jsonObject* object = jsonNewObject(NULL);
5692 char dt_string[256];
5696 int columnIndex = 1;
5698 unsigned short type;
5699 const char* columnName;
5701 /* cycle through the column list */
5702 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5704 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5706 fmIndex = -1; // reset the position
5708 /* determine the field type and storage attributes */
5709 type = dbi_result_get_field_type_idx(result, columnIndex);
5710 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5712 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5713 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
5718 case DBI_TYPE_INTEGER :
5720 if( attr & DBI_INTEGER_SIZE8 )
5721 jsonObjectSetKey( object, columnName,
5722 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
5724 jsonObjectSetKey( object, columnName,
5725 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
5728 case DBI_TYPE_DECIMAL :
5729 jsonObjectSetKey( object, columnName,
5730 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
5733 case DBI_TYPE_STRING :
5734 jsonObjectSetKey( object, columnName,
5735 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
5738 case DBI_TYPE_DATETIME :
5740 memset(dt_string, '\0', sizeof(dt_string));
5741 memset(&gmdt, '\0', sizeof(gmdt));
5743 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5746 if (!(attr & DBI_DATETIME_DATE)) {
5747 gmtime_r( &_tmp_dt, &gmdt );
5748 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5749 } else if (!(attr & DBI_DATETIME_TIME)) {
5750 localtime_r( &_tmp_dt, &gmdt );
5751 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5753 localtime_r( &_tmp_dt, &gmdt );
5754 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5757 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5760 case DBI_TYPE_BINARY :
5761 osrfLogError( OSRF_LOG_MARK,
5762 "Can't do binary at column %s : index %d", columnName, columnIndex );
5766 } // end while loop traversing result
5771 // Interpret a string as true or false
5772 static int str_is_true( const char* str ) {
5773 if( NULL == str || strcasecmp( str, "true" ) )
5779 // Interpret a jsonObject as true or false
5780 static int obj_is_true( const jsonObject* obj ) {
5783 else switch( obj->type )
5791 if( strcasecmp( obj->value.s, "true" ) )
5795 case JSON_NUMBER : // Support 1/0 for perl's sake
5796 if( jsonObjectGetNumber( obj ) == 1.0 )
5805 // Translate a numeric code into a text string identifying a type of
5806 // jsonObject. To be used for building error messages.
5807 static const char* json_type( int code ) {
5813 return "JSON_ARRAY";
5815 return "JSON_STRING";
5817 return "JSON_NUMBER";
5823 return "(unrecognized)";
5827 // Extract the "primitive" attribute from an IDL field definition.
5828 // If we haven't initialized the app, then we must be running in
5829 // some kind of testbed. In that case, default to "string".
5830 static const char* get_primitive( osrfHash* field ) {
5831 const char* s = osrfHashGet( field, "primitive" );
5833 if( child_initialized )
5836 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5838 osrfHashGet( field, "name" )
5846 // Extract the "datatype" attribute from an IDL field definition.
5847 // If we haven't initialized the app, then we must be running in
5848 // some kind of testbed. In that case, default to to NUMERIC,
5849 // since we look at the datatype only for numbers.
5850 static const char* get_datatype( osrfHash* field ) {
5851 const char* s = osrfHashGet( field, "datatype" );
5853 if( child_initialized )
5856 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5858 osrfHashGet( field, "name" )
5867 If the input string is potentially a valid SQL identifier, return 1.
5870 Purpose: to prevent certain kinds of SQL injection. To that end we
5871 don't necessarily need to follow all the rules exactly, such as requiring
5872 that the first character not be a digit.
5874 We allow leading and trailing white space. In between, we do not allow
5875 punctuation (except for underscores and dollar signs), control
5876 characters, or embedded white space.
5878 More pedantically we should allow quoted identifiers containing arbitrary
5879 characters, but for the foreseeable future such quoted identifiers are not
5880 likely to be an issue.
5882 static int is_identifier( const char* s) {
5886 // Skip leading white space
5887 while( isspace( (unsigned char) *s ) )
5891 return 0; // Nothing but white space? Not okay.
5893 // Check each character until we reach white space or
5894 // end-of-string. Letters, digits, underscores, and
5895 // dollar signs are okay. With the exception of periods
5896 // (as in schema.identifier), control characters and other
5897 // punctuation characters are not okay. Anything else
5898 // is okay -- it could for example be part of a multibyte
5899 // UTF8 character such as a letter with diacritical marks,
5900 // and those are allowed.
5902 if( isalnum( (unsigned char) *s )
5906 ; // Fine; keep going
5907 else if( ispunct( (unsigned char) *s )
5908 || iscntrl( (unsigned char) *s ) )
5911 } while( *s && ! isspace( (unsigned char) *s ) );
5913 // If we found any white space in the above loop,
5914 // the rest had better be all white space.
5916 while( isspace( (unsigned char) *s ) )
5920 return 0; // White space was embedded within non-white space
5926 Determine whether to accept a character string as a comparison operator.
5927 Return 1 if it's good, or 0 if it's bad.
5929 We don't validate it for real. We just make sure that it doesn't contain
5930 any semicolons or white space (with special exceptions for a few specific
5931 operators). The idea is to block certain kinds of SQL injection. If it
5932 has no semicolons or white space but it's still not a valid operator, then
5933 the database will complain.
5935 Another approach would be to compare the string against a short list of
5936 approved operators. We don't do that because we want to allow custom
5937 operators like ">100*", which would be difficult or impossible to
5938 express otherwise in a JSON query.
5940 static int is_good_operator( const char* op ) {
5941 if( !op ) return 0; // Sanity check
5945 if( isspace( (unsigned char) *s ) ) {
5946 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
5947 // and IS NOT DISTINCT FROM.
5948 if( !strcasecmp( op, "similar to" ) )
5950 else if( !strcasecmp( op, "is distinct from" ) )
5952 else if( !strcasecmp( op, "is not distinct from" ) )
5957 else if( ';' == *s )
5964 /* ----------------------------------------------------------------------------------
5965 The following machinery supports a stack of query frames for use by SELECT().
5967 A query frame caches information about one level of a SELECT query. When we enter
5968 a subquery, we push another query frame onto the stack, and pop it off when we leave.
5970 The query frame stores information about the core class, and about any joined classes
5973 The main purpose is to map table aliases to classes and tables, so that a query can
5974 join to the same table more than once. A secondary goal is to reduce the number of
5975 lookups in the IDL by caching the results.
5976 ----------------------------------------------------------------------------------*/
5978 #define STATIC_CLASS_INFO_COUNT 3
5980 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
5982 /* ---------------------------------------------------------------------------
5983 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5985 ---------------------------------------------------------------------------*/
5986 static ClassInfo* allocate_class_info( void ) {
5987 // In order to reduce the number of mallocs and frees, we return a static
5988 // instance of ClassInfo, if we can find one that we're not already using.
5989 // We rely on the fact that the compiler will implicitly initialize the
5990 // static instances so that in_use == 0.
5993 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5994 if( ! static_class_info[ i ].in_use ) {
5995 static_class_info[ i ].in_use = 1;
5996 return static_class_info + i;
6000 // The static ones are all in use. Malloc one.
6002 return safe_malloc( sizeof( ClassInfo ) );
6005 /* --------------------------------------------------------------------------
6006 Free any malloc'd memory owned by a ClassInfo; return it to a pristine state
6007 ---------------------------------------------------------------------------*/
6008 static void clear_class_info( ClassInfo* info ) {
6013 // Free any malloc'd strings
6015 if( info->alias != info->alias_store )
6016 free( info->alias );
6018 if( info->class_name != info->class_name_store )
6019 free( info->class_name );
6021 free( info->source_def );
6023 info->alias = info->class_name = info->source_def = NULL;
6027 /* --------------------------------------------------------------------------
6028 Deallocate a ClassInfo and everything it owns
6029 ---------------------------------------------------------------------------*/
6030 static void free_class_info( ClassInfo* info ) {
6035 clear_class_info( info );
6037 // If it's one of the static instances, just mark it as not in use
6040 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
6041 if( info == static_class_info + i ) {
6042 static_class_info[ i ].in_use = 0;
6047 // Otherwise it must have been malloc'd, so free it
6052 /* --------------------------------------------------------------------------
6053 Populate an already-allocated ClassInfo. Return 0 if successful, 1 if not.
6054 ---------------------------------------------------------------------------*/
6055 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
6058 osrfLogError( OSRF_LOG_MARK,
6059 "%s ERROR: No ClassInfo available to populate", MODULENAME );
6060 info->alias = info->class_name = info->source_def = NULL;
6061 info->class_def = info->fields = info->links = NULL;
6066 osrfLogError( OSRF_LOG_MARK,
6067 "%s ERROR: No class name provided for lookup", MODULENAME );
6068 info->alias = info->class_name = info->source_def = NULL;
6069 info->class_def = info->fields = info->links = NULL;
6073 // Alias defaults to class name if not supplied
6074 if( ! alias || ! alias[ 0 ] )
6077 // Look up class info in the IDL
6078 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
6080 osrfLogError( OSRF_LOG_MARK,
6081 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
6082 info->alias = info->class_name = info->source_def = NULL;
6083 info->class_def = info->fields = info->links = NULL;
6085 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
6086 osrfLogError( OSRF_LOG_MARK,
6087 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
6088 info->alias = info->class_name = info->source_def = NULL;
6089 info->class_def = info->fields = info->links = NULL;
6093 osrfHash* links = osrfHashGet( class_def, "links" );
6095 osrfLogError( OSRF_LOG_MARK,
6096 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
6097 info->alias = info->class_name = info->source_def = NULL;
6098 info->class_def = info->fields = info->links = NULL;
6102 osrfHash* fields = osrfHashGet( class_def, "fields" );
6104 osrfLogError( OSRF_LOG_MARK,
6105 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
6106 info->alias = info->class_name = info->source_def = NULL;
6107 info->class_def = info->fields = info->links = NULL;
6111 char* source_def = getRelation( class_def );
6115 // We got everything we need, so populate the ClassInfo
6116 if( strlen( alias ) > ALIAS_STORE_SIZE )
6117 info->alias = strdup( alias );
6119 strcpy( info->alias_store, alias );
6120 info->alias = info->alias_store;
6123 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
6124 info->class_name = strdup( class );
6126 strcpy( info->class_name_store, class );
6127 info->class_name = info->class_name_store;
6130 info->source_def = source_def;
6132 info->class_def = class_def;
6133 info->links = links;
6134 info->fields = fields;
6139 #define STATIC_FRAME_COUNT 3
6141 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
6143 /* ---------------------------------------------------------------------------
6144 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
6146 ---------------------------------------------------------------------------*/
6147 static QueryFrame* allocate_frame( void ) {
6148 // In order to reduce the number of mallocs and frees, we return a static
6149 // instance of QueryFrame, if we can find one that we're not already using.
6150 // We rely on the fact that the compiler will implicitly initialize the
6151 // static instances so that in_use == 0.
6154 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6155 if( ! static_frame[ i ].in_use ) {
6156 static_frame[ i ].in_use = 1;
6157 return static_frame + i;
6161 // The static ones are all in use. Malloc one.
6163 return safe_malloc( sizeof( QueryFrame ) );
6166 /* --------------------------------------------------------------------------
6167 Free a QueryFrame, and all the memory it owns.
6168 ---------------------------------------------------------------------------*/
6169 static void free_query_frame( QueryFrame* frame ) {
6174 clear_class_info( &frame->core );
6176 // Free the join list
6178 ClassInfo* info = frame->join_list;
6181 free_class_info( info );
6185 frame->join_list = NULL;
6188 // If the frame is a static instance, just mark it as unused
6190 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
6191 if( frame == static_frame + i ) {
6192 static_frame[ i ].in_use = 0;
6197 // Otherwise it must have been malloc'd, so free it
6202 /* --------------------------------------------------------------------------
6203 Search a given QueryFrame for a specified alias. If you find it, return
6204 a pointer to the corresponding ClassInfo. Otherwise return NULL.
6205 ---------------------------------------------------------------------------*/
6206 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
6207 if( ! frame || ! target ) {
6211 ClassInfo* found_class = NULL;
6213 if( !strcmp( target, frame->core.alias ) )
6214 return &(frame->core);
6216 ClassInfo* curr_class = frame->join_list;
6217 while( curr_class ) {
6218 if( strcmp( target, curr_class->alias ) )
6219 curr_class = curr_class->next;
6221 found_class = curr_class;
6230 /* --------------------------------------------------------------------------
6231 Push a new (blank) QueryFrame onto the stack.
6232 ---------------------------------------------------------------------------*/
6233 static void push_query_frame( void ) {
6234 QueryFrame* frame = allocate_frame();
6235 frame->join_list = NULL;
6236 frame->next = curr_query;
6238 // Initialize the ClassInfo for the core class
6239 ClassInfo* core = &frame->core;
6240 core->alias = core->class_name = core->source_def = NULL;
6241 core->class_def = core->fields = core->links = NULL;
6246 /* --------------------------------------------------------------------------
6247 Pop a QueryFrame off the stack and destroy it
6248 ---------------------------------------------------------------------------*/
6249 static void pop_query_frame( void ) {
6254 QueryFrame* popped = curr_query;
6255 curr_query = popped->next;
6257 free_query_frame( popped );
6260 /* --------------------------------------------------------------------------
6261 Populate the ClassInfo for the core class. Return 0 if successful, 1 if not.
6262 ---------------------------------------------------------------------------*/
6263 static int add_query_core( const char* alias, const char* class_name ) {
6266 if( ! curr_query ) {
6267 osrfLogError( OSRF_LOG_MARK,
6268 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
6270 } else if( curr_query->core.alias ) {
6271 osrfLogError( OSRF_LOG_MARK,
6272 "%s ERROR: Core class %s already populated as %s",
6273 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
6277 build_class_info( &curr_query->core, alias, class_name );
6278 if( curr_query->core.alias )
6281 osrfLogError( OSRF_LOG_MARK,
6282 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
6287 /* --------------------------------------------------------------------------
6288 Search the current QueryFrame for a specified alias. If you find it,
6289 return a pointer to the corresponding ClassInfo. Otherwise return NULL.
6290 ---------------------------------------------------------------------------*/
6291 static ClassInfo* search_alias( const char* target ) {
6292 return search_alias_in_frame( curr_query, target );
6295 /* --------------------------------------------------------------------------
6296 Search all levels of query for a specified alias, starting with the
6297 current query. If you find it, return a pointer to the corresponding
6298 ClassInfo. Otherwise return NULL.
6299 ---------------------------------------------------------------------------*/
6300 static ClassInfo* search_all_alias( const char* target ) {
6301 ClassInfo* found_class = NULL;
6302 QueryFrame* curr_frame = curr_query;
6304 while( curr_frame ) {
6305 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6308 curr_frame = curr_frame->next;
6314 /* --------------------------------------------------------------------------
6315 Add a class to the list of classes joined to the current query.
6316 ---------------------------------------------------------------------------*/
6317 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6319 if( ! classname || ! *classname ) { // sanity check
6320 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6327 const ClassInfo* conflict = search_alias( alias );
6329 osrfLogError( OSRF_LOG_MARK,
6330 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6331 MODULENAME, alias, conflict->class_name );
6335 ClassInfo* info = allocate_class_info();
6337 if( build_class_info( info, alias, classname ) ) {
6338 free_class_info( info );
6342 // Add the new ClassInfo to the join list of the current QueryFrame
6343 info->next = curr_query->join_list;
6344 curr_query->join_list = info;
6349 /* --------------------------------------------------------------------------
6350 Destroy all nodes on the query stack.
6351 ---------------------------------------------------------------------------*/
6352 static void clear_query_stack( void ) {