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 int beginTransaction ( osrfMethodContext* );
89 int commitTransaction ( osrfMethodContext* );
90 int rollbackTransaction ( osrfMethodContext* );
92 int setSavepoint ( osrfMethodContext* );
93 int releaseSavepoint ( osrfMethodContext* );
94 int rollbackSavepoint ( osrfMethodContext* );
96 int doJSONSearch ( osrfMethodContext* );
98 int dispatchCRUDMethod ( osrfMethodContext* );
99 static jsonObject* doCreate ( osrfMethodContext*, int* );
100 static jsonObject* doRetrieve ( osrfMethodContext*, int* );
101 static jsonObject* doUpdate ( osrfMethodContext*, int* );
102 static jsonObject* doDelete ( osrfMethodContext*, int* );
103 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
104 jsonObject* where_hash, jsonObject* query_hash, int* err );
105 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
106 static jsonObject* oilsMakeJSONFromResult( dbi_result );
108 static char* searchSimplePredicate ( const char* op, const char* class_alias,
109 osrfHash* field, const jsonObject* node );
110 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
111 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject*);
112 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*, const char* );
113 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
114 static char* searchINPredicate ( const char*, osrfHash*,
115 jsonObject*, const char*, osrfMethodContext* );
116 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
117 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
118 static char* searchWHERE ( const jsonObject*, const ClassInfo*, int, osrfMethodContext* );
119 static char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* );
120 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
122 char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int );
124 void userDataFree( void* );
125 static void sessionDataFree( char*, void* );
126 static char* getRelation( osrfHash* );
127 static int str_is_true( const char* str );
128 static int obj_is_true( const jsonObject* obj );
129 static const char* json_type( int code );
130 static const char* get_primitive( osrfHash* field );
131 static const char* get_datatype( osrfHash* field );
132 static int is_identifier( const char* s);
133 static int is_good_operator( const char* op );
134 static void pop_query_frame( void );
135 static void push_query_frame( void );
136 static int add_query_core( const char* alias, const char* class_name );
137 static ClassInfo* search_alias( const char* target );
138 static ClassInfo* search_all_alias( const char* target );
139 static ClassInfo* add_joined_class( const char* alias, const char* classname );
140 static void clear_query_stack( void );
143 static jsonObject* verifyUserPCRUD( osrfMethodContext* );
144 static int verifyObjectPCRUD( osrfMethodContext*, const jsonObject* );
145 static char* org_tree_root( osrfMethodContext* ctx );
146 static jsonObject* single_hash( const char* key, const char* value );
149 static int child_initialized = 0; /* boolean */
151 static dbi_conn writehandle; /* our MASTER db connection */
152 static dbi_conn dbhandle; /* our CURRENT db connection */
153 //static osrfHash * readHandles;
154 static jsonObject* const jsonNULL = NULL; //
155 static int max_flesh_depth = 100;
157 // The following points to the top of a stack of QueryFrames. It's a little
158 // confusing because the top level of the query is at the bottom of the stack.
159 static QueryFrame* curr_query = NULL;
162 @brief Disconnect from the database.
164 This function is called when the server drone is about to terminate.
166 void osrfAppChildExit() {
167 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
170 if (writehandle == dbhandle) same = 1;
172 dbi_conn_query(writehandle, "ROLLBACK;");
173 dbi_conn_close(writehandle);
176 if (dbhandle && !same)
177 dbi_conn_close(dbhandle);
179 // XXX add cleanup of readHandles whenever that gets used
185 @brief Initialize the application.
186 @return Zero if successful, or non-zero if not.
188 Load the IDL file into an internal data structure for future reference. Each non-virtual
189 class in the IDL corresponds to a table or view in the database, or to a subquery defined
190 in the IDL. Ignore all virtual tables and virtual fields.
192 Register a number of methods, some of them general-purpose and others specific for
195 The name of the application is given by the MODULENAME macro, whose value depends on
196 conditional compilation. The method names also incorporate MODULENAME, followed by a
197 dot, as a prefix. Some methods are registered or not registered depending on whether
198 the PCRUD macro is defined, and on whether the IDL includes permacrud entries for the
201 The general-purpose methods are as follows (minus their MODULENAME prefixes):
203 - json_query (not registered for PCRUD)
206 - transaction.rollback
211 For each non-virtual class, create up to eight class-specific methods:
213 - create (not for readonly classes)
215 - update (not for readonly classes)
216 - delete (not for readonly classes
217 - search (atomic and non-atomic versions)
218 - id_list (atomic and non-atomic versions)
220 For PCRUD, the full method names follow the pattern "MODULENAME.method_type.classname".
221 Otherwise they follow the pattern "MODULENAME.direct.XXX.method_type", where XXX is the
222 fieldmapper name from the IDL, with every run of one or more consecutive colons replaced
223 by a period. In addition, the names of atomic methods have a suffix of ".atomic".
225 This function is called when the registering the application, and is executed by the
226 listener before spawning the drones.
228 int osrfAppInitialize() {
230 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
231 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
233 if (!oilsIDLInit( osrf_settings_host_value("/IDL") ))
234 return 1; /* return non-zero to indicate error */
236 growing_buffer* method_name = buffer_init(64);
238 // Generic search thingy
239 buffer_add(method_name, MODULENAME);
240 buffer_add(method_name, ".json_query");
241 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
242 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
245 // first we register all the transaction and savepoint methods
246 buffer_reset(method_name);
247 OSRF_BUFFER_ADD(method_name, MODULENAME);
248 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
249 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
250 "beginTransaction", "", 0, 0 );
252 buffer_reset(method_name);
253 OSRF_BUFFER_ADD(method_name, MODULENAME);
254 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
255 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
256 "commitTransaction", "", 0, 0 );
258 buffer_reset(method_name);
259 OSRF_BUFFER_ADD(method_name, MODULENAME);
260 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
261 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
262 "rollbackTransaction", "", 0, 0 );
264 buffer_reset(method_name);
265 OSRF_BUFFER_ADD(method_name, MODULENAME);
266 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
267 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
268 "setSavepoint", "", 1, 0 );
270 buffer_reset(method_name);
271 OSRF_BUFFER_ADD(method_name, MODULENAME);
272 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
273 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
274 "releaseSavepoint", "", 1, 0 );
276 buffer_reset(method_name);
277 OSRF_BUFFER_ADD(method_name, MODULENAME);
278 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
279 osrfAppRegisterMethod( MODULENAME, OSRF_BUFFER_C_STR(method_name),
280 "rollbackSavepoint", "", 1, 0 );
282 static const char* global_method[] = {
290 const int global_method_count
291 = sizeof( global_method ) / sizeof ( global_method[0] );
293 unsigned long class_count = osrfHashGetCount( oilsIDL() );
294 osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
295 osrfLogDebug(OSRF_LOG_MARK,
296 "At most %lu methods will be generated",
297 (unsigned long) (class_count * global_method_count) );
299 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
300 osrfHash* idlClass = NULL;
302 // For each class in the IDL...
303 while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
305 const char* classname = osrfHashIteratorKey( class_itr );
306 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
308 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) {
309 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
310 MODULENAME, classname);
314 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
315 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
319 // Look up some other attributes of the current class
320 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
321 if( !idlClass_fieldmapper ) {
322 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
328 // For PCRUD, ignore classes with no permacrud attribute
329 osrfHash* idlClass_permacrud = osrfHashGet(idlClass, "permacrud");
330 if (!idlClass_permacrud) {
331 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no permacrud in IDL", classname );
335 const char* readonly = osrfHashGet(idlClass, "readonly");
338 for( i = 0; i < global_method_count; ++i ) { // for each global method
339 const char* method_type = global_method[ i ];
340 osrfLogDebug(OSRF_LOG_MARK,
341 "Using files to build %s class methods for %s", method_type, classname);
344 // Treat "id_list" or "search" as forms of "retrieve"
345 const char* tmp_method = method_type;
346 if ( *tmp_method == 'i' || *tmp_method == 's') { // "id_list" or "search"
347 tmp_method = "retrieve";
349 // Skip this method if there is no permacrud entry for it
350 if (!osrfHashGet( idlClass_permacrud, tmp_method ))
354 // No create, update, or delete methods for a readonly class
355 if ( str_is_true( readonly ) &&
356 ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd')
359 buffer_reset( method_name );
361 // Build the method name
363 // For PCRUD: MODULENAME.method_type.classname
364 buffer_fadd(method_name, "%s.%s.%s", MODULENAME, method_type, classname);
366 // For non-PCRUD: MODULENAME.MODULENAME.direct.XXX.method_type
367 // where XXX is the fieldmapper name from the IDL, with every run of
368 // one or more consecutive colons replaced by a period.
371 char* _fm = strdup( idlClass_fieldmapper );
372 part = strtok_r(_fm, ":", &st_tmp);
374 buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
376 while ((part = strtok_r(NULL, ":", &st_tmp))) {
377 OSRF_BUFFER_ADD_CHAR(method_name, '.');
378 OSRF_BUFFER_ADD(method_name, part);
380 OSRF_BUFFER_ADD_CHAR(method_name, '.');
381 OSRF_BUFFER_ADD(method_name, method_type);
385 // For an id_list or search method we specify the OSRF_METHOD_STREAMING option.
386 // The consequence is that we implicitly create an atomic method in addition to
387 // the usual non-atomic method.
389 if (*method_type == 'i' || *method_type == 's') { // id_list or search
390 flags = flags | OSRF_METHOD_STREAMING;
393 osrfHash* method_meta = osrfNewHash();
394 osrfHashSet( method_meta, idlClass, "class");
395 osrfHashSet( method_meta, buffer_data( method_name ), "methodname" );
396 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
398 // Register the method, with a pointer to an osrfHash to tell the method
399 // its name, type, and class.
400 osrfAppRegisterExtendedMethod(
402 OSRF_BUFFER_C_STR( method_name ),
403 "dispatchCRUDMethod",
410 } // end for each global method
411 } // end for each class in IDL
413 buffer_free( method_name );
414 osrfHashIteratorFree( class_itr );
420 @brief Get a table name, view name, or subquery for use in a FROM clause.
421 @param class Pointer to the IDL class entry.
422 @return A table name, a view name, or a subquery in parentheses.
424 In some cases the IDL defines a class, not with a table name or a view name, but with
425 a SELECT statement, which may be used as a subquery.
427 static char* getRelation( osrfHash* class ) {
429 char* source_def = NULL;
430 const char* tabledef = osrfHashGet(class, "tablename");
433 source_def = strdup( tabledef ); // Return the name of a table or view
435 tabledef = osrfHashGet( class, "source_definition" );
437 // Return a subquery, enclosed in parentheses
438 source_def = safe_malloc( strlen( tabledef ) + 3 );
439 source_def[ 0 ] = '(';
440 strcpy( source_def + 1, tabledef );
441 strcat( source_def, ")" );
443 // Not found: return an error
444 const char* classname = osrfHashGet( class, "classname" );
449 "%s ERROR No tablename or source_definition for class \"%s\"",
460 @brief Initialize a server drone.
461 @return Zero if successful, -1 if not.
463 Connect to the database. For each non-virtual class in the IDL, execute a dummy "SELECT * "
464 query to get the datatype of each column. Record the datatypes in the loaded IDL.
466 This function is called by a server drone shortly after it is spawned by the listener.
468 int osrfAppChildInit() {
470 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
471 dbi_initialize(NULL);
472 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
474 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
475 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
476 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
477 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
478 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
479 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
480 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion",
483 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
484 writehandle = dbi_conn_new(driver);
487 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
490 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
492 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
493 "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
495 if(host) dbi_conn_set_option(writehandle, "host", host );
496 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
497 if(user) dbi_conn_set_option(writehandle, "username", user);
498 if(pw) dbi_conn_set_option(writehandle, "password", pw );
499 if(db) dbi_conn_set_option(writehandle, "dbname", db );
501 if(md) max_flesh_depth = atoi(md);
502 if(max_flesh_depth < 0) max_flesh_depth = 1;
503 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
512 if (dbi_conn_connect(writehandle) < 0) {
514 if (dbi_conn_connect(writehandle) < 0) {
515 dbi_conn_error(writehandle, &err);
516 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
521 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
523 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
524 osrfHash* class = NULL;
525 growing_buffer* query_buf = buffer_init( 64 );
527 // For each class in the IDL...
528 while( (class = osrfHashIteratorNext( class_itr ) ) ) {
529 const char* classname = osrfHashIteratorKey( class_itr );
530 osrfHash* fields = osrfHashGet( class, "fields" );
532 // If the class is virtual, ignore it
533 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
534 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
538 char* tabledef = getRelation(class);
540 continue; // No such relation -- a query of it would be doomed to failure
542 buffer_reset( query_buf );
543 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
547 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
548 MODULENAME, OSRF_BUFFER_C_STR( query_buf ) );
550 dbi_result result = dbi_conn_query( writehandle, OSRF_BUFFER_C_STR( query_buf ) );
554 const char* columnName;
556 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
558 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
561 /* fetch the fieldmapper index */
562 if( (_f = osrfHashGet(fields, columnName)) ) {
564 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
566 /* determine the field type and storage attributes */
568 switch( dbi_result_get_field_type_idx(result, columnIndex) ) {
570 case DBI_TYPE_INTEGER : {
572 if ( !osrfHashGet(_f, "primitive") )
573 osrfHashSet(_f, "number", "primitive");
575 int attr = dbi_result_get_field_attribs_idx(result, columnIndex);
576 if( attr & DBI_INTEGER_SIZE8 )
577 osrfHashSet(_f, "INT8", "datatype");
579 osrfHashSet(_f, "INT", "datatype");
582 case DBI_TYPE_DECIMAL :
583 if ( !osrfHashGet(_f, "primitive") )
584 osrfHashSet(_f, "number", "primitive");
586 osrfHashSet(_f,"NUMERIC", "datatype");
589 case DBI_TYPE_STRING :
590 if ( !osrfHashGet(_f, "primitive") )
591 osrfHashSet(_f,"string", "primitive");
593 osrfHashSet(_f,"TEXT", "datatype");
596 case DBI_TYPE_DATETIME :
597 if ( !osrfHashGet(_f, "primitive") )
598 osrfHashSet(_f,"string", "primitive");
600 osrfHashSet(_f,"TIMESTAMP", "datatype");
603 case DBI_TYPE_BINARY :
604 if ( !osrfHashGet(_f, "primitive") )
605 osrfHashSet(_f,"string", "primitive");
607 osrfHashSet(_f,"BYTEA", "datatype");
612 "Setting [%s] to primitive [%s] and datatype [%s]...",
614 osrfHashGet(_f, "primitive"),
615 osrfHashGet(_f, "datatype")
619 } // end while loop for traversing columns of result
620 dbi_result_free(result);
622 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", classname);
624 } // end for each class in IDL
626 buffer_free( query_buf );
627 osrfHashIteratorFree( class_itr );
628 child_initialized = 1;
633 @brief Install a database driver.
634 @param conn Pointer to a database driver.
636 The driver is used to process quoted strings correctly.
638 This function is a sleazy hack intended @em only for testing and debugging without
639 actually connecting to a database. Any real server process should initialize the
640 database connection by calling osrfAppChildInit().
642 void set_cstore_dbi_conn( dbi_conn conn ) {
643 dbhandle = writehandle = conn;
647 @brief Free an osrfHash that stores a transaction ID.
648 @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
650 This function is a callback, to be called by the application session when it ends.
652 See also sessionDataFree().
654 void userDataFree( void* blob ) {
655 osrfHashFree( (osrfHash*) blob );
659 @brief Roll back a pending transaction at the end of the application session.
660 @param key The name of a key for an osrfHash.
661 @param item An opaque pointer to the item associated with the key.
663 We store an osrfHash with the application session, and arrange (by installing
664 userDataFree() as a different callback) for the session to free that osrfHash before
667 This function is a callback for freeing items in the osrfHash. If the item has a key
668 of "xact_id", the item is a transaction id for a transaction that is still pending.
669 So, if we're still connected, we do a rollback.
671 static void sessionDataFree( char* key, void* item ) {
672 if ( !strcmp(key,"xact_id") ) {
674 dbi_conn_query( writehandle, "ROLLBACK;" );
680 @brief Implement the transaction.begin method.
681 @param Pointer to the method context.
682 @return Zero if successful, or -1 upon error.
684 Start a transaction. Return a transaction ID (actually the session id of the
685 application session) to the client.
687 int beginTransaction ( osrfMethodContext* ctx ) {
688 if(osrfMethodVerifyContext( ctx )) {
689 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
694 jsonObject* user = verifyUserPCRUD( ctx );
697 jsonObjectFree(user);
700 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
702 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
703 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
704 "osrfMethodException", ctx->request, "Error starting transaction" );
707 jsonObject* ret = jsonNewObject( ctx->session->session_id );
708 osrfAppRespondComplete( ctx, ret );
711 // Store a copy of the application session ID as a transaction id in an osrfHash,
712 // to be stored with the application session. Install some callbacks so that, if
713 // the session ends while the transaction is still active, we will do a rollback.
714 if (!ctx->session->userData) {
715 ctx->session->userData = osrfNewHash();
716 osrfHashSetCallback((osrfHash*)ctx->session->userData, &sessionDataFree);
719 osrfHashSet( (osrfHash*)ctx->session->userData, strdup( ctx->session->session_id ),
721 ctx->session->userDataFree = &userDataFree;
727 int setSavepoint ( osrfMethodContext* ctx ) {
728 if(osrfMethodVerifyContext( ctx )) {
729 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
736 jsonObject* user = verifyUserPCRUD( ctx );
739 jsonObjectFree(user);
742 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
743 osrfAppSessionStatus(
745 OSRF_STATUS_INTERNALSERVERERROR,
746 "osrfMethodException",
748 "No active transaction -- required for savepoints"
753 const char* spName = jsonObjectGetString(jsonObjectGetIndex(ctx->params, spNamePos));
755 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
759 "%s: Error creating savepoint %s in transaction %s",
762 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
764 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
765 "osrfMethodException", ctx->request, "Error creating savepoint" );
768 jsonObject* ret = jsonNewObject(spName);
769 osrfAppRespondComplete( ctx, ret );
775 int releaseSavepoint ( osrfMethodContext* ctx ) {
776 if(osrfMethodVerifyContext( ctx )) {
777 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
784 jsonObject* user = verifyUserPCRUD( ctx );
787 jsonObjectFree(user);
790 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
791 osrfAppSessionStatus(
793 OSRF_STATUS_INTERNALSERVERERROR,
794 "osrfMethodException",
796 "No active transaction -- required for savepoints"
801 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
803 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
807 "%s: Error releasing savepoint %s in transaction %s",
810 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
812 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
813 "osrfMethodException", ctx->request, "Error releasing savepoint" );
816 jsonObject* ret = jsonNewObject(spName);
817 osrfAppRespondComplete( ctx, ret );
823 int rollbackSavepoint ( osrfMethodContext* ctx ) {
824 if(osrfMethodVerifyContext( ctx )) {
825 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
832 jsonObject* user = verifyUserPCRUD( ctx );
835 jsonObjectFree(user);
838 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
839 osrfAppSessionStatus(
841 OSRF_STATUS_INTERNALSERVERERROR,
842 "osrfMethodException",
844 "No active transaction -- required for savepoints"
849 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
851 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
855 "%s: Error rolling back savepoint %s in transaction %s",
858 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
860 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
861 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
864 jsonObject* ret = jsonNewObject(spName);
865 osrfAppRespondComplete( ctx, ret );
871 int commitTransaction ( osrfMethodContext* ctx ) {
872 if(osrfMethodVerifyContext( ctx )) {
873 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
878 jsonObject* user = verifyUserPCRUD( ctx );
881 jsonObjectFree(user);
884 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
885 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
886 "osrfMethodException", ctx->request, "No active transaction to commit" );
890 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
892 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
893 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
894 "osrfMethodException", ctx->request, "Error committing transaction" );
897 osrfHashRemove(ctx->session->userData, "xact_id");
898 jsonObject* ret = jsonNewObject(ctx->session->session_id);
899 osrfAppRespondComplete( ctx, ret );
905 int rollbackTransaction ( osrfMethodContext* ctx ) {
906 if(osrfMethodVerifyContext( ctx )) {
907 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
912 jsonObject* user = verifyUserPCRUD( ctx );
915 jsonObjectFree(user);
918 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
919 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
920 "osrfMethodException", ctx->request, "No active transaction to roll back" );
924 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
926 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
927 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
928 "osrfMethodException", ctx->request, "Error rolling back transaction" );
931 osrfHashRemove(ctx->session->userData, "xact_id");
932 jsonObject* ret = jsonNewObject(ctx->session->session_id);
933 osrfAppRespondComplete( ctx, ret );
939 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
940 if(osrfMethodVerifyContext( ctx )) {
941 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
945 osrfHash* meta = (osrfHash*) ctx->method->userData;
946 osrfHash* class_obj = osrfHashGet( meta, "class" );
950 const char* methodtype = osrfHashGet(meta, "methodtype");
951 jsonObject * obj = NULL;
953 if (!strcmp(methodtype, "create")) {
954 obj = doCreate(ctx, &err);
955 osrfAppRespondComplete( ctx, obj );
957 else if (!strcmp(methodtype, "retrieve")) {
958 obj = doRetrieve(ctx, &err);
959 osrfAppRespondComplete( ctx, obj );
961 else if (!strcmp(methodtype, "update")) {
962 obj = doUpdate(ctx, &err);
963 osrfAppRespondComplete( ctx, obj );
965 else if (!strcmp(methodtype, "delete")) {
966 obj = doDelete(ctx, &err);
967 osrfAppRespondComplete( ctx, obj );
969 else if (!strcmp(methodtype, "search")) {
971 jsonObject* where_clause;
972 jsonObject* rest_of_query;
975 where_clause = jsonObjectGetIndex( ctx->params, 1 );
976 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
978 where_clause = jsonObjectGetIndex( ctx->params, 0 );
979 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
982 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
987 unsigned long res_idx = 0;
988 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
990 if(!verifyObjectPCRUD(ctx, cur)) continue;
992 osrfAppRespond( ctx, cur );
994 osrfAppRespondComplete( ctx, NULL );
996 } else if (!strcmp(methodtype, "id_list")) {
998 jsonObject* where_clause;
999 jsonObject* rest_of_query;
1001 // We use the where clause without change. But we need
1002 // to massage the rest of the query, so we work with a copy
1003 // of it instead of modifying the original.
1005 where_clause = jsonObjectGetIndex( ctx->params, 1 );
1006 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1008 where_clause = jsonObjectGetIndex( ctx->params, 0 );
1009 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1012 if ( rest_of_query ) {
1013 jsonObjectRemoveKey( rest_of_query, "select" );
1014 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1015 jsonObjectRemoveKey( rest_of_query, "flesh" );
1016 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
1018 rest_of_query = jsonNewObjectType( JSON_HASH );
1021 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1023 // Build a SELECT list containing just the primary key,
1024 // i.e. like { "classname":["keyname"] }
1025 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1026 jsonObjectPush( col_list_obj, // Load array with name of primary key
1027 jsonNewObject( osrfHashGet( class_obj, "primarykey" ) ) );
1028 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1029 jsonObjectSetKey( select_clause, osrfHashGet( class_obj, "classname" ), col_list_obj );
1031 jsonObjectSetKey( rest_of_query, "select", select_clause );
1033 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
1035 jsonObjectFree( rest_of_query );
1039 unsigned long res_idx = 0;
1040 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1042 if(!verifyObjectPCRUD(ctx, cur)) continue;
1046 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
1049 osrfAppRespondComplete( ctx, NULL );
1052 osrfAppRespondComplete( ctx, obj );
1055 jsonObjectFree(obj);
1060 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1063 osrfHash* meta = (osrfHash*) ctx->method->userData;
1064 osrfHash* class = osrfHashGet( meta, "class" );
1066 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1068 const char* temp_classname = param->classname;
1069 if( ! temp_classname )
1070 temp_classname = "(null)";
1072 growing_buffer* msg = buffer_init(128);
1075 "%s: %s method for type %s was passed a %s",
1077 osrfHashGet(meta, "methodtype"),
1078 osrfHashGet(class, "classname"),
1082 char* m = buffer_release(msg);
1083 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1092 ret = verifyObjectPCRUD( ctx, param );
1100 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1101 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1102 jsonObject* auth_object = jsonNewObject(auth);
1103 jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve",
1105 jsonObjectFree(auth_object);
1107 if (!user->classname || strcmp(user->classname, "au")) {
1109 growing_buffer* msg = buffer_init(128);
1112 "%s: permacrud received a bad auth token: %s",
1117 char* m = buffer_release(msg);
1118 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1122 jsonObjectFree(user);
1130 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1132 dbhandle = writehandle;
1134 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1135 osrfHash* class = osrfHashGet( method_metadata, "class" );
1136 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1139 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
1140 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1141 } else if ( *method_type == 'u' || *method_type == 'd' ) {
1142 fetch = 1; // MUST go to the db for the object for update and delete
1145 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
1148 // No permacrud for this method type on this class
1150 growing_buffer* msg = buffer_init(128);
1153 "%s: %s on class %s has no permacrud IDL entry",
1155 osrfHashGet(method_metadata, "methodtype"),
1156 osrfHashGet(class, "classname")
1159 char* m = buffer_release(msg);
1160 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN, "osrfMethodException", ctx->request, m );
1167 jsonObject* user = verifyUserPCRUD( ctx );
1171 int userid = atoi( oilsFMGetString( user, "id" ) );
1172 jsonObjectFree(user);
1174 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1175 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1176 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1178 osrfStringArray* context_org_array = osrfNewStringArray(1);
1181 char* pkey_value = NULL;
1182 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1183 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions required, fetching top of the org tree" );
1185 // check for perm at top of org tree
1186 char* org_tree_root_id = org_tree_root( ctx );
1187 if( org_tree_root_id ) {
1188 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1189 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1191 osrfStringArrayFree( context_org_array );
1196 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, fetching context org ids" );
1197 const char* pkey = osrfHashGet(class, "primarykey");
1198 jsonObject *param = NULL;
1200 if (obj->classname) {
1201 pkey_value = oilsFMGetString( obj, pkey );
1202 if (!fetch) param = jsonObjectClone(obj);
1203 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s", pkey_value );
1205 pkey_value = jsonObjectToSimpleString( obj );
1207 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value of %s and retrieving from the database", pkey_value );
1211 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1212 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1213 jsonObjectFree(_tmp_params);
1215 param = jsonObjectExtractIndex(_list, 0);
1216 jsonObjectFree(_list);
1220 osrfLogDebug( OSRF_LOG_MARK, "Object not found in the database with primary key %s of %s", pkey, pkey_value );
1222 growing_buffer* msg = buffer_init(128);
1225 "%s: no object found with primary key %s of %s",
1231 char* m = buffer_release(msg);
1232 osrfAppSessionStatus(
1234 OSRF_STATUS_INTERNALSERVERERROR,
1235 "osrfMethodException",
1241 if (pkey_value) free(pkey_value);
1246 if (local_context->size > 0) {
1247 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified", local_context->size);
1249 const char* lcontext = NULL;
1250 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1251 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1254 "adding class-local field %s (value: %s) to the context org list",
1256 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1262 if (foreign_context) {
1263 unsigned long class_count = osrfHashGetCount( foreign_context );
1264 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1266 if (class_count > 0) {
1268 osrfHash* fcontext = NULL;
1269 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1270 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1271 const char* class_name = osrfHashIteratorKey( class_itr );
1272 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1276 "%d foreign context fields(s) specified for class %s",
1277 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1281 char* foreign_pkey = osrfHashGet(fcontext, "field");
1282 char* foreign_pkey_value = oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1284 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1286 jsonObject* _list = doFieldmapperSearch(
1287 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1289 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1290 jsonObjectFree(_tmp_params);
1291 jsonObjectFree(_list);
1293 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1295 if (_fparam && jump_list) {
1296 const char* flink = NULL;
1298 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1299 free(foreign_pkey_value);
1301 osrfHash* foreign_link_hash = oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1303 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1304 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1306 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1308 _list = doFieldmapperSearch(
1310 osrfHashGet( oilsIDL(), osrfHashGet( foreign_link_hash, "class" ) ),
1316 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1317 jsonObjectFree(_tmp_params);
1318 jsonObjectFree(_list);
1324 growing_buffer* msg = buffer_init(128);
1327 "%s: no object found with primary key %s of %s",
1333 char* m = buffer_release(msg);
1334 osrfAppSessionStatus(
1336 OSRF_STATUS_INTERNALSERVERERROR,
1337 "osrfMethodException",
1343 osrfHashIteratorFree(class_itr);
1344 free(foreign_pkey_value);
1345 jsonObjectFree(param);
1350 free(foreign_pkey_value);
1353 const char* foreign_field = NULL;
1354 while ( (foreign_field = osrfStringArrayGetString(osrfHashGet(fcontext,"context"), j++)) ) {
1355 osrfStringArrayAdd( context_org_array, oilsFMGetString( _fparam, foreign_field ) );
1358 "adding foreign class %s field %s (value: %s) to the context org list",
1361 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1365 jsonObjectFree(_fparam);
1368 osrfHashIteratorFree( class_itr );
1372 jsonObjectFree(param);
1375 const char* context_org = NULL;
1376 const char* perm = NULL;
1379 if (permission->size == 0) {
1380 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1385 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1387 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1393 "Checking object permission [%s] for user %d on object %s (class %s) at org %d",
1397 osrfHashGet(class, "classname"),
1401 result = dbi_conn_queryf(
1403 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1406 osrfHashGet(class, "classname"),
1414 "Received a result for object permission [%s] for user %d on object %s (class %s) at org %d",
1418 osrfHashGet(class, "classname"),
1422 if (dbi_result_first_row(result)) {
1423 jsonObject* return_val = oilsMakeJSONFromResult( result );
1424 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1428 "Status of object permission [%s] for user %d on object %s (class %s) at org %d is %s",
1432 osrfHashGet(class, "classname"),
1437 if ( *has_perm == 't' ) OK = 1;
1438 jsonObjectFree(return_val);
1441 dbi_result_free(result);
1446 osrfLogDebug( OSRF_LOG_MARK, "Checking non-object permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1447 result = dbi_conn_queryf(
1449 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1456 osrfLogDebug( OSRF_LOG_MARK, "Received a result for permission [%s] for user %d at org %d",
1457 perm, userid, atoi(context_org) );
1458 if ( dbi_result_first_row(result) ) {
1459 jsonObject* return_val = oilsMakeJSONFromResult( result );
1460 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1461 osrfLogDebug( OSRF_LOG_MARK, "Status of permission [%s] for user %d at org %d is [%s]",
1462 perm, userid, atoi(context_org), has_perm );
1463 if ( *has_perm == 't' ) OK = 1;
1464 jsonObjectFree(return_val);
1467 dbi_result_free(result);
1475 if (pkey_value) free(pkey_value);
1476 osrfStringArrayFree(context_org_array);
1482 * Look up the root of the org_unit tree. If you find it, return
1483 * a string containing the id, which the caller is responsible for freeing.
1484 * Otherwise return NULL.
1486 static char* org_tree_root( osrfMethodContext* ctx ) {
1488 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1489 static time_t last_lookup_time = 0;
1490 time_t current_time = time( NULL );
1492 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1493 // We successfully looked this up less than an hour ago.
1494 // It's not likely to have changed since then.
1495 return strdup( cached_root_id );
1497 last_lookup_time = current_time;
1500 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1501 jsonObject* result = doFieldmapperSearch(
1502 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1503 jsonObjectFree( where_clause );
1505 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1508 jsonObjectFree( result );
1510 growing_buffer* msg = buffer_init(128);
1511 OSRF_BUFFER_ADD( msg, MODULENAME );
1512 OSRF_BUFFER_ADD( msg,
1513 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1515 char* m = buffer_release(msg);
1516 osrfAppSessionStatus( ctx->session,
1517 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1520 cached_root_id[ 0 ] = '\0';
1524 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1525 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1527 jsonObjectFree( result );
1529 strcpy( cached_root_id, root_org_unit_id );
1530 return root_org_unit_id;
1534 Utility function: create a JSON_HASH with a single key/value pair.
1535 This function is equivalent to:
1537 jsonParseFmt( "{\"%s\":\"%s\"}", key, value )
1539 or, if value is NULL:
1541 jsonParseFmt( "{\"%s\":null}", key )
1543 ...but faster because it doesn't create and parse a JSON string.
1545 static jsonObject* single_hash( const char* key, const char* value ) {
1547 if( ! key ) key = "";
1549 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1550 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1556 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1558 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1560 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1561 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1563 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1564 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1567 if (!verifyObjectClass(ctx, target)) {
1572 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1574 char* trans_id = NULL;
1575 if( ctx->session && ctx->session->userData )
1576 trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
1579 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1581 osrfAppSessionStatus(
1583 OSRF_STATUS_BADREQUEST,
1584 "osrfMethodException",
1586 "No active transaction -- required for CREATE"
1592 // The following test is harmless but redundant. If a class is
1593 // readonly, we don't register a create method for it.
1594 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1595 osrfAppSessionStatus(
1597 OSRF_STATUS_BADREQUEST,
1598 "osrfMethodException",
1600 "Cannot INSERT readonly class"
1606 // Set the last_xact_id
1607 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1609 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1610 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1613 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1615 dbhandle = writehandle;
1617 osrfHash* fields = osrfHashGet(meta, "fields");
1618 char* pkey = osrfHashGet(meta, "primarykey");
1619 char* seq = osrfHashGet(meta, "sequence");
1621 growing_buffer* table_buf = buffer_init(128);
1622 growing_buffer* col_buf = buffer_init(128);
1623 growing_buffer* val_buf = buffer_init(128);
1625 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1626 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1627 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1628 buffer_add(val_buf,"VALUES (");
1632 osrfHash* field = NULL;
1633 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1634 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1636 const char* field_name = osrfHashIteratorKey( field_itr );
1638 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1641 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1644 if (field_object && field_object->classname) {
1645 value = oilsFMGetString(
1647 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1649 } else if( field_object && JSON_BOOL == field_object->type ) {
1650 if( jsonBoolIsTrue( field_object ) )
1651 value = strdup( "t" );
1653 value = strdup( "f" );
1655 value = jsonObjectToSimpleString( field_object );
1661 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1662 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1665 buffer_add(col_buf, field_name);
1667 if (!field_object || field_object->type == JSON_NULL) {
1668 buffer_add( val_buf, "DEFAULT" );
1670 } else if ( !strcmp(get_primitive( field ), "number") ) {
1671 const char* numtype = get_datatype( field );
1672 if ( !strcmp( numtype, "INT8") ) {
1673 buffer_fadd( val_buf, "%lld", atoll(value) );
1675 } else if ( !strcmp( numtype, "INT") ) {
1676 buffer_fadd( val_buf, "%d", atoi(value) );
1678 } else if ( !strcmp( numtype, "NUMERIC") ) {
1679 buffer_fadd( val_buf, "%f", atof(value) );
1682 if ( dbi_conn_quote_string(writehandle, &value) ) {
1683 OSRF_BUFFER_ADD( val_buf, value );
1686 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1687 osrfAppSessionStatus(
1689 OSRF_STATUS_INTERNALSERVERERROR,
1690 "osrfMethodException",
1692 "Error quoting string -- please see the error log for more details"
1695 buffer_free(table_buf);
1696 buffer_free(col_buf);
1697 buffer_free(val_buf);
1707 osrfHashIteratorFree( field_itr );
1709 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1710 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1712 char* table_str = buffer_release(table_buf);
1713 char* col_str = buffer_release(col_buf);
1714 char* val_str = buffer_release(val_buf);
1715 growing_buffer* sql = buffer_init(128);
1716 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1721 char* query = buffer_release(sql);
1723 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1726 dbi_result result = dbi_conn_query(writehandle, query);
1728 jsonObject* obj = NULL;
1731 obj = jsonNewObject(NULL);
1734 "%s ERROR inserting %s object using query [%s]",
1736 osrfHashGet(meta, "fieldmapper"),
1739 osrfAppSessionStatus(
1741 OSRF_STATUS_INTERNALSERVERERROR,
1742 "osrfMethodException",
1744 "INSERT error -- please see the error log for more details"
1749 char* id = oilsFMGetString(target, pkey);
1751 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1752 growing_buffer* _id = buffer_init(10);
1753 buffer_fadd(_id, "%lld", new_id);
1754 id = buffer_release(_id);
1757 // Find quietness specification, if present
1758 const char* quiet_str = NULL;
1760 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1762 quiet_str = jsonObjectGetString( quiet_obj );
1765 if( str_is_true( quiet_str ) ) { // if quietness is specified
1766 obj = jsonNewObject(id);
1770 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1771 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1773 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1775 jsonObjectFree( where_clause );
1780 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1783 jsonObjectFree( list );
1796 * Fetch one row from a specified table, using a specified value
1797 * for the primary key
1799 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1809 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1811 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos); // key value
1815 "%s retrieving %s object with primary key value of %s",
1817 osrfHashGet( class_def, "fieldmapper" ),
1818 jsonObjectGetString( id_obj )
1821 // Build a WHERE clause based on the key value
1822 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1825 osrfHashGet( class_def, "primarykey" ),
1826 jsonObjectClone( id_obj )
1829 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
1831 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
1833 jsonObjectFree( where_clause );
1837 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
1838 jsonObjectFree( list );
1841 if(!verifyObjectPCRUD(ctx, obj)) {
1842 jsonObjectFree(obj);
1845 growing_buffer* msg = buffer_init(128);
1846 OSRF_BUFFER_ADD( msg, MODULENAME );
1847 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1849 char* m = buffer_release(msg);
1850 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1861 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1862 growing_buffer* val_buf = buffer_init(32);
1863 const char* numtype = get_datatype( field );
1865 if ( !strncmp( numtype, "INT", 3 ) ) {
1866 if (value->type == JSON_NUMBER)
1867 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1868 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1870 //const char* val_str = jsonObjectGetString( value );
1871 //buffer_fadd( val_buf, "%ld", atol(val_str) );
1872 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1875 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1876 if (value->type == JSON_NUMBER)
1877 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
1878 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1880 //const char* val_str = jsonObjectGetString( value );
1881 //buffer_fadd( val_buf, "%f", atof(val_str) );
1882 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1886 // Presumably this was really intended ot be a string, so quote it
1887 char* str = jsonObjectToSimpleString( value );
1888 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1889 OSRF_BUFFER_ADD( val_buf, str );
1892 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1894 buffer_free(val_buf);
1899 return buffer_release(val_buf);
1902 static char* searchINPredicate (const char* class_alias, osrfHash* field,
1903 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1904 growing_buffer* sql_buf = buffer_init(32);
1910 osrfHashGet(field, "name")
1914 buffer_add(sql_buf, "IN (");
1915 } else if (!(strcasecmp(op,"not in"))) {
1916 buffer_add(sql_buf, "NOT IN (");
1918 buffer_add(sql_buf, "IN (");
1921 if (node->type == JSON_HASH) {
1922 // subquery predicate
1923 char* subpred = buildQuery( ctx, node, SUBSELECT );
1925 buffer_free( sql_buf );
1929 buffer_add(sql_buf, subpred);
1932 } else if (node->type == JSON_ARRAY) {
1933 // literal value list
1934 int in_item_index = 0;
1935 int in_item_first = 1;
1936 const jsonObject* in_item;
1937 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1942 buffer_add(sql_buf, ", ");
1945 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1946 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1947 MODULENAME, json_type( in_item->type ) );
1948 buffer_free(sql_buf);
1952 // Append the literal value -- quoted if not a number
1953 if ( JSON_NUMBER == in_item->type ) {
1954 char* val = jsonNumberToDBString( field, in_item );
1955 OSRF_BUFFER_ADD( sql_buf, val );
1958 } else if ( !strcmp( get_primitive( field ), "number") ) {
1959 char* val = jsonNumberToDBString( field, in_item );
1960 OSRF_BUFFER_ADD( sql_buf, val );
1964 char* key_string = jsonObjectToSimpleString(in_item);
1965 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1966 OSRF_BUFFER_ADD( sql_buf, key_string );
1969 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1971 buffer_free(sql_buf);
1977 if( in_item_first ) {
1978 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1979 buffer_free( sql_buf );
1983 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1984 MODULENAME, json_type( node->type ) );
1985 buffer_free(sql_buf);
1989 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1991 return buffer_release(sql_buf);
1994 // Receive a JSON_ARRAY representing a function call. The first
1995 // entry in the array is the function name. The rest are parameters.
1996 static char* searchValueTransform( const jsonObject* array ) {
1998 if( array->size < 1 ) {
1999 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
2003 // Get the function name
2004 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2005 if( func_item->type != JSON_STRING ) {
2006 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2007 MODULENAME, json_type( func_item->type ) );
2011 growing_buffer* sql_buf = buffer_init(32);
2013 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2014 OSRF_BUFFER_ADD( sql_buf, "( " );
2016 // Get the parameters
2017 int func_item_index = 1; // We already grabbed the zeroth entry
2018 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2020 // Add a separator comma, if we need one
2021 if( func_item_index > 2 )
2022 buffer_add( sql_buf, ", " );
2024 // Add the current parameter
2025 if (func_item->type == JSON_NULL) {
2026 buffer_add( sql_buf, "NULL" );
2028 char* val = jsonObjectToSimpleString(func_item);
2029 if ( dbi_conn_quote_string(dbhandle, &val) ) {
2030 OSRF_BUFFER_ADD( sql_buf, val );
2033 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2034 buffer_free(sql_buf);
2041 buffer_add( sql_buf, " )" );
2043 return buffer_release(sql_buf);
2046 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
2047 const jsonObject* node, const char* op) {
2049 if( ! is_good_operator( op ) ) {
2050 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2054 char* val = searchValueTransform(node);
2058 growing_buffer* sql_buf = buffer_init(32);
2063 osrfHashGet(field, "name"),
2070 return buffer_release(sql_buf);
2073 // class_alias is a class name or other table alias
2074 // field is a field definition as stored in the IDL
2075 // node comes from the method parameter, and may represent an entry in the SELECT list
2076 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
2077 growing_buffer* sql_buf = buffer_init(32);
2079 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
2080 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
2082 if(transform_subcolumn) {
2083 if( ! is_identifier( transform_subcolumn ) ) {
2084 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2085 MODULENAME, transform_subcolumn );
2086 buffer_free( sql_buf );
2089 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2092 if (field_transform) {
2094 if( ! is_identifier( field_transform ) ) {
2095 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2096 MODULENAME, field_transform );
2097 buffer_free( sql_buf );
2101 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class_alias, osrfHashGet(field, "name"));
2102 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2105 if( array->type != JSON_ARRAY ) {
2106 osrfLogError( OSRF_LOG_MARK,
2107 "%s: Expected JSON_ARRAY for function params; found %s",
2108 MODULENAME, json_type( array->type ) );
2109 buffer_free( sql_buf );
2112 int func_item_index = 0;
2113 jsonObject* func_item;
2114 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2116 char* val = jsonObjectToSimpleString(func_item);
2119 buffer_add( sql_buf, ",NULL" );
2120 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
2121 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2122 OSRF_BUFFER_ADD( sql_buf, val );
2124 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2126 buffer_free(sql_buf);
2133 buffer_add( sql_buf, " )" );
2136 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2139 if (transform_subcolumn)
2140 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2142 return buffer_release(sql_buf);
2145 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2146 const jsonObject* node, const char* op ) {
2148 if( ! is_good_operator( op ) ) {
2149 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2153 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2154 if( ! field_transform )
2157 int extra_parens = 0; // boolean
2159 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2160 if ( ! value_obj ) {
2161 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2163 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
2164 free(field_transform);
2168 } else if ( value_obj->type == JSON_ARRAY ) {
2169 value = searchValueTransform( value_obj );
2171 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
2172 free( field_transform );
2175 } else if ( value_obj->type == JSON_HASH ) {
2176 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2178 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
2179 free(field_transform);
2183 } else if ( value_obj->type == JSON_NUMBER ) {
2184 value = jsonNumberToDBString( field, value_obj );
2185 } else if ( value_obj->type == JSON_NULL ) {
2186 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
2187 free(field_transform);
2189 } else if ( value_obj->type == JSON_BOOL ) {
2190 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
2191 free(field_transform);
2194 if ( !strcmp( get_primitive( field ), "number") ) {
2195 value = jsonNumberToDBString( field, value_obj );
2197 value = jsonObjectToSimpleString( value_obj );
2198 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2199 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
2201 free(field_transform);
2207 const char* left_parens = "";
2208 const char* right_parens = "";
2210 if( extra_parens ) {
2215 growing_buffer* sql_buf = buffer_init(32);
2219 "%s%s %s %s %s %s%s",
2230 free(field_transform);
2232 return buffer_release(sql_buf);
2235 static char* searchSimplePredicate (const char* op, const char* class_alias,
2236 osrfHash* field, const jsonObject* node) {
2238 if( ! is_good_operator( op ) ) {
2239 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2245 // Get the value to which we are comparing the specified column
2246 if (node->type != JSON_NULL) {
2247 if ( node->type == JSON_NUMBER ) {
2248 val = jsonNumberToDBString( field, node );
2249 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2250 val = jsonNumberToDBString( field, node );
2252 val = jsonObjectToSimpleString(node);
2257 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2258 // Value is not numeric; enclose it in quotes
2259 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2260 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2266 // Compare to a null value
2267 val = strdup( "NULL" );
2268 if (strcmp( op, "=" ))
2274 growing_buffer* sql_buf = buffer_init(32);
2275 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2276 char* pred = buffer_release( sql_buf );
2283 static char* searchBETWEENPredicate (const char* class_alias,
2284 osrfHash* field, const jsonObject* node) {
2286 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2287 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2289 if( NULL == y_node ) {
2290 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2293 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2294 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2301 if ( !strcmp( get_primitive( field ), "number") ) {
2302 x_string = jsonNumberToDBString(field, x_node);
2303 y_string = jsonNumberToDBString(field, y_node);
2306 x_string = jsonObjectToSimpleString(x_node);
2307 y_string = jsonObjectToSimpleString(y_node);
2308 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2309 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2310 MODULENAME, x_string, y_string);
2317 growing_buffer* sql_buf = buffer_init(32);
2318 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2319 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2323 return buffer_release(sql_buf);
2326 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2327 jsonObject* node, osrfMethodContext* ctx ) {
2330 if (node->type == JSON_ARRAY) { // equality IN search
2331 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2332 } else if (node->type == JSON_HASH) { // other search
2333 jsonIterator* pred_itr = jsonNewIterator( node );
2334 if( !jsonIteratorHasNext( pred_itr ) ) {
2335 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2336 MODULENAME, osrfHashGet(field, "name") );
2338 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2340 // Verify that there are no additional predicates
2341 if( jsonIteratorHasNext( pred_itr ) ) {
2342 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2343 MODULENAME, osrfHashGet(field, "name") );
2344 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2345 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2346 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2347 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
2348 else if ( pred_node->type == JSON_ARRAY )
2349 pred = searchFunctionPredicate( class_info->alias, field, pred_node, pred_itr->key );
2350 else if ( pred_node->type == JSON_HASH )
2351 pred = searchFieldTransformPredicate( class_info, field, pred_node, pred_itr->key );
2353 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2355 jsonIteratorFree(pred_itr);
2357 } else if (node->type == JSON_NULL) { // IS NULL search
2358 growing_buffer* _p = buffer_init(64);
2361 "\"%s\".%s IS NULL",
2362 class_info->class_name,
2363 osrfHashGet(field, "name")
2365 pred = buffer_release(_p);
2366 } else { // equality search
2367 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2386 field : call_number,
2402 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2404 const jsonObject* working_hash;
2405 jsonObject* freeable_hash = NULL;
2407 if (join_hash->type == JSON_HASH) {
2408 working_hash = join_hash;
2409 } else if (join_hash->type == JSON_STRING) {
2410 // turn it into a JSON_HASH by creating a wrapper
2411 // around a copy of the original
2412 const char* _tmp = jsonObjectGetString( join_hash );
2413 freeable_hash = jsonNewObjectType(JSON_HASH);
2414 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2415 working_hash = freeable_hash;
2419 "%s: JOIN failed; expected JSON object type not found",
2425 growing_buffer* join_buf = buffer_init(128);
2426 const char* leftclass = left_info->class_name;
2428 jsonObject* snode = NULL;
2429 jsonIterator* search_itr = jsonNewIterator( working_hash );
2431 while ( (snode = jsonIteratorNext( search_itr )) ) {
2432 const char* right_alias = search_itr->key;
2434 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2436 class = right_alias;
2438 const ClassInfo* right_info = add_joined_class( right_alias, class );
2442 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2446 jsonIteratorFree( search_itr );
2447 buffer_free( join_buf );
2449 jsonObjectFree( freeable_hash );
2452 osrfHash* links = right_info->links;
2453 const char* table = right_info->source_def;
2455 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2456 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2458 if (field && !fkey) {
2459 // Look up the corresponding join column in the IDL.
2460 // The link must be defined in the child table,
2461 // and point to the right parent table.
2462 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2463 const char* reltype = NULL;
2464 const char* other_class = NULL;
2465 reltype = osrfHashGet( idl_link, "reltype" );
2466 if( reltype && strcmp( reltype, "has_many" ) )
2467 other_class = osrfHashGet( idl_link, "class" );
2468 if( other_class && !strcmp( other_class, leftclass ) )
2469 fkey = osrfHashGet( idl_link, "key" );
2473 "%s: JOIN failed. No link defined from %s.%s to %s",
2479 buffer_free(join_buf);
2481 jsonObjectFree(freeable_hash);
2482 jsonIteratorFree(search_itr);
2486 } else if (!field && fkey) {
2487 // Look up the corresponding join column in the IDL.
2488 // The link must be defined in the child table,
2489 // and point to the right parent table.
2490 osrfHash* left_links = left_info->links;
2491 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2492 const char* reltype = NULL;
2493 const char* other_class = NULL;
2494 reltype = osrfHashGet( idl_link, "reltype" );
2495 if( reltype && strcmp( reltype, "has_many" ) )
2496 other_class = osrfHashGet( idl_link, "class" );
2497 if( other_class && !strcmp( other_class, class ) )
2498 field = osrfHashGet( idl_link, "key" );
2502 "%s: JOIN failed. No link defined from %s.%s to %s",
2508 buffer_free(join_buf);
2510 jsonObjectFree(freeable_hash);
2511 jsonIteratorFree(search_itr);
2515 } else if (!field && !fkey) {
2516 osrfHash* left_links = left_info->links;
2518 // For each link defined for the left class:
2519 // see if the link references the joined class
2520 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2521 osrfHash* curr_link = NULL;
2522 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2523 const char* other_class = osrfHashGet( curr_link, "class" );
2524 if( other_class && !strcmp( other_class, class ) ) {
2526 // In the IDL, the parent class doesn't know then names of the child
2527 // columns that are pointing to it, so don't use that end of the link
2528 const char* reltype = osrfHashGet( curr_link, "reltype" );
2529 if( reltype && strcmp( reltype, "has_many" ) ) {
2530 // Found a link between the classes
2531 fkey = osrfHashIteratorKey( itr );
2532 field = osrfHashGet( curr_link, "key" );
2537 osrfHashIteratorFree( itr );
2539 if (!field || !fkey) {
2540 // Do another such search, with the classes reversed
2542 // For each link defined for the joined class:
2543 // see if the link references the left class
2544 osrfHashIterator* itr = osrfNewHashIterator( links );
2545 osrfHash* curr_link = NULL;
2546 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2547 const char* other_class = osrfHashGet( curr_link, "class" );
2548 if( other_class && !strcmp( other_class, leftclass ) ) {
2550 // In the IDL, the parent class doesn't know then names of the child
2551 // columns that are pointing to it, so don't use that end of the link
2552 const char* reltype = osrfHashGet( curr_link, "reltype" );
2553 if( reltype && strcmp( reltype, "has_many" ) ) {
2554 // Found a link between the classes
2555 field = osrfHashIteratorKey( itr );
2556 fkey = osrfHashGet( curr_link, "key" );
2561 osrfHashIteratorFree( itr );
2564 if (!field || !fkey) {
2567 "%s: JOIN failed. No link defined between %s and %s",
2572 buffer_free(join_buf);
2574 jsonObjectFree(freeable_hash);
2575 jsonIteratorFree(search_itr);
2581 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2583 if ( !strcasecmp(type,"left") ) {
2584 buffer_add(join_buf, " LEFT JOIN");
2585 } else if ( !strcasecmp(type,"right") ) {
2586 buffer_add(join_buf, " RIGHT JOIN");
2587 } else if ( !strcasecmp(type,"full") ) {
2588 buffer_add(join_buf, " FULL JOIN");
2590 buffer_add(join_buf, " INNER JOIN");
2593 buffer_add(join_buf, " INNER JOIN");
2596 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2597 table, right_alias, right_alias, field, left_info->alias, fkey);
2599 // Add any other join conditions as specified by "filter"
2600 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2602 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2603 if ( filter_op && !strcasecmp("or",filter_op) ) {
2604 buffer_add( join_buf, " OR " );
2606 buffer_add( join_buf, " AND " );
2609 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2611 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2612 OSRF_BUFFER_ADD( join_buf, jpred );
2617 "%s: JOIN failed. Invalid conditional expression.",
2620 jsonIteratorFree( search_itr );
2621 buffer_free( join_buf );
2623 jsonObjectFree( freeable_hash );
2628 buffer_add(join_buf, " ) ");
2630 // Recursively add a nested join, if one is present
2631 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2633 char* jpred = searchJOIN( join_filter, right_info );
2635 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2636 OSRF_BUFFER_ADD( join_buf, jpred );
2639 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2640 jsonIteratorFree( search_itr );
2641 buffer_free( join_buf );
2643 jsonObjectFree( freeable_hash );
2650 jsonObjectFree(freeable_hash);
2651 jsonIteratorFree(search_itr);
2653 return buffer_release(join_buf);
2658 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2659 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2660 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2662 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2664 search_hash is the JSON expression of the conditions.
2665 meta is the class definition from the IDL, for the relevant table.
2666 opjoin_type indicates whether multiple conditions, if present, should be
2667 connected by AND or OR.
2668 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2669 to pass it to other functions -- and all they do with it is to use the session
2670 and request members to send error messages back to the client.
2674 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
2675 int opjoin_type, osrfMethodContext* ctx ) {
2679 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2682 class_info->class_def,
2687 growing_buffer* sql_buf = buffer_init(128);
2689 jsonObject* node = NULL;
2692 if ( search_hash->type == JSON_ARRAY ) {
2693 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2694 if( 0 == search_hash->size ) {
2697 "%s: Invalid predicate structure: empty JSON array",
2700 buffer_free( sql_buf );
2704 unsigned long i = 0;
2705 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
2709 if (opjoin_type == OR_OP_JOIN)
2710 buffer_add(sql_buf, " OR ");
2712 buffer_add(sql_buf, " AND ");
2715 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2717 buffer_free( sql_buf );
2721 buffer_fadd(sql_buf, "( %s )", subpred);
2725 } else if ( search_hash->type == JSON_HASH ) {
2726 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2727 jsonIterator* search_itr = jsonNewIterator( search_hash );
2728 if( !jsonIteratorHasNext( search_itr ) ) {
2731 "%s: Invalid predicate structure: empty JSON object",
2734 jsonIteratorFree( search_itr );
2735 buffer_free( sql_buf );
2739 while ( (node = jsonIteratorNext( search_itr )) ) {
2744 if (opjoin_type == OR_OP_JOIN)
2745 buffer_add(sql_buf, " OR ");
2747 buffer_add(sql_buf, " AND ");
2750 if ( '+' == search_itr->key[ 0 ] ) {
2752 // This plus sign prefixes a class name or other table alias;
2753 // make sure the table alias is in scope
2754 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2755 if( ! alias_info ) {
2758 "%s: Invalid table alias \"%s\" in WHERE clause",
2762 jsonIteratorFree( search_itr );
2763 buffer_free( sql_buf );
2767 if ( node->type == JSON_STRING ) {
2768 // It's the name of a column; make sure it belongs to the class
2769 const char* fieldname = jsonObjectGetString( node );
2770 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2773 "%s: Invalid column name \"%s\" in WHERE clause for table alias \"%s\"",
2778 jsonIteratorFree( search_itr );
2779 buffer_free( sql_buf );
2783 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
2785 // It's something more complicated
2786 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
2788 jsonIteratorFree( search_itr );
2789 buffer_free( sql_buf );
2793 buffer_fadd(sql_buf, "( %s )", subpred);
2796 } else if ( '-' == search_itr->key[ 0 ] ) {
2797 if ( !strcasecmp("-or",search_itr->key) ) {
2798 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
2800 jsonIteratorFree( search_itr );
2801 buffer_free( sql_buf );
2805 buffer_fadd(sql_buf, "( %s )", subpred);
2807 } else if ( !strcasecmp("-and",search_itr->key) ) {
2808 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2810 jsonIteratorFree( search_itr );
2811 buffer_free( sql_buf );
2815 buffer_fadd(sql_buf, "( %s )", subpred);
2817 } else if ( !strcasecmp("-not",search_itr->key) ) {
2818 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2820 jsonIteratorFree( search_itr );
2821 buffer_free( sql_buf );
2825 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2827 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2828 char* subpred = buildQuery( ctx, node, SUBSELECT );
2830 jsonIteratorFree( search_itr );
2831 buffer_free( sql_buf );
2835 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2837 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2838 char* subpred = buildQuery( ctx, node, SUBSELECT );
2840 jsonIteratorFree( search_itr );
2841 buffer_free( sql_buf );
2845 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2847 } else { // Invalid "minus" operator
2850 "%s: Invalid operator \"%s\" in WHERE clause",
2854 jsonIteratorFree( search_itr );
2855 buffer_free( sql_buf );
2861 const char* class = class_info->class_name;
2862 osrfHash* fields = class_info->fields;
2863 osrfHash* field = osrfHashGet( fields, search_itr->key );
2866 const char* table = class_info->source_def;
2869 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2872 table ? table : "?",
2875 jsonIteratorFree(search_itr);
2876 buffer_free(sql_buf);
2880 char* subpred = searchPredicate( class_info, field, node, ctx );
2882 buffer_free(sql_buf);
2883 jsonIteratorFree(search_itr);
2887 buffer_add( sql_buf, subpred );
2891 jsonIteratorFree(search_itr);
2894 // ERROR ... only hash and array allowed at this level
2895 char* predicate_string = jsonObjectToJSON( search_hash );
2898 "%s: Invalid predicate structure: %s",
2902 buffer_free(sql_buf);
2903 free(predicate_string);
2907 return buffer_release(sql_buf);
2910 /* Build a JSON_ARRAY of field names for a given table alias
2912 static jsonObject* defaultSelectList( const char* table_alias ) {
2917 ClassInfo* class_info = search_all_alias( table_alias );
2918 if( ! class_info ) {
2921 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
2928 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
2929 osrfHash* field_def = NULL;
2930 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
2931 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2932 const char* field_name = osrfHashIteratorKey( field_itr );
2933 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2934 jsonObjectPush( array, jsonNewObject( field_name ) );
2937 osrfHashIteratorFree( field_itr );
2942 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
2943 // The jsonObject must be a JSON_HASH with an single entry for "union",
2944 // "intersect", or "except". The data associated with this key must be an
2945 // array of hashes, each hash being a query.
2946 // Also allowed but currently ignored: entries for "order_by" and "alias".
2947 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
2949 if( ! combo || combo->type != JSON_HASH )
2950 return NULL; // should be impossible; validated by caller
2952 const jsonObject* query_array = NULL; // array of subordinate queries
2953 const char* op = NULL; // name of operator, e.g. UNION
2954 const char* alias = NULL; // alias for the query (needed for ORDER BY)
2955 int op_count = 0; // for detecting conflicting operators
2956 int excepting = 0; // boolean
2957 int all = 0; // boolean
2958 jsonObject* order_obj = NULL;
2960 // Identify the elements in the hash
2961 jsonIterator* query_itr = jsonNewIterator( combo );
2962 jsonObject* curr_obj = NULL;
2963 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
2964 if( ! strcmp( "union", query_itr->key ) ) {
2967 query_array = curr_obj;
2968 } else if( ! strcmp( "intersect", query_itr->key ) ) {
2971 query_array = curr_obj;
2972 } else if( ! strcmp( "except", query_itr->key ) ) {
2976 query_array = curr_obj;
2977 } else if( ! strcmp( "order_by", query_itr->key ) ) {
2980 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
2983 order_obj = curr_obj;
2984 } else if( ! strcmp( "alias", query_itr->key ) ) {
2985 if( curr_obj->type != JSON_STRING ) {
2986 jsonIteratorFree( query_itr );
2989 alias = jsonObjectGetString( curr_obj );
2990 } else if( ! strcmp( "all", query_itr->key ) ) {
2991 if( obj_is_true( curr_obj ) )
2995 osrfAppSessionStatus(
2997 OSRF_STATUS_INTERNALSERVERERROR,
2998 "osrfMethodException",
3000 "Malformed query; unexpected entry in query object"
3004 "%s: Unexpected entry for \"%s\" in%squery",
3009 jsonIteratorFree( query_itr );
3013 jsonIteratorFree( query_itr );
3015 // More sanity checks
3016 if( ! query_array ) {
3018 osrfAppSessionStatus(
3020 OSRF_STATUS_INTERNALSERVERERROR,
3021 "osrfMethodException",
3023 "Expected UNION, INTERSECT, or EXCEPT operator not found"
3027 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
3030 return NULL; // should be impossible...
3031 } else if( op_count > 1 ) {
3033 osrfAppSessionStatus(
3035 OSRF_STATUS_INTERNALSERVERERROR,
3036 "osrfMethodException",
3038 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3042 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3046 } if( query_array->type != JSON_ARRAY ) {
3048 osrfAppSessionStatus(
3050 OSRF_STATUS_INTERNALSERVERERROR,
3051 "osrfMethodException",
3053 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3057 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3060 json_type( query_array->type )
3063 } if( query_array->size < 2 ) {
3065 osrfAppSessionStatus(
3067 OSRF_STATUS_INTERNALSERVERERROR,
3068 "osrfMethodException",
3070 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3074 "%s:%srequires multiple queries as operands",
3079 } else if( excepting && query_array->size > 2 ) {
3081 osrfAppSessionStatus(
3083 OSRF_STATUS_INTERNALSERVERERROR,
3084 "osrfMethodException",
3086 "EXCEPT operator has too many queries as operands"
3090 "%s:EXCEPT operator has too many queries as operands",
3094 } else if( order_obj && ! alias ) {
3096 osrfAppSessionStatus(
3098 OSRF_STATUS_INTERNALSERVERERROR,
3099 "osrfMethodException",
3101 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3105 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3111 // So far so good. Now build the SQL.
3112 growing_buffer* sql = buffer_init( 256 );
3114 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3115 // Add a layer of parentheses
3116 if( flags & SUBCOMBO )
3117 OSRF_BUFFER_ADD( sql, "( " );
3119 // Traverse the query array. Each entry should be a hash.
3120 int first = 1; // boolean
3122 jsonObject* query = NULL;
3123 while((query = jsonObjectGetIndex( query_array, i++ ) )) {
3124 if( query->type != JSON_HASH ) {
3126 osrfAppSessionStatus(
3128 OSRF_STATUS_INTERNALSERVERERROR,
3129 "osrfMethodException",
3131 "Malformed query under UNION, INTERSECT or EXCEPT"
3135 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3138 json_type( query->type )
3147 OSRF_BUFFER_ADD( sql, op );
3149 OSRF_BUFFER_ADD( sql, "ALL " );
3152 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3156 "%s: Error building query under%s",
3164 OSRF_BUFFER_ADD( sql, query_str );
3167 if( flags & SUBCOMBO )
3168 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3170 if ( !(flags & SUBSELECT) )
3171 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3173 return buffer_release( sql );
3176 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3177 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3178 // or "except" to indicate the type of query.
3179 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3183 osrfAppSessionStatus(
3185 OSRF_STATUS_INTERNALSERVERERROR,
3186 "osrfMethodException",
3188 "Malformed query; no query object"
3190 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", MODULENAME );
3192 } else if( query->type != JSON_HASH ) {
3194 osrfAppSessionStatus(
3196 OSRF_STATUS_INTERNALSERVERERROR,
3197 "osrfMethodException",
3199 "Malformed query object"
3203 "%s: Query object is %s instead of JSON_HASH",
3205 json_type( query->type )
3210 // Determine what kind of query it purports to be, and dispatch accordingly.
3211 if( jsonObjectGetKey( query, "union" ) ||
3212 jsonObjectGetKey( query, "intersect" ) ||
3213 jsonObjectGetKey( query, "except" ) ) {
3214 return doCombo( ctx, query, flags );
3216 // It is presumably a SELECT query
3218 // Push a node onto the stack for the current query. Every level of
3219 // subquery gets its own QueryFrame on the Stack.
3222 // Build an SQL SELECT statement
3225 jsonObjectGetKey( query, "select" ),
3226 jsonObjectGetKey( query, "from" ),
3227 jsonObjectGetKey( query, "where" ),
3228 jsonObjectGetKey( query, "having" ),
3229 jsonObjectGetKey( query, "order_by" ),
3230 jsonObjectGetKey( query, "limit" ),
3231 jsonObjectGetKey( query, "offset" ),
3240 /* method context */ osrfMethodContext* ctx,
3242 /* SELECT */ jsonObject* selhash,
3243 /* FROM */ jsonObject* join_hash,
3244 /* WHERE */ jsonObject* search_hash,
3245 /* HAVING */ jsonObject* having_hash,
3246 /* ORDER BY */ jsonObject* order_hash,
3247 /* LIMIT */ jsonObject* limit,
3248 /* OFFSET */ jsonObject* offset,
3249 /* flags */ int flags
3251 const char* locale = osrf_message_get_last_locale();
3253 // general tmp objects
3254 const jsonObject* tmp_const;
3255 jsonObject* selclass = NULL;
3256 jsonObject* snode = NULL;
3257 jsonObject* onode = NULL;
3259 char* string = NULL;
3260 int from_function = 0;
3265 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3267 // punt if there's no FROM clause
3268 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3271 "%s: FROM clause is missing or empty",
3275 osrfAppSessionStatus(
3277 OSRF_STATUS_INTERNALSERVERERROR,
3278 "osrfMethodException",
3280 "FROM clause is missing or empty in JSON query"
3285 // the core search class
3286 const char* core_class = NULL;
3288 // get the core class -- the only key of the top level FROM clause, or a string
3289 if (join_hash->type == JSON_HASH) {
3290 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3291 snode = jsonIteratorNext( tmp_itr );
3293 // Populate the current QueryFrame with information
3294 // about the core class
3295 if( add_query_core( NULL, tmp_itr->key ) ) {
3297 osrfAppSessionStatus(
3299 OSRF_STATUS_INTERNALSERVERERROR,
3300 "osrfMethodException",
3302 "Unable to look up core class"
3306 core_class = curr_query->core.class_name;
3309 jsonObject* extra = jsonIteratorNext( tmp_itr );
3311 jsonIteratorFree( tmp_itr );
3314 // There shouldn't be more than one entry in join_hash
3318 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3322 osrfAppSessionStatus(
3324 OSRF_STATUS_INTERNALSERVERERROR,
3325 "osrfMethodException",
3327 "Malformed FROM clause in JSON query"
3329 return NULL; // Malformed join_hash; extra entry
3331 } else if (join_hash->type == JSON_ARRAY) {
3332 // We're selecting from a function, not from a table
3334 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
3337 } else if (join_hash->type == JSON_STRING) {
3338 // Populate the current QueryFrame with information
3339 // about the core class
3340 core_class = jsonObjectGetString( join_hash );
3342 if( add_query_core( NULL, core_class ) ) {
3344 osrfAppSessionStatus(
3346 OSRF_STATUS_INTERNALSERVERERROR,
3347 "osrfMethodException",
3349 "Unable to look up core class"
3357 "%s: FROM clause is unexpected JSON type: %s",
3359 json_type( join_hash->type )
3362 osrfAppSessionStatus(
3364 OSRF_STATUS_INTERNALSERVERERROR,
3365 "osrfMethodException",
3367 "Ill-formed FROM clause in JSON query"
3372 // Build the join clause, if any, while filling out the list
3373 // of joined classes in the current QueryFrame.
3374 char* join_clause = NULL;
3375 if( join_hash && ! from_function ) {
3377 join_clause = searchJOIN( join_hash, &curr_query->core );
3378 if( ! join_clause ) {
3380 osrfAppSessionStatus(
3382 OSRF_STATUS_INTERNALSERVERERROR,
3383 "osrfMethodException",
3385 "Unable to construct JOIN clause(s)"
3391 // For in case we don't get a select list
3392 jsonObject* defaultselhash = NULL;
3394 // if there is no select list, build a default select list ...
3395 if (!selhash && !from_function) {
3396 jsonObject* default_list = defaultSelectList( core_class );
3397 if( ! default_list ) {
3399 osrfAppSessionStatus(
3401 OSRF_STATUS_INTERNALSERVERERROR,
3402 "osrfMethodException",
3404 "Unable to build default SELECT clause in JSON query"
3406 free( join_clause );
3411 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3412 jsonObjectSetKey( selhash, core_class, default_list );
3415 // The SELECT clause can be encoded only by a hash
3416 if( !from_function && selhash->type != JSON_HASH ) {
3419 "%s: Expected JSON_HASH for SELECT clause; found %s",
3421 json_type( selhash->type )
3425 osrfAppSessionStatus(
3427 OSRF_STATUS_INTERNALSERVERERROR,
3428 "osrfMethodException",
3430 "Malformed SELECT clause in JSON query"
3432 free( join_clause );
3436 // If you see a null or wild card specifier for the core class, or an
3437 // empty array, replace it with a default SELECT list
3438 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3440 int default_needed = 0; // boolean
3441 if( JSON_STRING == tmp_const->type
3442 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3444 else if( JSON_NULL == tmp_const->type )
3447 if( default_needed ) {
3448 // Build a default SELECT list
3449 jsonObject* default_list = defaultSelectList( core_class );
3450 if( ! default_list ) {
3452 osrfAppSessionStatus(
3454 OSRF_STATUS_INTERNALSERVERERROR,
3455 "osrfMethodException",
3457 "Can't build default SELECT clause in JSON query"
3459 free( join_clause );
3464 jsonObjectSetKey( selhash, core_class, default_list );
3468 // temp buffers for the SELECT list and GROUP BY clause
3469 growing_buffer* select_buf = buffer_init(128);
3470 growing_buffer* group_buf = buffer_init(128);
3472 int aggregate_found = 0; // boolean
3474 // Build a select list
3475 if(from_function) // From a function we select everything
3476 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3479 // Build the SELECT list as SQL
3483 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3484 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3486 const char* cname = selclass_itr->key;
3488 // Make sure the target relation is in the FROM clause.
3490 // At this point join_hash is a step down from the join_hash we
3491 // received as a parameter. If the original was a JSON_STRING,
3492 // then json_hash is now NULL. If the original was a JSON_HASH,
3493 // then json_hash is now the first (and only) entry in it,
3494 // denoting the core class. We've already excluded the
3495 // possibility that the original was a JSON_ARRAY, because in
3496 // that case from_function would be non-NULL, and we wouldn't
3499 // If the current table alias isn't in scope, bail out
3500 ClassInfo* class_info = search_alias( cname );
3501 if( ! class_info ) {
3504 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3509 osrfAppSessionStatus(
3511 OSRF_STATUS_INTERNALSERVERERROR,
3512 "osrfMethodException",
3514 "Selected class not in FROM clause in JSON query"
3516 jsonIteratorFree( selclass_itr );
3517 buffer_free( select_buf );
3518 buffer_free( group_buf );
3519 if( defaultselhash ) jsonObjectFree( defaultselhash );
3520 free( join_clause );
3524 if( selclass->type != JSON_ARRAY ) {
3527 "%s: Malformed SELECT list for class \"%s\"; not an array",
3532 osrfAppSessionStatus(
3534 OSRF_STATUS_INTERNALSERVERERROR,
3535 "osrfMethodException",
3537 "Selected class not in FROM clause in JSON query"
3540 jsonIteratorFree( selclass_itr );
3541 buffer_free( select_buf );
3542 buffer_free( group_buf );
3543 if( defaultselhash ) jsonObjectFree( defaultselhash );
3544 free( join_clause );
3548 // Look up some attributes of the current class
3549 osrfHash* idlClass = class_info->class_def;
3550 osrfHash* class_field_set = class_info->fields;
3551 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3552 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3554 if( 0 == selclass->size ) {
3557 "%s: No columns selected from \"%s\"",
3563 // stitch together the column list for the current table alias...
3564 unsigned long field_idx = 0;
3565 jsonObject* selfield = NULL;
3566 while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3568 // If we need a separator comma, add one
3572 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3575 // if the field specification is a string, add it to the list
3576 if (selfield->type == JSON_STRING) {
3578 // Look up the field in the IDL
3579 const char* col_name = jsonObjectGetString( selfield );
3580 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3582 // No such field in current class
3585 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3591 osrfAppSessionStatus(
3593 OSRF_STATUS_INTERNALSERVERERROR,
3594 "osrfMethodException",
3596 "Selected column not defined in JSON query"
3598 jsonIteratorFree( selclass_itr );
3599 buffer_free( select_buf );
3600 buffer_free( group_buf );
3601 if( defaultselhash ) jsonObjectFree( defaultselhash );
3602 free( join_clause );
3604 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3605 // Virtual field not allowed
3608 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3614 osrfAppSessionStatus(
3616 OSRF_STATUS_INTERNALSERVERERROR,
3617 "osrfMethodException",
3619 "Selected column may not be virtual in JSON query"
3621 jsonIteratorFree( selclass_itr );
3622 buffer_free( select_buf );
3623 buffer_free( group_buf );
3624 if( defaultselhash ) jsonObjectFree( defaultselhash );
3625 free( join_clause );
3631 if (flags & DISABLE_I18N)
3634 i18n = osrfHashGet(field_def, "i18n");
3636 if( str_is_true( i18n ) ) {
3637 buffer_fadd( select_buf,
3638 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3639 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3641 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3644 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3647 // ... but it could be an object, in which case we check for a Field Transform
3648 } else if (selfield->type == JSON_HASH) {
3650 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3652 // Get the field definition from the IDL
3653 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3655 // No such field in current class
3658 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3664 osrfAppSessionStatus(
3666 OSRF_STATUS_INTERNALSERVERERROR,
3667 "osrfMethodException",
3669 "Selected column is not defined in JSON query"
3671 jsonIteratorFree( selclass_itr );
3672 buffer_free( select_buf );
3673 buffer_free( group_buf );
3674 if( defaultselhash ) jsonObjectFree( defaultselhash );
3675 free( join_clause );
3677 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3678 // No such field in current class
3681 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3687 osrfAppSessionStatus(
3689 OSRF_STATUS_INTERNALSERVERERROR,
3690 "osrfMethodException",
3692 "Selected column is virtual in JSON query"
3694 jsonIteratorFree( selclass_itr );
3695 buffer_free( select_buf );
3696 buffer_free( group_buf );
3697 if( defaultselhash ) jsonObjectFree( defaultselhash );
3698 free( join_clause );
3702 // Decide what to use as a column alias
3704 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3705 _alias = jsonObjectGetString( tmp_const );
3706 } else { // Use field name as the alias
3710 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3711 char* transform_str = searchFieldTransform(class_info->alias, field_def, selfield);
3712 if( transform_str ) {
3713 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3714 free(transform_str);
3717 osrfAppSessionStatus(
3719 OSRF_STATUS_INTERNALSERVERERROR,
3720 "osrfMethodException",
3722 "Unable to generate transform function in JSON query"
3724 jsonIteratorFree( selclass_itr );
3725 buffer_free( select_buf );
3726 buffer_free( group_buf );
3727 if( defaultselhash ) jsonObjectFree( defaultselhash );
3728 free( join_clause );
3735 if (flags & DISABLE_I18N)
3738 i18n = osrfHashGet(field_def, "i18n");
3740 if( str_is_true( i18n ) ) {
3741 buffer_fadd( select_buf,
3742 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3743 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3745 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3748 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3755 "%s: Selected item is unexpected JSON type: %s",
3757 json_type( selfield->type )
3760 osrfAppSessionStatus(
3762 OSRF_STATUS_INTERNALSERVERERROR,
3763 "osrfMethodException",
3765 "Ill-formed SELECT item in JSON query"
3767 jsonIteratorFree( selclass_itr );
3768 buffer_free( select_buf );
3769 buffer_free( group_buf );
3770 if( defaultselhash ) jsonObjectFree( defaultselhash );
3771 free( join_clause );
3775 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3776 if( obj_is_true( agg_obj ) )
3777 aggregate_found = 1;
3779 // Append a comma (except for the first one)
3780 // and add the column to a GROUP BY clause
3784 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3786 buffer_fadd(group_buf, " %d", sel_pos);
3790 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3792 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3793 if ( ! obj_is_true( aggregate_obj ) ) {
3797 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3800 buffer_fadd(group_buf, " %d", sel_pos);
3803 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3807 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3810 _column = searchFieldTransform(class_info->alias, field, selfield);
3811 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3812 OSRF_BUFFER_ADD(group_buf, _column);
3813 _column = searchFieldTransform(class_info->alias, field, selfield);
3820 } // end while -- iterating across SELECT columns
3822 } // end while -- iterating across classes
3824 jsonIteratorFree(selclass_itr);
3828 char* col_list = buffer_release(select_buf);
3830 // Make sure the SELECT list isn't empty. This can happen, for example,
3831 // if we try to build a default SELECT clause from a non-core table.
3834 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
3836 osrfAppSessionStatus(
3838 OSRF_STATUS_INTERNALSERVERERROR,
3839 "osrfMethodException",
3841 "SELECT list is empty"
3844 buffer_free( group_buf );
3845 if( defaultselhash ) jsonObjectFree( defaultselhash );
3846 free( join_clause );
3851 if (from_function) table = searchValueTransform(join_hash);
3852 else table = strdup( curr_query->core.source_def );
3856 osrfAppSessionStatus(
3858 OSRF_STATUS_INTERNALSERVERERROR,
3859 "osrfMethodException",
3861 "Unable to identify table for core class"
3864 buffer_free( group_buf );
3865 if( defaultselhash ) jsonObjectFree( defaultselhash );
3866 free( join_clause );
3870 // Put it all together
3871 growing_buffer* sql_buf = buffer_init(128);
3872 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3876 // Append the join clause, if any
3878 buffer_add(sql_buf, join_clause);
3882 char* order_by_list = NULL;
3883 char* having_buf = NULL;
3885 if (!from_function) {
3887 // Build a WHERE clause, if there is one
3888 if ( search_hash ) {
3889 buffer_add(sql_buf, " WHERE ");
3891 // and it's on the WHERE clause
3892 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
3895 osrfAppSessionStatus(
3897 OSRF_STATUS_INTERNALSERVERERROR,
3898 "osrfMethodException",
3900 "Severe query error in WHERE predicate -- see error log for more details"
3903 buffer_free(group_buf);
3904 buffer_free(sql_buf);
3905 if (defaultselhash) jsonObjectFree(defaultselhash);
3909 buffer_add(sql_buf, pred);
3913 // Build a HAVING clause, if there is one
3914 if ( having_hash ) {
3916 // and it's on the the WHERE clause
3917 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
3919 if( ! having_buf ) {
3921 osrfAppSessionStatus(
3923 OSRF_STATUS_INTERNALSERVERERROR,
3924 "osrfMethodException",
3926 "Severe query error in HAVING predicate -- see error log for more details"
3929 buffer_free(group_buf);
3930 buffer_free(sql_buf);
3931 if (defaultselhash) jsonObjectFree(defaultselhash);
3936 growing_buffer* order_buf = NULL; // to collect ORDER BY list
3938 // Build an ORDER BY clause, if there is one
3939 if( NULL == order_hash )
3940 ; // No ORDER BY? do nothing
3941 else if( JSON_ARRAY == order_hash->type ) {
3942 // Array of field specifications, each specification being a
3943 // hash to define the class, field, and other details
3945 jsonObject* order_spec;
3946 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3948 if( JSON_HASH != order_spec->type ) {
3949 osrfLogError(OSRF_LOG_MARK,
3950 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3951 MODULENAME, json_type( order_spec->type ) );
3953 osrfAppSessionStatus(
3955 OSRF_STATUS_INTERNALSERVERERROR,
3956 "osrfMethodException",
3958 "Malformed ORDER BY clause -- see error log for more details"
3960 buffer_free( order_buf );
3962 buffer_free(group_buf);
3963 buffer_free(sql_buf);
3964 if (defaultselhash) jsonObjectFree(defaultselhash);
3968 const char* class_alias =
3969 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3971 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3974 OSRF_BUFFER_ADD(order_buf, ", ");
3976 order_buf = buffer_init(128);
3978 if( !field || !class_alias ) {
3979 osrfLogError(OSRF_LOG_MARK,
3980 "%s: Missing class or field name in field specification of ORDER BY clause",
3983 osrfAppSessionStatus(
3985 OSRF_STATUS_INTERNALSERVERERROR,
3986 "osrfMethodException",
3988 "Malformed ORDER BY clause -- see error log for more details"
3990 buffer_free( order_buf );
3992 buffer_free(group_buf);
3993 buffer_free(sql_buf);
3994 if (defaultselhash) jsonObjectFree(defaultselhash);
3998 ClassInfo* order_class_info = search_alias( class_alias );
3999 if( ! order_class_info ) {
4000 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
4001 "not in FROM clause", MODULENAME, class_alias );
4003 osrfAppSessionStatus(
4005 OSRF_STATUS_INTERNALSERVERERROR,
4006 "osrfMethodException",
4008 "Invalid class referenced in ORDER BY clause -- see error log for more details"
4011 buffer_free(group_buf);
4012 buffer_free(sql_buf);
4013 if (defaultselhash) jsonObjectFree(defaultselhash);
4017 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
4019 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
4020 MODULENAME, class_alias, field );
4022 osrfAppSessionStatus(
4024 OSRF_STATUS_INTERNALSERVERERROR,
4025 "osrfMethodException",
4027 "Invalid field referenced in ORDER BY clause -- see error log for more details"
4030 buffer_free(group_buf);
4031 buffer_free(sql_buf);
4032 if (defaultselhash) jsonObjectFree(defaultselhash);
4034 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4035 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4036 MODULENAME, field );
4038 osrfAppSessionStatus(
4040 OSRF_STATUS_INTERNALSERVERERROR,
4041 "osrfMethodException",
4043 "Virtual field in ORDER BY clause -- see error log for more details"
4045 buffer_free( order_buf );
4047 buffer_free(group_buf);
4048 buffer_free(sql_buf);
4049 if (defaultselhash) jsonObjectFree(defaultselhash);
4053 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4054 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
4055 if( ! transform_str ) {
4057 osrfAppSessionStatus(
4059 OSRF_STATUS_INTERNALSERVERERROR,
4060 "osrfMethodException",
4062 "Severe query error in ORDER BY clause -- see error log for more details"
4064 buffer_free( order_buf );
4066 buffer_free(group_buf);
4067 buffer_free(sql_buf);
4068 if (defaultselhash) jsonObjectFree(defaultselhash);
4072 OSRF_BUFFER_ADD( order_buf, transform_str );
4073 free( transform_str );
4076 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4078 const char* direction =
4079 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4081 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4082 OSRF_BUFFER_ADD( order_buf, " DESC" );
4084 OSRF_BUFFER_ADD( order_buf, " ASC" );
4087 } else if( JSON_HASH == order_hash->type ) {
4088 // This hash is keyed on class alias. Each class has either
4089 // an array of field names or a hash keyed on field name.
4090 jsonIterator* class_itr = jsonNewIterator( order_hash );
4091 while ( (snode = jsonIteratorNext( class_itr )) ) {
4093 ClassInfo* order_class_info = search_alias( class_itr->key );
4094 if( ! order_class_info ) {
4095 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4096 MODULENAME, class_itr->key );
4098 osrfAppSessionStatus(
4100 OSRF_STATUS_INTERNALSERVERERROR,
4101 "osrfMethodException",
4103 "Invalid class referenced in ORDER BY clause -- see error log for more details"
4105 jsonIteratorFree( class_itr );
4106 buffer_free( order_buf );
4108 buffer_free(group_buf);
4109 buffer_free(sql_buf);
4110 if (defaultselhash) jsonObjectFree(defaultselhash);
4114 osrfHash* field_list_def = order_class_info->fields;
4116 if ( snode->type == JSON_HASH ) {
4118 // Hash is keyed on field names from the current class. For each field
4119 // there is another layer of hash to define the sorting details, if any,
4120 // or a string to indicate direction of sorting.
4121 jsonIterator* order_itr = jsonNewIterator( snode );
4122 while ( (onode = jsonIteratorNext( order_itr )) ) {
4124 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4126 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4127 MODULENAME, order_itr->key );
4129 osrfAppSessionStatus(
4131 OSRF_STATUS_INTERNALSERVERERROR,
4132 "osrfMethodException",
4134 "Invalid field in ORDER BY clause -- see error log for more details"
4136 jsonIteratorFree( order_itr );
4137 jsonIteratorFree( class_itr );
4138 buffer_free( order_buf );
4140 buffer_free(group_buf);
4141 buffer_free(sql_buf);
4142 if (defaultselhash) jsonObjectFree(defaultselhash);
4144 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4145 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4146 MODULENAME, order_itr->key );
4148 osrfAppSessionStatus(
4150 OSRF_STATUS_INTERNALSERVERERROR,
4151 "osrfMethodException",
4153 "Virtual field in ORDER BY clause -- see error log for more details"
4155 jsonIteratorFree( order_itr );
4156 jsonIteratorFree( class_itr );
4157 buffer_free( order_buf );
4159 buffer_free(group_buf);
4160 buffer_free(sql_buf);
4161 if (defaultselhash) jsonObjectFree(defaultselhash);
4165 const char* direction = NULL;
4166 if ( onode->type == JSON_HASH ) {
4167 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4168 string = searchFieldTransform(
4170 osrfHashGet( field_list_def, order_itr->key ),
4174 if( ctx ) osrfAppSessionStatus(
4176 OSRF_STATUS_INTERNALSERVERERROR,
4177 "osrfMethodException",
4179 "Severe query error in ORDER BY clause -- see error log for more details"
4181 jsonIteratorFree( order_itr );
4182 jsonIteratorFree( class_itr );
4184 buffer_free(group_buf);
4185 buffer_free(order_buf);
4186 buffer_free(sql_buf);
4187 if (defaultselhash) jsonObjectFree(defaultselhash);
4191 growing_buffer* field_buf = buffer_init(16);
4192 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4193 string = buffer_release(field_buf);
4196 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4197 const char* dir = jsonObjectGetString(tmp_const);
4198 if (!strncasecmp(dir, "d", 1)) {
4199 direction = " DESC";
4205 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4206 osrfLogError( OSRF_LOG_MARK,
4207 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4208 MODULENAME, json_type( onode->type ) );
4210 osrfAppSessionStatus(
4212 OSRF_STATUS_INTERNALSERVERERROR,
4213 "osrfMethodException",
4215 "Malformed ORDER BY clause -- see error log for more details"
4217 jsonIteratorFree( order_itr );
4218 jsonIteratorFree( class_itr );
4220 buffer_free(group_buf);
4221 buffer_free(order_buf);
4222 buffer_free(sql_buf);
4223 if (defaultselhash) jsonObjectFree(defaultselhash);
4227 string = strdup(order_itr->key);
4228 const char* dir = jsonObjectGetString(onode);
4229 if (!strncasecmp(dir, "d", 1)) {
4230 direction = " DESC";
4237 OSRF_BUFFER_ADD(order_buf, ", ");
4239 order_buf = buffer_init(128);
4241 OSRF_BUFFER_ADD(order_buf, string);
4245 OSRF_BUFFER_ADD(order_buf, direction);
4249 jsonIteratorFree(order_itr);
4251 } else if ( snode->type == JSON_ARRAY ) {
4253 // Array is a list of fields from the current class
4254 unsigned long order_idx = 0;
4255 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4257 const char* _f = jsonObjectGetString( onode );
4259 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4261 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4264 osrfAppSessionStatus(
4266 OSRF_STATUS_INTERNALSERVERERROR,
4267 "osrfMethodException",
4269 "Invalid field in ORDER BY clause -- see error log for more details"
4271 jsonIteratorFree( class_itr );
4272 buffer_free( order_buf );
4274 buffer_free(group_buf);
4275 buffer_free(sql_buf);
4276 if (defaultselhash) jsonObjectFree(defaultselhash);
4278 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4279 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4282 osrfAppSessionStatus(
4284 OSRF_STATUS_INTERNALSERVERERROR,
4285 "osrfMethodException",
4287 "Virtual field in ORDER BY clause -- see error log for more details"
4289 jsonIteratorFree( class_itr );
4290 buffer_free( order_buf );
4292 buffer_free(group_buf);
4293 buffer_free(sql_buf);
4294 if (defaultselhash) jsonObjectFree(defaultselhash);
4299 OSRF_BUFFER_ADD(order_buf, ", ");
4301 order_buf = buffer_init(128);
4303 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
4307 // IT'S THE OOOOOOOOOOOLD STYLE!
4309 osrfLogError(OSRF_LOG_MARK,
4310 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
4312 osrfAppSessionStatus(
4314 OSRF_STATUS_INTERNALSERVERERROR,
4315 "osrfMethodException",
4317 "Severe query error -- see error log for more details"
4322 buffer_free(group_buf);
4323 buffer_free(order_buf);
4324 buffer_free(sql_buf);
4325 if (defaultselhash) jsonObjectFree(defaultselhash);
4326 jsonIteratorFree(class_itr);
4330 jsonIteratorFree( class_itr );
4332 osrfLogError(OSRF_LOG_MARK,
4333 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4334 MODULENAME, json_type( order_hash->type ) );
4336 osrfAppSessionStatus(
4338 OSRF_STATUS_INTERNALSERVERERROR,
4339 "osrfMethodException",
4341 "Malformed ORDER BY clause -- see error log for more details"
4343 buffer_free( order_buf );
4345 buffer_free(group_buf);
4346 buffer_free(sql_buf);
4347 if (defaultselhash) jsonObjectFree(defaultselhash);
4352 order_by_list = buffer_release( order_buf );
4356 string = buffer_release(group_buf);
4358 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4359 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4360 OSRF_BUFFER_ADD( sql_buf, string );
4365 if( having_buf && *having_buf ) {
4366 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4367 OSRF_BUFFER_ADD( sql_buf, having_buf );
4371 if( order_by_list ) {
4373 if ( *order_by_list ) {
4374 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4375 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4378 free( order_by_list );
4382 const char* str = jsonObjectGetString(limit);
4383 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
4387 const char* str = jsonObjectGetString(offset);
4388 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
4391 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4393 if (defaultselhash) jsonObjectFree(defaultselhash);
4395 return buffer_release(sql_buf);
4397 } // end of SELECT()
4399 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4401 const char* locale = osrf_message_get_last_locale();
4403 osrfHash* fields = osrfHashGet(meta, "fields");
4404 char* core_class = osrfHashGet(meta, "classname");
4406 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4408 jsonObject* node = NULL;
4409 jsonObject* snode = NULL;
4410 jsonObject* onode = NULL;
4411 const jsonObject* _tmp = NULL;
4412 jsonObject* selhash = NULL;
4413 jsonObject* defaultselhash = NULL;
4415 growing_buffer* sql_buf = buffer_init(128);
4416 growing_buffer* select_buf = buffer_init(128);
4418 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4419 defaultselhash = jsonNewObjectType(JSON_HASH);
4420 selhash = defaultselhash;
4423 // If there's no SELECT list for the core class, build one
4424 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4425 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4427 // Add every non-virtual field to the field list
4428 osrfHash* field_def = NULL;
4429 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4430 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4431 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4432 const char* field = osrfHashIteratorKey( field_itr );
4433 jsonObjectPush( field_list, jsonNewObject( field ) );
4436 osrfHashIteratorFree( field_itr );
4437 jsonObjectSetKey( selhash, core_class, field_list );
4441 jsonIterator* class_itr = jsonNewIterator( selhash );
4442 while ( (snode = jsonIteratorNext( class_itr )) ) {
4444 const char* cname = class_itr->key;
4445 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4446 if (!idlClass) continue;
4448 if (strcmp(core_class,class_itr->key)) {
4449 if (!join_hash) continue;
4451 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4453 jsonObjectFree(found);
4457 jsonObjectFree(found);
4460 jsonIterator* select_itr = jsonNewIterator( snode );
4461 while ( (node = jsonIteratorNext( select_itr )) ) {
4462 const char* item_str = jsonObjectGetString( node );
4463 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4464 char* fname = osrfHashGet(field, "name");
4466 if (!field) continue;
4471 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
4476 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4477 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4480 i18n = osrfHashGet(field, "i18n");
4482 if( str_is_true( i18n ) ) {
4483 char* pkey = osrfHashGet(idlClass, "primarykey");
4484 char* tname = osrfHashGet(idlClass, "tablename");
4486 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);
4488 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4491 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4495 jsonIteratorFree(select_itr);
4498 jsonIteratorFree(class_itr);
4500 char* col_list = buffer_release(select_buf);
4501 char* table = getRelation(meta);
4503 table = strdup( "(null)" );
4505 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4509 // Clear the query stack (as a fail-safe precaution against possible
4510 // leftover garbage); then push the first query frame onto the stack.
4511 clear_query_stack();
4513 if( add_query_core( NULL, core_class ) ) {
4515 osrfAppSessionStatus(
4517 OSRF_STATUS_INTERNALSERVERERROR,
4518 "osrfMethodException",
4520 "Unable to build query frame for core class"
4526 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4527 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
4528 OSRF_BUFFER_ADD(sql_buf, join_clause);
4532 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4533 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4535 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4537 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4539 osrfAppSessionStatus(
4541 OSRF_STATUS_INTERNALSERVERERROR,
4542 "osrfMethodException",
4544 "Severe query error -- see error log for more details"
4546 buffer_free(sql_buf);
4547 if(defaultselhash) jsonObjectFree(defaultselhash);
4548 clear_query_stack();
4551 buffer_add(sql_buf, pred);
4556 char* string = NULL;
4557 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4559 growing_buffer* order_buf = buffer_init(128);
4562 jsonIterator* class_itr = jsonNewIterator( _tmp );
4563 while ( (snode = jsonIteratorNext( class_itr )) ) {
4565 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4568 if ( snode->type == JSON_HASH ) {
4570 jsonIterator* order_itr = jsonNewIterator( snode );
4571 while ( (onode = jsonIteratorNext( order_itr )) ) {
4573 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4574 class_itr->key, order_itr->key );
4578 char* direction = NULL;
4579 if ( onode->type == JSON_HASH ) {
4580 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4581 string = searchFieldTransform( class_itr->key, field_def, onode );
4583 osrfAppSessionStatus(
4585 OSRF_STATUS_INTERNALSERVERERROR,
4586 "osrfMethodException",
4588 "Severe query error in ORDER BY clause -- see error log for more details"
4590 jsonIteratorFree( order_itr );
4591 jsonIteratorFree( class_itr );
4592 buffer_free( order_buf );
4593 buffer_free( sql_buf );
4594 if( defaultselhash ) jsonObjectFree( defaultselhash );
4595 clear_query_stack();
4599 growing_buffer* field_buf = buffer_init(16);
4600 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4601 string = buffer_release(field_buf);
4604 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4605 const char* dir = jsonObjectGetString(_tmp);
4606 if (!strncasecmp(dir, "d", 1)) {
4607 direction = " DESC";
4614 string = strdup(order_itr->key);
4615 const char* dir = jsonObjectGetString(onode);
4616 if (!strncasecmp(dir, "d", 1)) {
4617 direction = " DESC";
4626 buffer_add(order_buf, ", ");
4629 buffer_add(order_buf, string);
4633 buffer_add(order_buf, direction);
4638 jsonIteratorFree(order_itr);
4641 const char* str = jsonObjectGetString(snode);
4642 buffer_add(order_buf, str);
4648 jsonIteratorFree(class_itr);
4650 string = buffer_release(order_buf);
4653 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4654 OSRF_BUFFER_ADD( sql_buf, string );
4660 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4661 const char* str = jsonObjectGetString(_tmp);
4669 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4671 const char* str = jsonObjectGetString(_tmp);
4680 if (defaultselhash) jsonObjectFree(defaultselhash);
4681 clear_query_stack();
4683 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4684 return buffer_release(sql_buf);
4687 int doJSONSearch ( osrfMethodContext* ctx ) {
4688 if(osrfMethodVerifyContext( ctx )) {
4689 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4693 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
4698 dbhandle = writehandle;
4700 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4704 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4705 flags |= SELECT_DISTINCT;
4707 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4708 flags |= DISABLE_I18N;
4710 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4711 clear_query_stack(); // a possibly needless precaution
4712 char* sql = buildQuery( ctx, hash, flags );
4713 clear_query_stack();
4720 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4721 dbi_result result = dbi_conn_query(dbhandle, sql);
4724 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4726 if (dbi_result_first_row(result)) {
4727 /* JSONify the result */
4728 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4731 jsonObject* return_val = oilsMakeJSONFromResult( result );
4732 osrfAppRespond( ctx, return_val );
4733 jsonObjectFree( return_val );
4734 } while (dbi_result_next_row(result));
4737 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4740 osrfAppRespondComplete( ctx, NULL );
4742 /* clean up the query */
4743 dbi_result_free(result);
4747 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4748 osrfAppSessionStatus(
4750 OSRF_STATUS_INTERNALSERVERERROR,
4751 "osrfMethodException",
4753 "Severe query error -- see error log for more details"
4761 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4762 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4765 dbhandle = writehandle;
4767 osrfHash* links = osrfHashGet(meta, "links");
4768 osrfHash* fields = osrfHashGet(meta, "fields");
4769 char* core_class = osrfHashGet(meta, "classname");
4770 char* pkey = osrfHashGet(meta, "primarykey");
4772 const jsonObject* _tmp;
4775 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4777 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4782 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4784 dbi_result result = dbi_conn_query(dbhandle, sql);
4785 if( NULL == result ) {
4786 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4787 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4788 osrfAppSessionStatus(
4790 OSRF_STATUS_INTERNALSERVERERROR,
4791 "osrfMethodException",
4793 "Severe query error -- see error log for more details"
4800 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4803 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4804 osrfHash* dedup = osrfNewHash();
4806 if (dbi_result_first_row(result)) {
4807 /* JSONify the result */
4808 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4810 obj = oilsMakeFieldmapperFromResult( result, meta );
4811 char* pkey_val = oilsFMGetString( obj, pkey );
4812 if ( osrfHashGet( dedup, pkey_val ) ) {
4813 jsonObjectFree(obj);
4816 osrfHashSet( dedup, pkey_val, pkey_val );
4817 jsonObjectPush(res_list, obj);
4819 } while (dbi_result_next_row(result));
4821 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4825 osrfHashFree(dedup);
4826 /* clean up the query */
4827 dbi_result_free(result);
4830 if (res_list->size && query_hash) {
4831 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4833 int x = (int)jsonObjectGetNumber(_tmp);
4834 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4836 const jsonObject* temp_blob;
4837 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4839 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4840 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4842 osrfStringArray* link_fields = NULL;
4845 if (flesh_fields->size == 1) {
4846 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4847 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4852 link_fields = osrfNewStringArray(1);
4853 jsonIterator* _i = jsonNewIterator( flesh_fields );
4854 while ((_f = jsonIteratorNext( _i ))) {
4855 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4857 jsonIteratorFree(_i);
4862 unsigned long res_idx = 0;
4863 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
4866 const char* link_field;
4868 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4870 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4872 osrfHash* kid_link = osrfHashGet(links, link_field);
4873 if (!kid_link) continue;
4875 osrfHash* field = osrfHashGet(fields, link_field);
4876 if (!field) continue;
4878 osrfHash* value_field = field;
4880 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4881 if (!kid_idl) continue;
4883 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4884 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4887 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4888 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4891 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4893 if (link_map->size > 0) {
4894 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4897 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4902 osrfHashGet(kid_link, "class"),
4909 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4910 osrfHashGet(kid_link, "field"),
4911 osrfHashGet(kid_link, "class"),
4912 osrfHashGet(kid_link, "key"),
4913 osrfHashGet(kid_link, "reltype")
4916 const char* search_key = jsonObjectGetString(
4919 atoi( osrfHashGet(value_field, "array_position") )
4924 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4928 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4930 // construct WHERE clause
4931 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
4934 osrfHashGet(kid_link, "key"),
4935 jsonNewObject( search_key )
4938 // construct the rest of the query
4939 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4940 jsonObjectSetKey( rest_of_query, "flesh",
4941 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4945 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4947 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4948 jsonObjectSetKey( rest_of_query, "order_by",
4949 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4953 if (jsonObjectGetKeyConst(query_hash, "select")) {
4954 jsonObjectSetKey( rest_of_query, "select",
4955 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4959 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4960 where_clause, rest_of_query, err);
4962 jsonObjectFree( where_clause );
4963 jsonObjectFree( rest_of_query );
4966 osrfStringArrayFree(link_fields);
4967 jsonObjectFree(res_list);
4968 jsonObjectFree(flesh_blob);
4972 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4974 jsonObject* X = NULL;
4975 if ( link_map->size > 0 && kids->size > 0 ) {
4977 kids = jsonNewObjectType(JSON_ARRAY);
4979 jsonObject* _k_node;
4980 unsigned long res_idx = 0;
4981 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
4987 (unsigned long)atoi(
4993 osrfHashGet(kid_link, "class")
4997 osrfStringArrayGetString( link_map, 0 )
5005 } // end while loop traversing X
5008 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
5009 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
5012 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5013 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
5017 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
5018 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
5021 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
5022 jsonObjectClone( kids )
5027 jsonObjectFree(kids);
5031 jsonObjectFree( kids );
5033 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
5034 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
5037 } // end while loop traversing res_list
5038 jsonObjectFree( flesh_blob );
5039 osrfStringArrayFree(link_fields);
5048 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
5050 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5052 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
5054 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
5057 if (!verifyObjectClass(ctx, target)) {
5062 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
5063 osrfAppSessionStatus(
5065 OSRF_STATUS_BADREQUEST,
5066 "osrfMethodException",
5068 "No active transaction -- required for UPDATE"
5074 // The following test is harmless but redundant. If a class is
5075 // readonly, we don't register an update method for it.
5076 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5077 osrfAppSessionStatus(
5079 OSRF_STATUS_BADREQUEST,
5080 "osrfMethodException",
5082 "Cannot UPDATE readonly class"
5088 dbhandle = writehandle;
5090 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
5092 // Set the last_xact_id
5093 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5095 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5096 trans_id, target->classname, index);
5097 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
5100 char* pkey = osrfHashGet(meta, "primarykey");
5101 osrfHash* fields = osrfHashGet(meta, "fields");
5103 char* id = oilsFMGetString( target, pkey );
5107 "%s updating %s object with %s = %s",
5109 osrfHashGet(meta, "fieldmapper"),
5114 growing_buffer* sql = buffer_init(128);
5115 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
5118 osrfHash* field_def = NULL;
5119 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5120 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
5122 // Skip virtual fields, and the primary key
5123 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5126 const char* field_name = osrfHashIteratorKey( field_itr );
5127 if( ! strcmp( field_name, pkey ) )
5130 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5132 int value_is_numeric = 0; // boolean
5134 if (field_object && field_object->classname) {
5135 value = oilsFMGetString(
5137 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
5139 } else if( field_object && JSON_BOOL == field_object->type ) {
5140 if( jsonBoolIsTrue( field_object ) )
5141 value = strdup( "t" );
5143 value = strdup( "f" );
5145 value = jsonObjectToSimpleString( field_object );
5146 if( field_object && JSON_NUMBER == field_object->type )
5147 value_is_numeric = 1;
5150 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5151 osrfHashGet(meta, "fieldmapper"), field_name, value);
5153 if (!field_object || field_object->type == JSON_NULL) {
5154 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
5155 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5156 if (first) first = 0;
5157 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5158 buffer_fadd( sql, " %s = NULL", field_name );
5161 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5162 if (first) first = 0;
5163 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5165 const char* numtype = get_datatype( field_def );
5166 if ( !strncmp( numtype, "INT", 3 ) ) {
5167 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
5168 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
5169 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
5171 // Must really be intended as a string, so quote it
5172 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5173 buffer_fadd( sql, " %s = %s", field_name, value );
5175 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5176 osrfAppSessionStatus(
5178 OSRF_STATUS_INTERNALSERVERERROR,
5179 "osrfMethodException",
5181 "Error quoting string -- please see the error log for more details"
5185 osrfHashIteratorFree( field_itr );
5192 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5195 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5196 if (first) first = 0;
5197 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5198 buffer_fadd( sql, " %s = %s", field_name, value );
5201 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5202 osrfAppSessionStatus(
5204 OSRF_STATUS_INTERNALSERVERERROR,
5205 "osrfMethodException",
5207 "Error quoting string -- please see the error log for more details"
5211 osrfHashIteratorFree( field_itr );
5222 osrfHashIteratorFree( field_itr );
5224 jsonObject* obj = jsonNewObject(id);
5226 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5227 dbi_conn_quote_string(dbhandle, &id);
5229 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5231 char* query = buffer_release(sql);
5232 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
5234 dbi_result result = dbi_conn_query(dbhandle, query);
5238 jsonObjectFree(obj);
5239 obj = jsonNewObject(NULL);
5242 "%s ERROR updating %s object with %s = %s",
5244 osrfHashGet(meta, "fieldmapper"),
5255 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
5257 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5259 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
5260 osrfAppSessionStatus(
5262 OSRF_STATUS_BADREQUEST,
5263 "osrfMethodException",
5265 "No active transaction -- required for DELETE"
5271 // The following test is harmless but redundant. If a class is
5272 // readonly, we don't register a delete method for it.
5273 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5274 osrfAppSessionStatus(
5276 OSRF_STATUS_BADREQUEST,
5277 "osrfMethodException",
5279 "Cannot DELETE readonly class"
5285 dbhandle = writehandle;
5289 char* pkey = osrfHashGet(meta, "primarykey");
5297 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
5298 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5303 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5306 if (!verifyObjectPCRUD( ctx, NULL )) {
5311 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
5316 "%s deleting %s object with %s = %s",
5318 osrfHashGet(meta, "fieldmapper"),
5323 obj = jsonNewObject(id);
5325 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5326 dbi_conn_quote_string(writehandle, &id);
5328 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
5331 jsonObjectFree(obj);
5332 obj = jsonNewObject(NULL);
5335 "%s ERROR deleting %s object with %s = %s",
5337 osrfHashGet(meta, "fieldmapper"),
5350 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5351 if(!(result && meta)) return jsonNULL;
5353 jsonObject* object = jsonNewObject(NULL);
5354 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
5356 osrfHash* fields = osrfHashGet(meta, "fields");
5358 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
5362 char dt_string[256];
5366 int columnIndex = 1;
5368 unsigned short type;
5369 const char* columnName;
5371 /* cycle through the column list */
5372 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5374 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5376 fmIndex = -1; // reset the position
5378 /* determine the field type and storage attributes */
5379 type = dbi_result_get_field_type_idx(result, columnIndex);
5380 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5382 /* fetch the fieldmapper index */
5383 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
5385 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
5388 const char* pos = (char*)osrfHashGet(_f, "array_position");
5389 if ( !pos ) continue;
5391 fmIndex = atoi( pos );
5392 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
5397 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5398 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
5403 case DBI_TYPE_INTEGER :
5405 if( attr & DBI_INTEGER_SIZE8 )
5406 jsonObjectSetIndex( object, fmIndex,
5407 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
5409 jsonObjectSetIndex( object, fmIndex,
5410 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
5414 case DBI_TYPE_DECIMAL :
5415 jsonObjectSetIndex( object, fmIndex,
5416 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
5419 case DBI_TYPE_STRING :
5425 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
5430 case DBI_TYPE_DATETIME :
5432 memset(dt_string, '\0', sizeof(dt_string));
5433 memset(&gmdt, '\0', sizeof(gmdt));
5435 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5438 if (!(attr & DBI_DATETIME_DATE)) {
5439 gmtime_r( &_tmp_dt, &gmdt );
5440 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5441 } else if (!(attr & DBI_DATETIME_TIME)) {
5442 localtime_r( &_tmp_dt, &gmdt );
5443 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5445 localtime_r( &_tmp_dt, &gmdt );
5446 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5449 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
5453 case DBI_TYPE_BINARY :
5454 osrfLogError( OSRF_LOG_MARK,
5455 "Can't do binary at column %s : index %d", columnName, columnIndex);
5464 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5465 if(!result) return jsonNULL;
5467 jsonObject* object = jsonNewObject(NULL);
5470 char dt_string[256];
5474 int columnIndex = 1;
5476 unsigned short type;
5477 const char* columnName;
5479 /* cycle through the column list */
5480 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5482 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5484 fmIndex = -1; // reset the position
5486 /* determine the field type and storage attributes */
5487 type = dbi_result_get_field_type_idx(result, columnIndex);
5488 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5490 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5491 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
5496 case DBI_TYPE_INTEGER :
5498 if( attr & DBI_INTEGER_SIZE8 )
5499 jsonObjectSetKey( object, columnName,
5500 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
5502 jsonObjectSetKey( object, columnName,
5503 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
5506 case DBI_TYPE_DECIMAL :
5507 jsonObjectSetKey( object, columnName,
5508 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
5511 case DBI_TYPE_STRING :
5512 jsonObjectSetKey( object, columnName,
5513 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
5516 case DBI_TYPE_DATETIME :
5518 memset(dt_string, '\0', sizeof(dt_string));
5519 memset(&gmdt, '\0', sizeof(gmdt));
5521 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5524 if (!(attr & DBI_DATETIME_DATE)) {
5525 gmtime_r( &_tmp_dt, &gmdt );
5526 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5527 } else if (!(attr & DBI_DATETIME_TIME)) {
5528 localtime_r( &_tmp_dt, &gmdt );
5529 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5531 localtime_r( &_tmp_dt, &gmdt );
5532 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5535 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5538 case DBI_TYPE_BINARY :
5539 osrfLogError( OSRF_LOG_MARK,
5540 "Can't do binary at column %s : index %d", columnName, columnIndex );
5544 } // end while loop traversing result
5549 // Interpret a string as true or false
5550 static int str_is_true( const char* str ) {
5551 if( NULL == str || strcasecmp( str, "true" ) )
5557 // Interpret a jsonObject as true or false
5558 static int obj_is_true( const jsonObject* obj ) {
5561 else switch( obj->type )
5569 if( strcasecmp( obj->value.s, "true" ) )
5573 case JSON_NUMBER : // Support 1/0 for perl's sake
5574 if( jsonObjectGetNumber( obj ) == 1.0 )
5583 // Translate a numeric code into a text string identifying a type of
5584 // jsonObject. To be used for building error messages.
5585 static const char* json_type( int code ) {
5591 return "JSON_ARRAY";
5593 return "JSON_STRING";
5595 return "JSON_NUMBER";
5601 return "(unrecognized)";
5605 // Extract the "primitive" attribute from an IDL field definition.
5606 // If we haven't initialized the app, then we must be running in
5607 // some kind of testbed. In that case, default to "string".
5608 static const char* get_primitive( osrfHash* field ) {
5609 const char* s = osrfHashGet( field, "primitive" );
5611 if( child_initialized )
5614 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5616 osrfHashGet( field, "name" )
5624 // Extract the "datatype" attribute from an IDL field definition.
5625 // If we haven't initialized the app, then we must be running in
5626 // some kind of testbed. In that case, default to to NUMERIC,
5627 // since we look at the datatype only for numbers.
5628 static const char* get_datatype( osrfHash* field ) {
5629 const char* s = osrfHashGet( field, "datatype" );
5631 if( child_initialized )
5634 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5636 osrfHashGet( field, "name" )
5645 If the input string is potentially a valid SQL identifier, return 1.
5648 Purpose: to prevent certain kinds of SQL injection. To that end we
5649 don't necessarily need to follow all the rules exactly, such as requiring
5650 that the first character not be a digit.
5652 We allow leading and trailing white space. In between, we do not allow
5653 punctuation (except for underscores and dollar signs), control
5654 characters, or embedded white space.
5656 More pedantically we should allow quoted identifiers containing arbitrary
5657 characters, but for the foreseeable future such quoted identifiers are not
5658 likely to be an issue.
5660 static int is_identifier( const char* s) {
5664 // Skip leading white space
5665 while( isspace( (unsigned char) *s ) )
5669 return 0; // Nothing but white space? Not okay.
5671 // Check each character until we reach white space or
5672 // end-of-string. Letters, digits, underscores, and
5673 // dollar signs are okay. With the exception of periods
5674 // (as in schema.identifier), control characters and other
5675 // punctuation characters are not okay. Anything else
5676 // is okay -- it could for example be part of a multibyte
5677 // UTF8 character such as a letter with diacritical marks,
5678 // and those are allowed.
5680 if( isalnum( (unsigned char) *s )
5684 ; // Fine; keep going
5685 else if( ispunct( (unsigned char) *s )
5686 || iscntrl( (unsigned char) *s ) )
5689 } while( *s && ! isspace( (unsigned char) *s ) );
5691 // If we found any white space in the above loop,
5692 // the rest had better be all white space.
5694 while( isspace( (unsigned char) *s ) )
5698 return 0; // White space was embedded within non-white space
5704 Determine whether to accept a character string as a comparison operator.
5705 Return 1 if it's good, or 0 if it's bad.
5707 We don't validate it for real. We just make sure that it doesn't contain
5708 any semicolons or white space (with special exceptions for a few specific
5709 operators). The idea is to block certain kinds of SQL injection. If it
5710 has no semicolons or white space but it's still not a valid operator, then
5711 the database will complain.
5713 Another approach would be to compare the string against a short list of
5714 approved operators. We don't do that because we want to allow custom
5715 operators like ">100*", which would be difficult or impossible to
5716 express otherwise in a JSON query.
5718 static int is_good_operator( const char* op ) {
5719 if( !op ) return 0; // Sanity check
5723 if( isspace( (unsigned char) *s ) ) {
5724 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
5725 // and IS NOT DISTINCT FROM.
5726 if( !strcasecmp( op, "similar to" ) )
5728 else if( !strcasecmp( op, "is distinct from" ) )
5730 else if( !strcasecmp( op, "is not distinct from" ) )
5735 else if( ';' == *s )
5742 /* ----------------------------------------------------------------------------------
5743 The following machinery supports a stack of query frames for use by SELECT().
5745 A query frame caches information about one level of a SELECT query. When we enter
5746 a subquery, we push another query frame onto the stack, and pop it off when we leave.
5748 The query frame stores information about the core class, and about any joined classes
5751 The main purpose is to map table aliases to classes and tables, so that a query can
5752 join to the same table more than once. A secondary goal is to reduce the number of
5753 lookups in the IDL by caching the results.
5754 ----------------------------------------------------------------------------------*/
5756 #define STATIC_CLASS_INFO_COUNT 3
5758 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
5760 /* ---------------------------------------------------------------------------
5761 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5763 ---------------------------------------------------------------------------*/
5764 static ClassInfo* allocate_class_info( void ) {
5765 // In order to reduce the number of mallocs and frees, we return a static
5766 // instance of ClassInfo, if we can find one that we're not already using.
5767 // We rely on the fact that the compiler will implicitly initialize the
5768 // static instances so that in_use == 0.
5771 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5772 if( ! static_class_info[ i ].in_use ) {
5773 static_class_info[ i ].in_use = 1;
5774 return static_class_info + i;
5778 // The static ones are all in use. Malloc one.
5780 return safe_malloc( sizeof( ClassInfo ) );
5783 /* --------------------------------------------------------------------------
5784 Free any malloc'd memory owned by a ClassInfo; return it to a pristine state
5785 ---------------------------------------------------------------------------*/
5786 static void clear_class_info( ClassInfo* info ) {
5791 // Free any malloc'd strings
5793 if( info->alias != info->alias_store )
5794 free( info->alias );
5796 if( info->class_name != info->class_name_store )
5797 free( info->class_name );
5799 free( info->source_def );
5801 info->alias = info->class_name = info->source_def = NULL;
5805 /* --------------------------------------------------------------------------
5806 Deallocate a ClassInfo and everything it owns
5807 ---------------------------------------------------------------------------*/
5808 static void free_class_info( ClassInfo* info ) {
5813 clear_class_info( info );
5815 // If it's one of the static instances, just mark it as not in use
5818 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5819 if( info == static_class_info + i ) {
5820 static_class_info[ i ].in_use = 0;
5825 // Otherwise it must have been malloc'd, so free it
5830 /* --------------------------------------------------------------------------
5831 Populate an already-allocated ClassInfo. Return 0 if successful, 1 if not.
5832 ---------------------------------------------------------------------------*/
5833 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
5836 osrfLogError( OSRF_LOG_MARK,
5837 "%s ERROR: No ClassInfo available to populate", MODULENAME );
5838 info->alias = info->class_name = info->source_def = NULL;
5839 info->class_def = info->fields = info->links = NULL;
5844 osrfLogError( OSRF_LOG_MARK,
5845 "%s ERROR: No class name provided for lookup", MODULENAME );
5846 info->alias = info->class_name = info->source_def = NULL;
5847 info->class_def = info->fields = info->links = NULL;
5851 // Alias defaults to class name if not supplied
5852 if( ! alias || ! alias[ 0 ] )
5855 // Look up class info in the IDL
5856 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
5858 osrfLogError( OSRF_LOG_MARK,
5859 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
5860 info->alias = info->class_name = info->source_def = NULL;
5861 info->class_def = info->fields = info->links = NULL;
5863 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
5864 osrfLogError( OSRF_LOG_MARK,
5865 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
5866 info->alias = info->class_name = info->source_def = NULL;
5867 info->class_def = info->fields = info->links = NULL;
5871 osrfHash* links = osrfHashGet( class_def, "links" );
5873 osrfLogError( OSRF_LOG_MARK,
5874 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
5875 info->alias = info->class_name = info->source_def = NULL;
5876 info->class_def = info->fields = info->links = NULL;
5880 osrfHash* fields = osrfHashGet( class_def, "fields" );
5882 osrfLogError( OSRF_LOG_MARK,
5883 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
5884 info->alias = info->class_name = info->source_def = NULL;
5885 info->class_def = info->fields = info->links = NULL;
5889 char* source_def = getRelation( class_def );
5893 // We got everything we need, so populate the ClassInfo
5894 if( strlen( alias ) > ALIAS_STORE_SIZE )
5895 info->alias = strdup( alias );
5897 strcpy( info->alias_store, alias );
5898 info->alias = info->alias_store;
5901 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
5902 info->class_name = strdup( class );
5904 strcpy( info->class_name_store, class );
5905 info->class_name = info->class_name_store;
5908 info->source_def = source_def;
5910 info->class_def = class_def;
5911 info->links = links;
5912 info->fields = fields;
5917 #define STATIC_FRAME_COUNT 3
5919 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
5921 /* ---------------------------------------------------------------------------
5922 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5924 ---------------------------------------------------------------------------*/
5925 static QueryFrame* allocate_frame( void ) {
5926 // In order to reduce the number of mallocs and frees, we return a static
5927 // instance of QueryFrame, if we can find one that we're not already using.
5928 // We rely on the fact that the compiler will implicitly initialize the
5929 // static instances so that in_use == 0.
5932 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5933 if( ! static_frame[ i ].in_use ) {
5934 static_frame[ i ].in_use = 1;
5935 return static_frame + i;
5939 // The static ones are all in use. Malloc one.
5941 return safe_malloc( sizeof( QueryFrame ) );
5944 /* --------------------------------------------------------------------------
5945 Free a QueryFrame, and all the memory it owns.
5946 ---------------------------------------------------------------------------*/
5947 static void free_query_frame( QueryFrame* frame ) {
5952 clear_class_info( &frame->core );
5954 // Free the join list
5956 ClassInfo* info = frame->join_list;
5959 free_class_info( info );
5963 frame->join_list = NULL;
5966 // If the frame is a static instance, just mark it as unused
5968 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5969 if( frame == static_frame + i ) {
5970 static_frame[ i ].in_use = 0;
5975 // Otherwise it must have been malloc'd, so free it
5980 /* --------------------------------------------------------------------------
5981 Search a given QueryFrame for a specified alias. If you find it, return
5982 a pointer to the corresponding ClassInfo. Otherwise return NULL.
5983 ---------------------------------------------------------------------------*/
5984 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
5985 if( ! frame || ! target ) {
5989 ClassInfo* found_class = NULL;
5991 if( !strcmp( target, frame->core.alias ) )
5992 return &(frame->core);
5994 ClassInfo* curr_class = frame->join_list;
5995 while( curr_class ) {
5996 if( strcmp( target, curr_class->alias ) )
5997 curr_class = curr_class->next;
5999 found_class = curr_class;
6008 /* --------------------------------------------------------------------------
6009 Push a new (blank) QueryFrame onto the stack.
6010 ---------------------------------------------------------------------------*/
6011 static void push_query_frame( void ) {
6012 QueryFrame* frame = allocate_frame();
6013 frame->join_list = NULL;
6014 frame->next = curr_query;
6016 // Initialize the ClassInfo for the core class
6017 ClassInfo* core = &frame->core;
6018 core->alias = core->class_name = core->source_def = NULL;
6019 core->class_def = core->fields = core->links = NULL;
6024 /* --------------------------------------------------------------------------
6025 Pop a QueryFrame off the stack and destroy it
6026 ---------------------------------------------------------------------------*/
6027 static void pop_query_frame( void ) {
6032 QueryFrame* popped = curr_query;
6033 curr_query = popped->next;
6035 free_query_frame( popped );
6038 /* --------------------------------------------------------------------------
6039 Populate the ClassInfo for the core class. Return 0 if successful, 1 if not.
6040 ---------------------------------------------------------------------------*/
6041 static int add_query_core( const char* alias, const char* class_name ) {
6044 if( ! curr_query ) {
6045 osrfLogError( OSRF_LOG_MARK,
6046 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
6048 } else if( curr_query->core.alias ) {
6049 osrfLogError( OSRF_LOG_MARK,
6050 "%s ERROR: Core class %s already populated as %s",
6051 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
6055 build_class_info( &curr_query->core, alias, class_name );
6056 if( curr_query->core.alias )
6059 osrfLogError( OSRF_LOG_MARK,
6060 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
6065 /* --------------------------------------------------------------------------
6066 Search the current QueryFrame for a specified alias. If you find it,
6067 return a pointer to the corresponding ClassInfo. Otherwise return NULL.
6068 ---------------------------------------------------------------------------*/
6069 static ClassInfo* search_alias( const char* target ) {
6070 return search_alias_in_frame( curr_query, target );
6073 /* --------------------------------------------------------------------------
6074 Search all levels of query for a specified alias, starting with the
6075 current query. If you find it, return a pointer to the corresponding
6076 ClassInfo. Otherwise return NULL.
6077 ---------------------------------------------------------------------------*/
6078 static ClassInfo* search_all_alias( const char* target ) {
6079 ClassInfo* found_class = NULL;
6080 QueryFrame* curr_frame = curr_query;
6082 while( curr_frame ) {
6083 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6086 curr_frame = curr_frame->next;
6092 /* --------------------------------------------------------------------------
6093 Add a class to the list of classes joined to the current query.
6094 ---------------------------------------------------------------------------*/
6095 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6097 if( ! classname || ! *classname ) { // sanity check
6098 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6105 const ClassInfo* conflict = search_alias( alias );
6107 osrfLogError( OSRF_LOG_MARK,
6108 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6109 MODULENAME, alias, conflict->class_name );
6113 ClassInfo* info = allocate_class_info();
6115 if( build_class_info( info, alias, classname ) ) {
6116 free_class_info( info );
6120 // Add the new ClassInfo to the join list of the current QueryFrame
6121 info->next = curr_query->join_list;
6122 curr_query->join_list = info;
6127 /* --------------------------------------------------------------------------
6128 Destroy all nodes on the query stack.
6129 ---------------------------------------------------------------------------*/
6130 static void clear_query_stack( void ) {