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 This function is a sleazy hack intended *only* for testing and
634 debugging. Any real server process should initialize the
635 database connection by calling osrfAppChildInit().
637 void set_cstore_dbi_conn( dbi_conn conn ) {
638 dbhandle = writehandle = conn;
641 void userDataFree( void* blob ) {
642 osrfHashFree( (osrfHash*)blob );
646 static void sessionDataFree( char* key, void* item ) {
647 if (!(strcmp(key,"xact_id"))) {
649 dbi_conn_query(writehandle, "ROLLBACK;");
656 int beginTransaction ( osrfMethodContext* ctx ) {
657 if(osrfMethodVerifyContext( ctx )) {
658 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
663 jsonObject* user = verifyUserPCRUD( ctx );
666 jsonObjectFree(user);
669 dbi_result result = dbi_conn_query(writehandle, "START TRANSACTION;");
671 osrfLogError(OSRF_LOG_MARK, "%s: Error starting transaction", MODULENAME );
672 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, "Error starting transaction" );
675 jsonObject* ret = jsonNewObject(ctx->session->session_id);
676 osrfAppRespondComplete( ctx, ret );
679 if (!ctx->session->userData) {
680 ctx->session->userData = osrfNewHash();
681 osrfHashSetCallback((osrfHash*)ctx->session->userData, &sessionDataFree);
684 osrfHashSet( (osrfHash*)ctx->session->userData, strdup( ctx->session->session_id ), "xact_id" );
685 ctx->session->userDataFree = &userDataFree;
691 int setSavepoint ( osrfMethodContext* ctx ) {
692 if(osrfMethodVerifyContext( ctx )) {
693 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
700 jsonObject* user = verifyUserPCRUD( ctx );
703 jsonObjectFree(user);
706 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
707 osrfAppSessionStatus(
709 OSRF_STATUS_INTERNALSERVERERROR,
710 "osrfMethodException",
712 "No active transaction -- required for savepoints"
717 const char* spName = jsonObjectGetString(jsonObjectGetIndex(ctx->params, spNamePos));
719 dbi_result result = dbi_conn_queryf(writehandle, "SAVEPOINT \"%s\";", spName);
723 "%s: Error creating savepoint %s in transaction %s",
726 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
728 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
729 "osrfMethodException", ctx->request, "Error creating savepoint" );
732 jsonObject* ret = jsonNewObject(spName);
733 osrfAppRespondComplete( ctx, ret );
739 int releaseSavepoint ( osrfMethodContext* ctx ) {
740 if(osrfMethodVerifyContext( ctx )) {
741 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
748 jsonObject* user = verifyUserPCRUD( ctx );
751 jsonObjectFree(user);
754 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
755 osrfAppSessionStatus(
757 OSRF_STATUS_INTERNALSERVERERROR,
758 "osrfMethodException",
760 "No active transaction -- required for savepoints"
765 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
767 dbi_result result = dbi_conn_queryf(writehandle, "RELEASE SAVEPOINT \"%s\";", spName);
771 "%s: Error releasing savepoint %s in transaction %s",
774 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
776 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
777 "osrfMethodException", ctx->request, "Error releasing savepoint" );
780 jsonObject* ret = jsonNewObject(spName);
781 osrfAppRespondComplete( ctx, ret );
787 int rollbackSavepoint ( osrfMethodContext* ctx ) {
788 if(osrfMethodVerifyContext( ctx )) {
789 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
796 jsonObject* user = verifyUserPCRUD( ctx );
799 jsonObjectFree(user);
802 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
803 osrfAppSessionStatus(
805 OSRF_STATUS_INTERNALSERVERERROR,
806 "osrfMethodException",
808 "No active transaction -- required for savepoints"
813 const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
815 dbi_result result = dbi_conn_queryf(writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", spName);
819 "%s: Error rolling back savepoint %s in transaction %s",
822 osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )
824 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
825 "osrfMethodException", ctx->request, "Error rolling back savepoint" );
828 jsonObject* ret = jsonNewObject(spName);
829 osrfAppRespondComplete( ctx, ret );
835 int commitTransaction ( osrfMethodContext* ctx ) {
836 if(osrfMethodVerifyContext( ctx )) {
837 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
842 jsonObject* user = verifyUserPCRUD( ctx );
845 jsonObjectFree(user);
848 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
849 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
850 "osrfMethodException", ctx->request, "No active transaction to commit" );
854 dbi_result result = dbi_conn_query(writehandle, "COMMIT;");
856 osrfLogError(OSRF_LOG_MARK, "%s: Error committing transaction", MODULENAME );
857 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
858 "osrfMethodException", ctx->request, "Error committing transaction" );
861 osrfHashRemove(ctx->session->userData, "xact_id");
862 jsonObject* ret = jsonNewObject(ctx->session->session_id);
863 osrfAppRespondComplete( ctx, ret );
869 int rollbackTransaction ( osrfMethodContext* ctx ) {
870 if(osrfMethodVerifyContext( ctx )) {
871 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
876 jsonObject* user = verifyUserPCRUD( ctx );
879 jsonObjectFree(user);
882 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
883 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
884 "osrfMethodException", ctx->request, "No active transaction to roll back" );
888 dbi_result result = dbi_conn_query(writehandle, "ROLLBACK;");
890 osrfLogError(OSRF_LOG_MARK, "%s: Error rolling back transaction", MODULENAME );
891 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
892 "osrfMethodException", ctx->request, "Error rolling back transaction" );
895 osrfHashRemove(ctx->session->userData, "xact_id");
896 jsonObject* ret = jsonNewObject(ctx->session->session_id);
897 osrfAppRespondComplete( ctx, ret );
903 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
904 if(osrfMethodVerifyContext( ctx )) {
905 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
909 osrfHash* meta = (osrfHash*) ctx->method->userData;
910 osrfHash* class_obj = osrfHashGet( meta, "class" );
914 const char* methodtype = osrfHashGet(meta, "methodtype");
915 jsonObject * obj = NULL;
917 if (!strcmp(methodtype, "create")) {
918 obj = doCreate(ctx, &err);
919 osrfAppRespondComplete( ctx, obj );
921 else if (!strcmp(methodtype, "retrieve")) {
922 obj = doRetrieve(ctx, &err);
923 osrfAppRespondComplete( ctx, obj );
925 else if (!strcmp(methodtype, "update")) {
926 obj = doUpdate(ctx, &err);
927 osrfAppRespondComplete( ctx, obj );
929 else if (!strcmp(methodtype, "delete")) {
930 obj = doDelete(ctx, &err);
931 osrfAppRespondComplete( ctx, obj );
933 else if (!strcmp(methodtype, "search")) {
935 jsonObject* where_clause;
936 jsonObject* rest_of_query;
939 where_clause = jsonObjectGetIndex( ctx->params, 1 );
940 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
942 where_clause = jsonObjectGetIndex( ctx->params, 0 );
943 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
946 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
951 unsigned long res_idx = 0;
952 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
954 if(!verifyObjectPCRUD(ctx, cur)) continue;
956 osrfAppRespond( ctx, cur );
958 osrfAppRespondComplete( ctx, NULL );
960 } else if (!strcmp(methodtype, "id_list")) {
962 jsonObject* where_clause;
963 jsonObject* rest_of_query;
965 // We use the where clause without change. But we need
966 // to massage the rest of the query, so we work with a copy
967 // of it instead of modifying the original.
969 where_clause = jsonObjectGetIndex( ctx->params, 1 );
970 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
972 where_clause = jsonObjectGetIndex( ctx->params, 0 );
973 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
976 if ( rest_of_query ) {
977 jsonObjectRemoveKey( rest_of_query, "select" );
978 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
979 jsonObjectRemoveKey( rest_of_query, "flesh" );
980 jsonObjectRemoveKey( rest_of_query, "flesh_columns" );
982 rest_of_query = jsonNewObjectType( JSON_HASH );
985 jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
987 // Build a SELECT list containing just the primary key,
988 // i.e. like { "classname":["keyname"] }
989 jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
990 jsonObjectPush( col_list_obj, // Load array with name of primary key
991 jsonNewObject( osrfHashGet( class_obj, "primarykey" ) ) );
992 jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
993 jsonObjectSetKey( select_clause, osrfHashGet( class_obj, "classname" ), col_list_obj );
995 jsonObjectSetKey( rest_of_query, "select", select_clause );
997 obj = doFieldmapperSearch( ctx, class_obj, where_clause, rest_of_query, &err );
999 jsonObjectFree( rest_of_query );
1003 unsigned long res_idx = 0;
1004 while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1006 if(!verifyObjectPCRUD(ctx, cur)) continue;
1010 oilsFMGetObject( cur, osrfHashGet( class_obj, "primarykey" ) )
1013 osrfAppRespondComplete( ctx, NULL );
1016 osrfAppRespondComplete( ctx, obj );
1019 jsonObjectFree(obj);
1024 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1027 osrfHash* meta = (osrfHash*) ctx->method->userData;
1028 osrfHash* class = osrfHashGet( meta, "class" );
1030 if (!param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1032 const char* temp_classname = param->classname;
1033 if( ! temp_classname )
1034 temp_classname = "(null)";
1036 growing_buffer* msg = buffer_init(128);
1039 "%s: %s method for type %s was passed a %s",
1041 osrfHashGet(meta, "methodtype"),
1042 osrfHashGet(class, "classname"),
1046 char* m = buffer_release(msg);
1047 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1056 ret = verifyObjectPCRUD( ctx, param );
1064 static jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1065 const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1066 jsonObject* auth_object = jsonNewObject(auth);
1067 jsonObject* user = oilsUtilsQuickReq("open-ils.auth","open-ils.auth.session.retrieve",
1069 jsonObjectFree(auth_object);
1071 if (!user->classname || strcmp(user->classname, "au")) {
1073 growing_buffer* msg = buffer_init(128);
1076 "%s: permacrud received a bad auth token: %s",
1081 char* m = buffer_release(msg);
1082 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1086 jsonObjectFree(user);
1094 static int verifyObjectPCRUD ( osrfMethodContext* ctx, const jsonObject* obj ) {
1096 dbhandle = writehandle;
1098 osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1099 osrfHash* class = osrfHashGet( method_metadata, "class" );
1100 const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1103 if ( ( *method_type == 's' || *method_type == 'i' ) ) {
1104 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1105 } else if ( *method_type == 'u' || *method_type == 'd' ) {
1106 fetch = 1; // MUST go to the db for the object for update and delete
1109 osrfHash* pcrud = osrfHashGet( osrfHashGet(class, "permacrud"), method_type );
1112 // No permacrud for this method type on this class
1114 growing_buffer* msg = buffer_init(128);
1117 "%s: %s on class %s has no permacrud IDL entry",
1119 osrfHashGet(method_metadata, "methodtype"),
1120 osrfHashGet(class, "classname")
1123 char* m = buffer_release(msg);
1124 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN, "osrfMethodException", ctx->request, m );
1131 jsonObject* user = verifyUserPCRUD( ctx );
1135 int userid = atoi( oilsFMGetString( user, "id" ) );
1136 jsonObjectFree(user);
1138 osrfStringArray* permission = osrfHashGet(pcrud, "permission");
1139 osrfStringArray* local_context = osrfHashGet(pcrud, "local_context");
1140 osrfHash* foreign_context = osrfHashGet(pcrud, "foreign_context");
1142 osrfStringArray* context_org_array = osrfNewStringArray(1);
1145 char* pkey_value = NULL;
1146 if ( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1147 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions required, fetching top of the org tree" );
1149 // check for perm at top of org tree
1150 char* org_tree_root_id = org_tree_root( ctx );
1151 if( org_tree_root_id ) {
1152 osrfStringArrayAdd( context_org_array, org_tree_root_id );
1153 osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1155 osrfStringArrayFree( context_org_array );
1160 osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, fetching context org ids" );
1161 const char* pkey = osrfHashGet(class, "primarykey");
1162 jsonObject *param = NULL;
1164 if (obj->classname) {
1165 pkey_value = oilsFMGetString( obj, pkey );
1166 if (!fetch) param = jsonObjectClone(obj);
1167 osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s", pkey_value );
1169 pkey_value = jsonObjectToSimpleString( obj );
1171 osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value of %s and retrieving from the database", pkey_value );
1175 jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1176 jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1177 jsonObjectFree(_tmp_params);
1179 param = jsonObjectExtractIndex(_list, 0);
1180 jsonObjectFree(_list);
1184 osrfLogDebug( OSRF_LOG_MARK, "Object not found in the database with primary key %s of %s", pkey, pkey_value );
1186 growing_buffer* msg = buffer_init(128);
1189 "%s: no object found with primary key %s of %s",
1195 char* m = buffer_release(msg);
1196 osrfAppSessionStatus(
1198 OSRF_STATUS_INTERNALSERVERERROR,
1199 "osrfMethodException",
1205 if (pkey_value) free(pkey_value);
1210 if (local_context->size > 0) {
1211 osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified", local_context->size);
1213 const char* lcontext = NULL;
1214 while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1215 osrfStringArrayAdd( context_org_array, oilsFMGetString( param, lcontext ) );
1218 "adding class-local field %s (value: %s) to the context org list",
1220 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1226 if (foreign_context) {
1227 unsigned long class_count = osrfHashGetCount( foreign_context );
1228 osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count);
1230 if (class_count > 0) {
1232 osrfHash* fcontext = NULL;
1233 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1234 while( (fcontext = osrfHashIteratorNext( class_itr ) ) ) {
1235 const char* class_name = osrfHashIteratorKey( class_itr );
1236 osrfHash* fcontext = osrfHashGet(foreign_context, class_name);
1240 "%d foreign context fields(s) specified for class %s",
1241 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1245 char* foreign_pkey = osrfHashGet(fcontext, "field");
1246 char* foreign_pkey_value = oilsFMGetString(param, osrfHashGet(fcontext, "fkey"));
1248 jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1250 jsonObject* _list = doFieldmapperSearch(
1251 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1253 jsonObject* _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1254 jsonObjectFree(_tmp_params);
1255 jsonObjectFree(_list);
1257 osrfStringArray* jump_list = osrfHashGet(fcontext, "jump");
1259 if (_fparam && jump_list) {
1260 const char* flink = NULL;
1262 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1263 free(foreign_pkey_value);
1265 osrfHash* foreign_link_hash = oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1267 foreign_pkey_value = oilsFMGetString(_fparam, flink);
1268 foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1270 _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1272 _list = doFieldmapperSearch(
1274 osrfHashGet( oilsIDL(), osrfHashGet( foreign_link_hash, "class" ) ),
1280 _fparam = jsonObjectClone(jsonObjectGetIndex(_list, 0));
1281 jsonObjectFree(_tmp_params);
1282 jsonObjectFree(_list);
1288 growing_buffer* msg = buffer_init(128);
1291 "%s: no object found with primary key %s of %s",
1297 char* m = buffer_release(msg);
1298 osrfAppSessionStatus(
1300 OSRF_STATUS_INTERNALSERVERERROR,
1301 "osrfMethodException",
1307 osrfHashIteratorFree(class_itr);
1308 free(foreign_pkey_value);
1309 jsonObjectFree(param);
1314 free(foreign_pkey_value);
1317 const char* foreign_field = NULL;
1318 while ( (foreign_field = osrfStringArrayGetString(osrfHashGet(fcontext,"context"), j++)) ) {
1319 osrfStringArrayAdd( context_org_array, oilsFMGetString( _fparam, foreign_field ) );
1322 "adding foreign class %s field %s (value: %s) to the context org list",
1325 osrfStringArrayGetString(context_org_array, context_org_array->size - 1)
1329 jsonObjectFree(_fparam);
1332 osrfHashIteratorFree( class_itr );
1336 jsonObjectFree(param);
1339 const char* context_org = NULL;
1340 const char* perm = NULL;
1343 if (permission->size == 0) {
1344 osrfLogDebug( OSRF_LOG_MARK, "No permission specified for this action, passing through" );
1349 while ( (perm = osrfStringArrayGetString(permission, i++)) ) {
1351 while ( (context_org = osrfStringArrayGetString(context_org_array, j++)) ) {
1357 "Checking object permission [%s] for user %d on object %s (class %s) at org %d",
1361 osrfHashGet(class, "classname"),
1365 result = dbi_conn_queryf(
1367 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
1370 osrfHashGet(class, "classname"),
1378 "Received a result for object permission [%s] for user %d on object %s (class %s) at org %d",
1382 osrfHashGet(class, "classname"),
1386 if (dbi_result_first_row(result)) {
1387 jsonObject* return_val = oilsMakeJSONFromResult( result );
1388 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1392 "Status of object permission [%s] for user %d on object %s (class %s) at org %d is %s",
1396 osrfHashGet(class, "classname"),
1401 if ( *has_perm == 't' ) OK = 1;
1402 jsonObjectFree(return_val);
1405 dbi_result_free(result);
1410 osrfLogDebug( OSRF_LOG_MARK, "Checking non-object permission [%s] for user %d at org %d", perm, userid, atoi(context_org) );
1411 result = dbi_conn_queryf(
1413 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
1420 osrfLogDebug( OSRF_LOG_MARK, "Received a result for permission [%s] for user %d at org %d",
1421 perm, userid, atoi(context_org) );
1422 if ( dbi_result_first_row(result) ) {
1423 jsonObject* return_val = oilsMakeJSONFromResult( result );
1424 const char* has_perm = jsonObjectGetString( jsonObjectGetKeyConst(return_val, "has_perm") );
1425 osrfLogDebug( OSRF_LOG_MARK, "Status of permission [%s] for user %d at org %d is [%s]",
1426 perm, userid, atoi(context_org), has_perm );
1427 if ( *has_perm == 't' ) OK = 1;
1428 jsonObjectFree(return_val);
1431 dbi_result_free(result);
1439 if (pkey_value) free(pkey_value);
1440 osrfStringArrayFree(context_org_array);
1446 * Look up the root of the org_unit tree. If you find it, return
1447 * a string containing the id, which the caller is responsible for freeing.
1448 * Otherwise return NULL.
1450 static char* org_tree_root( osrfMethodContext* ctx ) {
1452 static char cached_root_id[ 32 ] = ""; // extravagantly large buffer
1453 static time_t last_lookup_time = 0;
1454 time_t current_time = time( NULL );
1456 if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
1457 // We successfully looked this up less than an hour ago.
1458 // It's not likely to have changed since then.
1459 return strdup( cached_root_id );
1461 last_lookup_time = current_time;
1464 jsonObject* where_clause = single_hash( "parent_ou", NULL );
1465 jsonObject* result = doFieldmapperSearch(
1466 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
1467 jsonObjectFree( where_clause );
1469 jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
1472 jsonObjectFree( result );
1474 growing_buffer* msg = buffer_init(128);
1475 OSRF_BUFFER_ADD( msg, MODULENAME );
1476 OSRF_BUFFER_ADD( msg,
1477 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
1479 char* m = buffer_release(msg);
1480 osrfAppSessionStatus( ctx->session,
1481 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
1484 cached_root_id[ 0 ] = '\0';
1488 char* root_org_unit_id = oilsFMGetString( tree_top, "id" );
1489 osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
1491 jsonObjectFree( result );
1493 strcpy( cached_root_id, root_org_unit_id );
1494 return root_org_unit_id;
1498 Utility function: create a JSON_HASH with a single key/value pair.
1499 This function is equivalent to:
1501 jsonParseFmt( "{\"%s\":\"%s\"}", key, value )
1503 or, if value is NULL:
1505 jsonParseFmt( "{\"%s\":null}", key )
1507 ...but faster because it doesn't create and parse a JSON string.
1509 static jsonObject* single_hash( const char* key, const char* value ) {
1511 if( ! key ) key = "";
1513 jsonObject* hash = jsonNewObjectType( JSON_HASH );
1514 jsonObjectSetKey( hash, key, jsonNewObject( value ) );
1520 static jsonObject* doCreate(osrfMethodContext* ctx, int* err ) {
1522 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1524 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
1525 jsonObject* options = jsonObjectGetIndex( ctx->params, 2 );
1527 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
1528 jsonObject* options = jsonObjectGetIndex( ctx->params, 1 );
1531 if (!verifyObjectClass(ctx, target)) {
1536 osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
1538 char* trans_id = NULL;
1539 if( ctx->session && ctx->session->userData )
1540 trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
1543 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
1545 osrfAppSessionStatus(
1547 OSRF_STATUS_BADREQUEST,
1548 "osrfMethodException",
1550 "No active transaction -- required for CREATE"
1556 // The following test is harmless but redundant. If a class is
1557 // readonly, we don't register a create method for it.
1558 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
1559 osrfAppSessionStatus(
1561 OSRF_STATUS_BADREQUEST,
1562 "osrfMethodException",
1564 "Cannot INSERT readonly class"
1570 // Set the last_xact_id
1571 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
1573 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d", trans_id, target->classname, index);
1574 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
1577 osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
1579 dbhandle = writehandle;
1581 osrfHash* fields = osrfHashGet(meta, "fields");
1582 char* pkey = osrfHashGet(meta, "primarykey");
1583 char* seq = osrfHashGet(meta, "sequence");
1585 growing_buffer* table_buf = buffer_init(128);
1586 growing_buffer* col_buf = buffer_init(128);
1587 growing_buffer* val_buf = buffer_init(128);
1589 OSRF_BUFFER_ADD(table_buf, "INSERT INTO ");
1590 OSRF_BUFFER_ADD(table_buf, osrfHashGet(meta, "tablename"));
1591 OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
1592 buffer_add(val_buf,"VALUES (");
1596 osrfHash* field = NULL;
1597 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
1598 while( (field = osrfHashIteratorNext( field_itr ) ) ) {
1600 const char* field_name = osrfHashIteratorKey( field_itr );
1602 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
1605 const jsonObject* field_object = oilsFMGetObject( target, field_name );
1608 if (field_object && field_object->classname) {
1609 value = oilsFMGetString(
1611 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
1613 } else if( field_object && JSON_BOOL == field_object->type ) {
1614 if( jsonBoolIsTrue( field_object ) )
1615 value = strdup( "t" );
1617 value = strdup( "f" );
1619 value = jsonObjectToSimpleString( field_object );
1625 OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
1626 OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
1629 buffer_add(col_buf, field_name);
1631 if (!field_object || field_object->type == JSON_NULL) {
1632 buffer_add( val_buf, "DEFAULT" );
1634 } else if ( !strcmp(get_primitive( field ), "number") ) {
1635 const char* numtype = get_datatype( field );
1636 if ( !strcmp( numtype, "INT8") ) {
1637 buffer_fadd( val_buf, "%lld", atoll(value) );
1639 } else if ( !strcmp( numtype, "INT") ) {
1640 buffer_fadd( val_buf, "%d", atoi(value) );
1642 } else if ( !strcmp( numtype, "NUMERIC") ) {
1643 buffer_fadd( val_buf, "%f", atof(value) );
1646 if ( dbi_conn_quote_string(writehandle, &value) ) {
1647 OSRF_BUFFER_ADD( val_buf, value );
1650 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
1651 osrfAppSessionStatus(
1653 OSRF_STATUS_INTERNALSERVERERROR,
1654 "osrfMethodException",
1656 "Error quoting string -- please see the error log for more details"
1659 buffer_free(table_buf);
1660 buffer_free(col_buf);
1661 buffer_free(val_buf);
1671 osrfHashIteratorFree( field_itr );
1673 OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
1674 OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
1676 char* table_str = buffer_release(table_buf);
1677 char* col_str = buffer_release(col_buf);
1678 char* val_str = buffer_release(val_buf);
1679 growing_buffer* sql = buffer_init(128);
1680 buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
1685 char* query = buffer_release(sql);
1687 osrfLogDebug(OSRF_LOG_MARK, "%s: Insert SQL [%s]", MODULENAME, query);
1690 dbi_result result = dbi_conn_query(writehandle, query);
1692 jsonObject* obj = NULL;
1695 obj = jsonNewObject(NULL);
1698 "%s ERROR inserting %s object using query [%s]",
1700 osrfHashGet(meta, "fieldmapper"),
1703 osrfAppSessionStatus(
1705 OSRF_STATUS_INTERNALSERVERERROR,
1706 "osrfMethodException",
1708 "INSERT error -- please see the error log for more details"
1713 char* id = oilsFMGetString(target, pkey);
1715 unsigned long long new_id = dbi_conn_sequence_last(writehandle, seq);
1716 growing_buffer* _id = buffer_init(10);
1717 buffer_fadd(_id, "%lld", new_id);
1718 id = buffer_release(_id);
1721 // Find quietness specification, if present
1722 const char* quiet_str = NULL;
1724 const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
1726 quiet_str = jsonObjectGetString( quiet_obj );
1729 if( str_is_true( quiet_str ) ) { // if quietness is specified
1730 obj = jsonNewObject(id);
1734 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1735 jsonObjectSetKey( where_clause, pkey, jsonNewObject(id) );
1737 jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, err );
1739 jsonObjectFree( where_clause );
1744 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
1747 jsonObjectFree( list );
1760 * Fetch one row from a specified table, using a specified value
1761 * for the primary key
1763 static jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) {
1773 osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
1775 const jsonObject* id_obj = jsonObjectGetIndex(ctx->params, id_pos); // key value
1779 "%s retrieving %s object with primary key value of %s",
1781 osrfHashGet( class_def, "fieldmapper" ),
1782 jsonObjectGetString( id_obj )
1785 // Build a WHERE clause based on the key value
1786 jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
1789 osrfHashGet( class_def, "primarykey" ),
1790 jsonObjectClone( id_obj )
1793 jsonObject* rest_of_query = jsonObjectGetIndex(ctx->params, order_pos);
1795 jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, err );
1797 jsonObjectFree( where_clause );
1801 jsonObject* obj = jsonObjectExtractIndex( list, 0 );
1802 jsonObjectFree( list );
1805 if(!verifyObjectPCRUD(ctx, obj)) {
1806 jsonObjectFree(obj);
1809 growing_buffer* msg = buffer_init(128);
1810 OSRF_BUFFER_ADD( msg, MODULENAME );
1811 OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
1813 char* m = buffer_release(msg);
1814 osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException", ctx->request, m );
1825 static char* jsonNumberToDBString ( osrfHash* field, const jsonObject* value ) {
1826 growing_buffer* val_buf = buffer_init(32);
1827 const char* numtype = get_datatype( field );
1829 if ( !strncmp( numtype, "INT", 3 ) ) {
1830 if (value->type == JSON_NUMBER)
1831 //buffer_fadd( val_buf, "%ld", (long)jsonObjectGetNumber(value) );
1832 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1834 //const char* val_str = jsonObjectGetString( value );
1835 //buffer_fadd( val_buf, "%ld", atol(val_str) );
1836 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1839 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
1840 if (value->type == JSON_NUMBER)
1841 //buffer_fadd( val_buf, "%f", jsonObjectGetNumber(value) );
1842 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1844 //const char* val_str = jsonObjectGetString( value );
1845 //buffer_fadd( val_buf, "%f", atof(val_str) );
1846 buffer_fadd( val_buf, jsonObjectGetString( value ) );
1850 // Presumably this was really intended ot be a string, so quote it
1851 char* str = jsonObjectToSimpleString( value );
1852 if ( dbi_conn_quote_string(dbhandle, &str) ) {
1853 OSRF_BUFFER_ADD( val_buf, str );
1856 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, str);
1858 buffer_free(val_buf);
1863 return buffer_release(val_buf);
1866 static char* searchINPredicate (const char* class_alias, osrfHash* field,
1867 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
1868 growing_buffer* sql_buf = buffer_init(32);
1874 osrfHashGet(field, "name")
1878 buffer_add(sql_buf, "IN (");
1879 } else if (!(strcasecmp(op,"not in"))) {
1880 buffer_add(sql_buf, "NOT IN (");
1882 buffer_add(sql_buf, "IN (");
1885 if (node->type == JSON_HASH) {
1886 // subquery predicate
1887 char* subpred = buildQuery( ctx, node, SUBSELECT );
1889 buffer_free( sql_buf );
1893 buffer_add(sql_buf, subpred);
1896 } else if (node->type == JSON_ARRAY) {
1897 // literal value list
1898 int in_item_index = 0;
1899 int in_item_first = 1;
1900 const jsonObject* in_item;
1901 while ( (in_item = jsonObjectGetIndex(node, in_item_index++)) ) {
1906 buffer_add(sql_buf, ", ");
1909 if ( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
1910 osrfLogError(OSRF_LOG_MARK, "%s: Expected string or number within IN list; found %s",
1911 MODULENAME, json_type( in_item->type ) );
1912 buffer_free(sql_buf);
1916 // Append the literal value -- quoted if not a number
1917 if ( JSON_NUMBER == in_item->type ) {
1918 char* val = jsonNumberToDBString( field, in_item );
1919 OSRF_BUFFER_ADD( sql_buf, val );
1922 } else if ( !strcmp( get_primitive( field ), "number") ) {
1923 char* val = jsonNumberToDBString( field, in_item );
1924 OSRF_BUFFER_ADD( sql_buf, val );
1928 char* key_string = jsonObjectToSimpleString(in_item);
1929 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
1930 OSRF_BUFFER_ADD( sql_buf, key_string );
1933 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
1935 buffer_free(sql_buf);
1941 if( in_item_first ) {
1942 osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", MODULENAME );
1943 buffer_free( sql_buf );
1947 osrfLogError(OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
1948 MODULENAME, json_type( node->type ) );
1949 buffer_free(sql_buf);
1953 OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
1955 return buffer_release(sql_buf);
1958 // Receive a JSON_ARRAY representing a function call. The first
1959 // entry in the array is the function name. The rest are parameters.
1960 static char* searchValueTransform( const jsonObject* array ) {
1962 if( array->size < 1 ) {
1963 osrfLogError(OSRF_LOG_MARK, "%s: Empty array for value transform", MODULENAME);
1967 // Get the function name
1968 jsonObject* func_item = jsonObjectGetIndex( array, 0 );
1969 if( func_item->type != JSON_STRING ) {
1970 osrfLogError(OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
1971 MODULENAME, json_type( func_item->type ) );
1975 growing_buffer* sql_buf = buffer_init(32);
1977 OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
1978 OSRF_BUFFER_ADD( sql_buf, "( " );
1980 // Get the parameters
1981 int func_item_index = 1; // We already grabbed the zeroth entry
1982 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
1984 // Add a separator comma, if we need one
1985 if( func_item_index > 2 )
1986 buffer_add( sql_buf, ", " );
1988 // Add the current parameter
1989 if (func_item->type == JSON_NULL) {
1990 buffer_add( sql_buf, "NULL" );
1992 char* val = jsonObjectToSimpleString(func_item);
1993 if ( dbi_conn_quote_string(dbhandle, &val) ) {
1994 OSRF_BUFFER_ADD( sql_buf, val );
1997 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
1998 buffer_free(sql_buf);
2005 buffer_add( sql_buf, " )" );
2007 return buffer_release(sql_buf);
2010 static char* searchFunctionPredicate (const char* class_alias, osrfHash* field,
2011 const jsonObject* node, const char* op) {
2013 if( ! is_good_operator( op ) ) {
2014 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2018 char* val = searchValueTransform(node);
2022 growing_buffer* sql_buf = buffer_init(32);
2027 osrfHashGet(field, "name"),
2034 return buffer_release(sql_buf);
2037 // class_alias is a class name or other table alias
2038 // field is a field definition as stored in the IDL
2039 // node comes from the method parameter, and may represent an entry in the SELECT list
2040 static char* searchFieldTransform (const char* class_alias, osrfHash* field, const jsonObject* node) {
2041 growing_buffer* sql_buf = buffer_init(32);
2043 const char* field_transform = jsonObjectGetString( jsonObjectGetKeyConst( node, "transform" ) );
2044 const char* transform_subcolumn = jsonObjectGetString( jsonObjectGetKeyConst( node, "result_field" ) );
2046 if(transform_subcolumn) {
2047 if( ! is_identifier( transform_subcolumn ) ) {
2048 osrfLogError( OSRF_LOG_MARK, "%s: Invalid subfield name: \"%s\"\n",
2049 MODULENAME, transform_subcolumn );
2050 buffer_free( sql_buf );
2053 OSRF_BUFFER_ADD_CHAR( sql_buf, '(' ); // enclose transform in parentheses
2056 if (field_transform) {
2058 if( ! is_identifier( field_transform ) ) {
2059 osrfLogError( OSRF_LOG_MARK, "%s: Expected function name, found \"%s\"\n",
2060 MODULENAME, field_transform );
2061 buffer_free( sql_buf );
2065 buffer_fadd( sql_buf, "%s(\"%s\".%s", field_transform, class_alias, osrfHashGet(field, "name"));
2066 const jsonObject* array = jsonObjectGetKeyConst( node, "params" );
2069 if( array->type != JSON_ARRAY ) {
2070 osrfLogError( OSRF_LOG_MARK,
2071 "%s: Expected JSON_ARRAY for function params; found %s",
2072 MODULENAME, json_type( array->type ) );
2073 buffer_free( sql_buf );
2076 int func_item_index = 0;
2077 jsonObject* func_item;
2078 while ( (func_item = jsonObjectGetIndex(array, func_item_index++)) ) {
2080 char* val = jsonObjectToSimpleString(func_item);
2083 buffer_add( sql_buf, ",NULL" );
2084 } else if ( dbi_conn_quote_string(dbhandle, &val) ) {
2085 OSRF_BUFFER_ADD_CHAR( sql_buf, ',' );
2086 OSRF_BUFFER_ADD( sql_buf, val );
2088 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val);
2090 buffer_free(sql_buf);
2097 buffer_add( sql_buf, " )" );
2100 buffer_fadd( sql_buf, "\"%s\".%s", class_alias, osrfHashGet(field, "name"));
2103 if (transform_subcolumn)
2104 buffer_fadd( sql_buf, ").\"%s\"", transform_subcolumn );
2106 return buffer_release(sql_buf);
2109 static char* searchFieldTransformPredicate( const ClassInfo* class_info, osrfHash* field,
2110 const jsonObject* node, const char* op ) {
2112 if( ! is_good_operator( op ) ) {
2113 osrfLogError(OSRF_LOG_MARK, "%s: Error: Invalid operator %s", MODULENAME, op);
2117 char* field_transform = searchFieldTransform( class_info->alias, field, node );
2118 if( ! field_transform )
2121 int extra_parens = 0; // boolean
2123 const jsonObject* value_obj = jsonObjectGetKeyConst( node, "value" );
2124 if ( ! value_obj ) {
2125 value = searchWHERE( node, class_info, AND_OP_JOIN, NULL );
2127 osrfLogError(OSRF_LOG_MARK, "%s: Error building condition for field transform", MODULENAME);
2128 free(field_transform);
2132 } else if ( value_obj->type == JSON_ARRAY ) {
2133 value = searchValueTransform( value_obj );
2135 osrfLogError(OSRF_LOG_MARK, "%s: Error building value transform for field transform", MODULENAME);
2136 free( field_transform );
2139 } else if ( value_obj->type == JSON_HASH ) {
2140 value = searchWHERE( value_obj, class_info, AND_OP_JOIN, NULL );
2142 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform", MODULENAME);
2143 free(field_transform);
2147 } else if ( value_obj->type == JSON_NUMBER ) {
2148 value = jsonNumberToDBString( field, value_obj );
2149 } else if ( value_obj->type == JSON_NULL ) {
2150 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: null value", MODULENAME);
2151 free(field_transform);
2153 } else if ( value_obj->type == JSON_BOOL ) {
2154 osrfLogError(OSRF_LOG_MARK, "%s: Error building predicate for field transform: boolean value", MODULENAME);
2155 free(field_transform);
2158 if ( !strcmp( get_primitive( field ), "number") ) {
2159 value = jsonNumberToDBString( field, value_obj );
2161 value = jsonObjectToSimpleString( value_obj );
2162 if ( !dbi_conn_quote_string(dbhandle, &value) ) {
2163 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, value);
2165 free(field_transform);
2171 const char* left_parens = "";
2172 const char* right_parens = "";
2174 if( extra_parens ) {
2179 growing_buffer* sql_buf = buffer_init(32);
2183 "%s%s %s %s %s %s%s",
2194 free(field_transform);
2196 return buffer_release(sql_buf);
2199 static char* searchSimplePredicate (const char* op, const char* class_alias,
2200 osrfHash* field, const jsonObject* node) {
2202 if( ! is_good_operator( op ) ) {
2203 osrfLogError( OSRF_LOG_MARK, "%s: Invalid operator [%s]", MODULENAME, op );
2209 // Get the value to which we are comparing the specified column
2210 if (node->type != JSON_NULL) {
2211 if ( node->type == JSON_NUMBER ) {
2212 val = jsonNumberToDBString( field, node );
2213 } else if ( !strcmp( get_primitive( field ), "number" ) ) {
2214 val = jsonNumberToDBString( field, node );
2216 val = jsonObjectToSimpleString(node);
2221 if( JSON_NUMBER != node->type && strcmp( get_primitive( field ), "number") ) {
2222 // Value is not numeric; enclose it in quotes
2223 if ( !dbi_conn_quote_string( dbhandle, &val ) ) {
2224 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, val );
2230 // Compare to a null value
2231 val = strdup( "NULL" );
2232 if (strcmp( op, "=" ))
2238 growing_buffer* sql_buf = buffer_init(32);
2239 buffer_fadd( sql_buf, "\"%s\".%s %s %s", class_alias, osrfHashGet(field, "name"), op, val );
2240 char* pred = buffer_release( sql_buf );
2247 static char* searchBETWEENPredicate (const char* class_alias,
2248 osrfHash* field, const jsonObject* node) {
2250 const jsonObject* x_node = jsonObjectGetIndex( node, 0 );
2251 const jsonObject* y_node = jsonObjectGetIndex( node, 1 );
2253 if( NULL == y_node ) {
2254 osrfLogError( OSRF_LOG_MARK, "%s: Not enough operands for BETWEEN operator", MODULENAME );
2257 else if( NULL != jsonObjectGetIndex( node, 2 ) ) {
2258 osrfLogError( OSRF_LOG_MARK, "%s: Too many operands for BETWEEN operator", MODULENAME );
2265 if ( !strcmp( get_primitive( field ), "number") ) {
2266 x_string = jsonNumberToDBString(field, x_node);
2267 y_string = jsonNumberToDBString(field, y_node);
2270 x_string = jsonObjectToSimpleString(x_node);
2271 y_string = jsonObjectToSimpleString(y_node);
2272 if ( !(dbi_conn_quote_string(dbhandle, &x_string) && dbi_conn_quote_string(dbhandle, &y_string)) ) {
2273 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting key strings [%s] and [%s]",
2274 MODULENAME, x_string, y_string);
2281 growing_buffer* sql_buf = buffer_init(32);
2282 buffer_fadd( sql_buf, "\"%s\".%s BETWEEN %s AND %s",
2283 class_alias, osrfHashGet(field, "name"), x_string, y_string );
2287 return buffer_release(sql_buf);
2290 static char* searchPredicate ( const ClassInfo* class_info, osrfHash* field,
2291 jsonObject* node, osrfMethodContext* ctx ) {
2294 if (node->type == JSON_ARRAY) { // equality IN search
2295 pred = searchINPredicate( class_info->alias, field, node, NULL, ctx );
2296 } else if (node->type == JSON_HASH) { // other search
2297 jsonIterator* pred_itr = jsonNewIterator( node );
2298 if( !jsonIteratorHasNext( pred_itr ) ) {
2299 osrfLogError( OSRF_LOG_MARK, "%s: Empty predicate for field \"%s\"",
2300 MODULENAME, osrfHashGet(field, "name") );
2302 jsonObject* pred_node = jsonIteratorNext( pred_itr );
2304 // Verify that there are no additional predicates
2305 if( jsonIteratorHasNext( pred_itr ) ) {
2306 osrfLogError( OSRF_LOG_MARK, "%s: Multiple predicates for field \"%s\"",
2307 MODULENAME, osrfHashGet(field, "name") );
2308 } else if ( !(strcasecmp( pred_itr->key,"between" )) )
2309 pred = searchBETWEENPredicate( class_info->alias, field, pred_node );
2310 else if ( !(strcasecmp( pred_itr->key,"in" )) || !(strcasecmp( pred_itr->key,"not in" )) )
2311 pred = searchINPredicate( class_info->alias, field, pred_node, pred_itr->key, ctx );
2312 else if ( pred_node->type == JSON_ARRAY )
2313 pred = searchFunctionPredicate( class_info->alias, field, pred_node, pred_itr->key );
2314 else if ( pred_node->type == JSON_HASH )
2315 pred = searchFieldTransformPredicate( class_info, field, pred_node, pred_itr->key );
2317 pred = searchSimplePredicate( pred_itr->key, class_info->alias, field, pred_node );
2319 jsonIteratorFree(pred_itr);
2321 } else if (node->type == JSON_NULL) { // IS NULL search
2322 growing_buffer* _p = buffer_init(64);
2325 "\"%s\".%s IS NULL",
2326 class_info->class_name,
2327 osrfHashGet(field, "name")
2329 pred = buffer_release(_p);
2330 } else { // equality search
2331 pred = searchSimplePredicate( "=", class_info->alias, field, node );
2350 field : call_number,
2366 static char* searchJOIN ( const jsonObject* join_hash, const ClassInfo* left_info ) {
2368 const jsonObject* working_hash;
2369 jsonObject* freeable_hash = NULL;
2371 if (join_hash->type == JSON_HASH) {
2372 working_hash = join_hash;
2373 } else if (join_hash->type == JSON_STRING) {
2374 // turn it into a JSON_HASH by creating a wrapper
2375 // around a copy of the original
2376 const char* _tmp = jsonObjectGetString( join_hash );
2377 freeable_hash = jsonNewObjectType(JSON_HASH);
2378 jsonObjectSetKey(freeable_hash, _tmp, NULL);
2379 working_hash = freeable_hash;
2383 "%s: JOIN failed; expected JSON object type not found",
2389 growing_buffer* join_buf = buffer_init(128);
2390 const char* leftclass = left_info->class_name;
2392 jsonObject* snode = NULL;
2393 jsonIterator* search_itr = jsonNewIterator( working_hash );
2395 while ( (snode = jsonIteratorNext( search_itr )) ) {
2396 const char* right_alias = search_itr->key;
2398 jsonObjectGetString( jsonObjectGetKeyConst( snode, "class" ) );
2400 class = right_alias;
2402 const ClassInfo* right_info = add_joined_class( right_alias, class );
2406 "%s: JOIN failed. Class \"%s\" not resolved in IDL",
2410 jsonIteratorFree( search_itr );
2411 buffer_free( join_buf );
2413 jsonObjectFree( freeable_hash );
2416 osrfHash* links = right_info->links;
2417 const char* table = right_info->source_def;
2419 const char* fkey = jsonObjectGetString( jsonObjectGetKeyConst( snode, "fkey" ) );
2420 const char* field = jsonObjectGetString( jsonObjectGetKeyConst( snode, "field" ) );
2422 if (field && !fkey) {
2423 // Look up the corresponding join column in the IDL.
2424 // The link must be defined in the child table,
2425 // and point to the right parent table.
2426 osrfHash* idl_link = (osrfHash*) osrfHashGet( links, field );
2427 const char* reltype = NULL;
2428 const char* other_class = NULL;
2429 reltype = osrfHashGet( idl_link, "reltype" );
2430 if( reltype && strcmp( reltype, "has_many" ) )
2431 other_class = osrfHashGet( idl_link, "class" );
2432 if( other_class && !strcmp( other_class, leftclass ) )
2433 fkey = osrfHashGet( idl_link, "key" );
2437 "%s: JOIN failed. No link defined from %s.%s to %s",
2443 buffer_free(join_buf);
2445 jsonObjectFree(freeable_hash);
2446 jsonIteratorFree(search_itr);
2450 } else if (!field && fkey) {
2451 // Look up the corresponding join column in the IDL.
2452 // The link must be defined in the child table,
2453 // and point to the right parent table.
2454 osrfHash* left_links = left_info->links;
2455 osrfHash* idl_link = (osrfHash*) osrfHashGet( left_links, fkey );
2456 const char* reltype = NULL;
2457 const char* other_class = NULL;
2458 reltype = osrfHashGet( idl_link, "reltype" );
2459 if( reltype && strcmp( reltype, "has_many" ) )
2460 other_class = osrfHashGet( idl_link, "class" );
2461 if( other_class && !strcmp( other_class, class ) )
2462 field = osrfHashGet( idl_link, "key" );
2466 "%s: JOIN failed. No link defined from %s.%s to %s",
2472 buffer_free(join_buf);
2474 jsonObjectFree(freeable_hash);
2475 jsonIteratorFree(search_itr);
2479 } else if (!field && !fkey) {
2480 osrfHash* left_links = left_info->links;
2482 // For each link defined for the left class:
2483 // see if the link references the joined class
2484 osrfHashIterator* itr = osrfNewHashIterator( left_links );
2485 osrfHash* curr_link = NULL;
2486 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2487 const char* other_class = osrfHashGet( curr_link, "class" );
2488 if( other_class && !strcmp( other_class, class ) ) {
2490 // In the IDL, the parent class doesn't know then names of the child
2491 // columns that are pointing to it, so don't use that end of the link
2492 const char* reltype = osrfHashGet( curr_link, "reltype" );
2493 if( reltype && strcmp( reltype, "has_many" ) ) {
2494 // Found a link between the classes
2495 fkey = osrfHashIteratorKey( itr );
2496 field = osrfHashGet( curr_link, "key" );
2501 osrfHashIteratorFree( itr );
2503 if (!field || !fkey) {
2504 // Do another such search, with the classes reversed
2506 // For each link defined for the joined class:
2507 // see if the link references the left class
2508 osrfHashIterator* itr = osrfNewHashIterator( links );
2509 osrfHash* curr_link = NULL;
2510 while( (curr_link = osrfHashIteratorNext( itr ) ) ) {
2511 const char* other_class = osrfHashGet( curr_link, "class" );
2512 if( other_class && !strcmp( other_class, leftclass ) ) {
2514 // In the IDL, the parent class doesn't know then names of the child
2515 // columns that are pointing to it, so don't use that end of the link
2516 const char* reltype = osrfHashGet( curr_link, "reltype" );
2517 if( reltype && strcmp( reltype, "has_many" ) ) {
2518 // Found a link between the classes
2519 field = osrfHashIteratorKey( itr );
2520 fkey = osrfHashGet( curr_link, "key" );
2525 osrfHashIteratorFree( itr );
2528 if (!field || !fkey) {
2531 "%s: JOIN failed. No link defined between %s and %s",
2536 buffer_free(join_buf);
2538 jsonObjectFree(freeable_hash);
2539 jsonIteratorFree(search_itr);
2545 const char* type = jsonObjectGetString( jsonObjectGetKeyConst( snode, "type" ) );
2547 if ( !strcasecmp(type,"left") ) {
2548 buffer_add(join_buf, " LEFT JOIN");
2549 } else if ( !strcasecmp(type,"right") ) {
2550 buffer_add(join_buf, " RIGHT JOIN");
2551 } else if ( !strcasecmp(type,"full") ) {
2552 buffer_add(join_buf, " FULL JOIN");
2554 buffer_add(join_buf, " INNER JOIN");
2557 buffer_add(join_buf, " INNER JOIN");
2560 buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s",
2561 table, right_alias, right_alias, field, left_info->alias, fkey);
2563 // Add any other join conditions as specified by "filter"
2564 const jsonObject* filter = jsonObjectGetKeyConst( snode, "filter" );
2566 const char* filter_op = jsonObjectGetString( jsonObjectGetKeyConst( snode, "filter_op" ) );
2567 if ( filter_op && !strcasecmp("or",filter_op) ) {
2568 buffer_add( join_buf, " OR " );
2570 buffer_add( join_buf, " AND " );
2573 char* jpred = searchWHERE( filter, right_info, AND_OP_JOIN, NULL );
2575 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2576 OSRF_BUFFER_ADD( join_buf, jpred );
2581 "%s: JOIN failed. Invalid conditional expression.",
2584 jsonIteratorFree( search_itr );
2585 buffer_free( join_buf );
2587 jsonObjectFree( freeable_hash );
2592 buffer_add(join_buf, " ) ");
2594 // Recursively add a nested join, if one is present
2595 const jsonObject* join_filter = jsonObjectGetKeyConst( snode, "join" );
2597 char* jpred = searchJOIN( join_filter, right_info );
2599 OSRF_BUFFER_ADD_CHAR( join_buf, ' ' );
2600 OSRF_BUFFER_ADD( join_buf, jpred );
2603 osrfLogError( OSRF_LOG_MARK, "%s: Invalid nested join.", MODULENAME );
2604 jsonIteratorFree( search_itr );
2605 buffer_free( join_buf );
2607 jsonObjectFree( freeable_hash );
2614 jsonObjectFree(freeable_hash);
2615 jsonIteratorFree(search_itr);
2617 return buffer_release(join_buf);
2622 { +class : { -or|-and : { field : { op : value }, ... } ... }, ... }
2623 { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }
2624 [ { +class : { -or|-and : [ { field : { op : value }, ... }, ...] ... }, ... }, ... ]
2626 Generate code to express a set of conditions, as for a WHERE clause. Parameters:
2628 search_hash is the JSON expression of the conditions.
2629 meta is the class definition from the IDL, for the relevant table.
2630 opjoin_type indicates whether multiple conditions, if present, should be
2631 connected by AND or OR.
2632 osrfMethodContext is loaded with all sorts of stuff, but all we do with it here is
2633 to pass it to other functions -- and all they do with it is to use the session
2634 and request members to send error messages back to the client.
2638 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo* class_info,
2639 int opjoin_type, osrfMethodContext* ctx ) {
2643 "%s: Entering searchWHERE; search_hash addr = %p, meta addr = %p, opjoin_type = %d, ctx addr = %p",
2646 class_info->class_def,
2651 growing_buffer* sql_buf = buffer_init(128);
2653 jsonObject* node = NULL;
2656 if ( search_hash->type == JSON_ARRAY ) {
2657 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_ARRAY", MODULENAME);
2658 if( 0 == search_hash->size ) {
2661 "%s: Invalid predicate structure: empty JSON array",
2664 buffer_free( sql_buf );
2668 unsigned long i = 0;
2669 while((node = jsonObjectGetIndex( search_hash, i++ ) )) {
2673 if (opjoin_type == OR_OP_JOIN)
2674 buffer_add(sql_buf, " OR ");
2676 buffer_add(sql_buf, " AND ");
2679 char* subpred = searchWHERE( node, class_info, opjoin_type, ctx );
2681 buffer_free( sql_buf );
2685 buffer_fadd(sql_buf, "( %s )", subpred);
2689 } else if ( search_hash->type == JSON_HASH ) {
2690 osrfLogDebug(OSRF_LOG_MARK, "%s: In WHERE clause, condition type is JSON_HASH", MODULENAME);
2691 jsonIterator* search_itr = jsonNewIterator( search_hash );
2692 if( !jsonIteratorHasNext( search_itr ) ) {
2695 "%s: Invalid predicate structure: empty JSON object",
2698 jsonIteratorFree( search_itr );
2699 buffer_free( sql_buf );
2703 while ( (node = jsonIteratorNext( search_itr )) ) {
2708 if (opjoin_type == OR_OP_JOIN)
2709 buffer_add(sql_buf, " OR ");
2711 buffer_add(sql_buf, " AND ");
2714 if ( '+' == search_itr->key[ 0 ] ) {
2716 // This plus sign prefixes a class name or other table alias;
2717 // make sure the table alias is in scope
2718 ClassInfo* alias_info = search_all_alias( search_itr->key + 1 );
2719 if( ! alias_info ) {
2722 "%s: Invalid table alias \"%s\" in WHERE clause",
2726 jsonIteratorFree( search_itr );
2727 buffer_free( sql_buf );
2731 if ( node->type == JSON_STRING ) {
2732 // It's the name of a column; make sure it belongs to the class
2733 const char* fieldname = jsonObjectGetString( node );
2734 if( ! osrfHashGet( alias_info->fields, fieldname ) ) {
2737 "%s: Invalid column name \"%s\" in WHERE clause for table alias \"%s\"",
2742 jsonIteratorFree( search_itr );
2743 buffer_free( sql_buf );
2747 buffer_fadd(sql_buf, " \"%s\".%s ", alias_info->alias, fieldname );
2749 // It's something more complicated
2750 char* subpred = searchWHERE( node, alias_info, AND_OP_JOIN, ctx );
2752 jsonIteratorFree( search_itr );
2753 buffer_free( sql_buf );
2757 buffer_fadd(sql_buf, "( %s )", subpred);
2760 } else if ( '-' == search_itr->key[ 0 ] ) {
2761 if ( !strcasecmp("-or",search_itr->key) ) {
2762 char* subpred = searchWHERE( node, class_info, OR_OP_JOIN, ctx );
2764 jsonIteratorFree( search_itr );
2765 buffer_free( sql_buf );
2769 buffer_fadd(sql_buf, "( %s )", subpred);
2771 } else if ( !strcasecmp("-and",search_itr->key) ) {
2772 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2774 jsonIteratorFree( search_itr );
2775 buffer_free( sql_buf );
2779 buffer_fadd(sql_buf, "( %s )", subpred);
2781 } else if ( !strcasecmp("-not",search_itr->key) ) {
2782 char* subpred = searchWHERE( node, class_info, AND_OP_JOIN, ctx );
2784 jsonIteratorFree( search_itr );
2785 buffer_free( sql_buf );
2789 buffer_fadd(sql_buf, " NOT ( %s )", subpred);
2791 } else if ( !strcasecmp("-exists",search_itr->key) ) {
2792 char* subpred = buildQuery( ctx, node, SUBSELECT );
2794 jsonIteratorFree( search_itr );
2795 buffer_free( sql_buf );
2799 buffer_fadd(sql_buf, "EXISTS ( %s )", subpred);
2801 } else if ( !strcasecmp("-not-exists",search_itr->key) ) {
2802 char* subpred = buildQuery( ctx, node, SUBSELECT );
2804 jsonIteratorFree( search_itr );
2805 buffer_free( sql_buf );
2809 buffer_fadd(sql_buf, "NOT EXISTS ( %s )", subpred);
2811 } else { // Invalid "minus" operator
2814 "%s: Invalid operator \"%s\" in WHERE clause",
2818 jsonIteratorFree( search_itr );
2819 buffer_free( sql_buf );
2825 const char* class = class_info->class_name;
2826 osrfHash* fields = class_info->fields;
2827 osrfHash* field = osrfHashGet( fields, search_itr->key );
2830 const char* table = class_info->source_def;
2833 "%s: Attempt to reference non-existent column \"%s\" on %s (%s)",
2836 table ? table : "?",
2839 jsonIteratorFree(search_itr);
2840 buffer_free(sql_buf);
2844 char* subpred = searchPredicate( class_info, field, node, ctx );
2846 buffer_free(sql_buf);
2847 jsonIteratorFree(search_itr);
2851 buffer_add( sql_buf, subpred );
2855 jsonIteratorFree(search_itr);
2858 // ERROR ... only hash and array allowed at this level
2859 char* predicate_string = jsonObjectToJSON( search_hash );
2862 "%s: Invalid predicate structure: %s",
2866 buffer_free(sql_buf);
2867 free(predicate_string);
2871 return buffer_release(sql_buf);
2874 /* Build a JSON_ARRAY of field names for a given table alias
2876 static jsonObject* defaultSelectList( const char* table_alias ) {
2881 ClassInfo* class_info = search_all_alias( table_alias );
2882 if( ! class_info ) {
2885 "%s: Can't build default SELECT clause for \"%s\"; no such table alias",
2892 jsonObject* array = jsonNewObjectType( JSON_ARRAY );
2893 osrfHash* field_def = NULL;
2894 osrfHashIterator* field_itr = osrfNewHashIterator( class_info->fields );
2895 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
2896 const char* field_name = osrfHashIteratorKey( field_itr );
2897 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
2898 jsonObjectPush( array, jsonNewObject( field_name ) );
2901 osrfHashIteratorFree( field_itr );
2906 // Translate a jsonObject into a UNION, INTERSECT, or EXCEPT query.
2907 // The jsonObject must be a JSON_HASH with an single entry for "union",
2908 // "intersect", or "except". The data associated with this key must be an
2909 // array of hashes, each hash being a query.
2910 // Also allowed but currently ignored: entries for "order_by" and "alias".
2911 static char* doCombo( osrfMethodContext* ctx, jsonObject* combo, int flags ) {
2913 if( ! combo || combo->type != JSON_HASH )
2914 return NULL; // should be impossible; validated by caller
2916 const jsonObject* query_array = NULL; // array of subordinate queries
2917 const char* op = NULL; // name of operator, e.g. UNION
2918 const char* alias = NULL; // alias for the query (needed for ORDER BY)
2919 int op_count = 0; // for detecting conflicting operators
2920 int excepting = 0; // boolean
2921 int all = 0; // boolean
2922 jsonObject* order_obj = NULL;
2924 // Identify the elements in the hash
2925 jsonIterator* query_itr = jsonNewIterator( combo );
2926 jsonObject* curr_obj = NULL;
2927 while( (curr_obj = jsonIteratorNext( query_itr ) ) ) {
2928 if( ! strcmp( "union", query_itr->key ) ) {
2931 query_array = curr_obj;
2932 } else if( ! strcmp( "intersect", query_itr->key ) ) {
2935 query_array = curr_obj;
2936 } else if( ! strcmp( "except", query_itr->key ) ) {
2940 query_array = curr_obj;
2941 } else if( ! strcmp( "order_by", query_itr->key ) ) {
2944 "%s: ORDER BY not supported for UNION, INTERSECT, or EXCEPT",
2947 order_obj = curr_obj;
2948 } else if( ! strcmp( "alias", query_itr->key ) ) {
2949 if( curr_obj->type != JSON_STRING ) {
2950 jsonIteratorFree( query_itr );
2953 alias = jsonObjectGetString( curr_obj );
2954 } else if( ! strcmp( "all", query_itr->key ) ) {
2955 if( obj_is_true( curr_obj ) )
2959 osrfAppSessionStatus(
2961 OSRF_STATUS_INTERNALSERVERERROR,
2962 "osrfMethodException",
2964 "Malformed query; unexpected entry in query object"
2968 "%s: Unexpected entry for \"%s\" in%squery",
2973 jsonIteratorFree( query_itr );
2977 jsonIteratorFree( query_itr );
2979 // More sanity checks
2980 if( ! query_array ) {
2982 osrfAppSessionStatus(
2984 OSRF_STATUS_INTERNALSERVERERROR,
2985 "osrfMethodException",
2987 "Expected UNION, INTERSECT, or EXCEPT operator not found"
2991 "%s: Expected UNION, INTERSECT, or EXCEPT operator not found",
2994 return NULL; // should be impossible...
2995 } else if( op_count > 1 ) {
2997 osrfAppSessionStatus(
2999 OSRF_STATUS_INTERNALSERVERERROR,
3000 "osrfMethodException",
3002 "Found more than one of UNION, INTERSECT, and EXCEPT in same query"
3006 "%s: Found more than one of UNION, INTERSECT, and EXCEPT in same query",
3010 } if( query_array->type != JSON_ARRAY ) {
3012 osrfAppSessionStatus(
3014 OSRF_STATUS_INTERNALSERVERERROR,
3015 "osrfMethodException",
3017 "Malformed query: expected array of queries under UNION, INTERSECT or EXCEPT"
3021 "%s: Expected JSON_ARRAY of queries for%soperator; found %s",
3024 json_type( query_array->type )
3027 } if( query_array->size < 2 ) {
3029 osrfAppSessionStatus(
3031 OSRF_STATUS_INTERNALSERVERERROR,
3032 "osrfMethodException",
3034 "UNION, INTERSECT or EXCEPT requires multiple queries as operands"
3038 "%s:%srequires multiple queries as operands",
3043 } else if( excepting && query_array->size > 2 ) {
3045 osrfAppSessionStatus(
3047 OSRF_STATUS_INTERNALSERVERERROR,
3048 "osrfMethodException",
3050 "EXCEPT operator has too many queries as operands"
3054 "%s:EXCEPT operator has too many queries as operands",
3058 } else if( order_obj && ! alias ) {
3060 osrfAppSessionStatus(
3062 OSRF_STATUS_INTERNALSERVERERROR,
3063 "osrfMethodException",
3065 "ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT"
3069 "%s:ORDER BY requires an alias for a UNION, INTERSECT, or EXCEPT",
3075 // So far so good. Now build the SQL.
3076 growing_buffer* sql = buffer_init( 256 );
3078 // If we nested inside another UNION, INTERSECT, or EXCEPT,
3079 // Add a layer of parentheses
3080 if( flags & SUBCOMBO )
3081 OSRF_BUFFER_ADD( sql, "( " );
3083 // Traverse the query array. Each entry should be a hash.
3084 int first = 1; // boolean
3086 jsonObject* query = NULL;
3087 while((query = jsonObjectGetIndex( query_array, i++ ) )) {
3088 if( query->type != JSON_HASH ) {
3090 osrfAppSessionStatus(
3092 OSRF_STATUS_INTERNALSERVERERROR,
3093 "osrfMethodException",
3095 "Malformed query under UNION, INTERSECT or EXCEPT"
3099 "%s: Malformed query under%s -- expected JSON_HASH, found %s",
3102 json_type( query->type )
3111 OSRF_BUFFER_ADD( sql, op );
3113 OSRF_BUFFER_ADD( sql, "ALL " );
3116 char* query_str = buildQuery( ctx, query, SUBSELECT | SUBCOMBO );
3120 "%s: Error building query under%s",
3128 OSRF_BUFFER_ADD( sql, query_str );
3131 if( flags & SUBCOMBO )
3132 OSRF_BUFFER_ADD_CHAR( sql, ')' );
3134 if ( !(flags & SUBSELECT) )
3135 OSRF_BUFFER_ADD_CHAR( sql, ';' );
3137 return buffer_release( sql );
3140 // Translate a jsonObject into a SELECT, UNION, INTERSECT, or EXCEPT query.
3141 // The jsonObject must be a JSON_HASH with an entry for "from", "union", "intersect",
3142 // or "except" to indicate the type of query.
3143 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags ) {
3147 osrfAppSessionStatus(
3149 OSRF_STATUS_INTERNALSERVERERROR,
3150 "osrfMethodException",
3152 "Malformed query; no query object"
3154 osrfLogError( OSRF_LOG_MARK, "%s: Null pointer to query object", MODULENAME );
3156 } else if( query->type != JSON_HASH ) {
3158 osrfAppSessionStatus(
3160 OSRF_STATUS_INTERNALSERVERERROR,
3161 "osrfMethodException",
3163 "Malformed query object"
3167 "%s: Query object is %s instead of JSON_HASH",
3169 json_type( query->type )
3174 // Determine what kind of query it purports to be, and dispatch accordingly.
3175 if( jsonObjectGetKey( query, "union" ) ||
3176 jsonObjectGetKey( query, "intersect" ) ||
3177 jsonObjectGetKey( query, "except" ) ) {
3178 return doCombo( ctx, query, flags );
3180 // It is presumably a SELECT query
3182 // Push a node onto the stack for the current query. Every level of
3183 // subquery gets its own QueryFrame on the Stack.
3186 // Build an SQL SELECT statement
3189 jsonObjectGetKey( query, "select" ),
3190 jsonObjectGetKey( query, "from" ),
3191 jsonObjectGetKey( query, "where" ),
3192 jsonObjectGetKey( query, "having" ),
3193 jsonObjectGetKey( query, "order_by" ),
3194 jsonObjectGetKey( query, "limit" ),
3195 jsonObjectGetKey( query, "offset" ),
3204 /* method context */ osrfMethodContext* ctx,
3206 /* SELECT */ jsonObject* selhash,
3207 /* FROM */ jsonObject* join_hash,
3208 /* WHERE */ jsonObject* search_hash,
3209 /* HAVING */ jsonObject* having_hash,
3210 /* ORDER BY */ jsonObject* order_hash,
3211 /* LIMIT */ jsonObject* limit,
3212 /* OFFSET */ jsonObject* offset,
3213 /* flags */ int flags
3215 const char* locale = osrf_message_get_last_locale();
3217 // general tmp objects
3218 const jsonObject* tmp_const;
3219 jsonObject* selclass = NULL;
3220 jsonObject* snode = NULL;
3221 jsonObject* onode = NULL;
3223 char* string = NULL;
3224 int from_function = 0;
3229 osrfLogDebug(OSRF_LOG_MARK, "cstore SELECT locale: %s", locale ? locale : "(none)" );
3231 // punt if there's no FROM clause
3232 if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) {
3235 "%s: FROM clause is missing or empty",
3239 osrfAppSessionStatus(
3241 OSRF_STATUS_INTERNALSERVERERROR,
3242 "osrfMethodException",
3244 "FROM clause is missing or empty in JSON query"
3249 // the core search class
3250 const char* core_class = NULL;
3252 // get the core class -- the only key of the top level FROM clause, or a string
3253 if (join_hash->type == JSON_HASH) {
3254 jsonIterator* tmp_itr = jsonNewIterator( join_hash );
3255 snode = jsonIteratorNext( tmp_itr );
3257 // Populate the current QueryFrame with information
3258 // about the core class
3259 if( add_query_core( NULL, tmp_itr->key ) ) {
3261 osrfAppSessionStatus(
3263 OSRF_STATUS_INTERNALSERVERERROR,
3264 "osrfMethodException",
3266 "Unable to look up core class"
3270 core_class = curr_query->core.class_name;
3273 jsonObject* extra = jsonIteratorNext( tmp_itr );
3275 jsonIteratorFree( tmp_itr );
3278 // There shouldn't be more than one entry in join_hash
3282 "%s: Malformed FROM clause: extra entry in JSON_HASH",
3286 osrfAppSessionStatus(
3288 OSRF_STATUS_INTERNALSERVERERROR,
3289 "osrfMethodException",
3291 "Malformed FROM clause in JSON query"
3293 return NULL; // Malformed join_hash; extra entry
3295 } else if (join_hash->type == JSON_ARRAY) {
3296 // We're selecting from a function, not from a table
3298 core_class = jsonObjectGetString( jsonObjectGetIndex(join_hash, 0) );
3301 } else if (join_hash->type == JSON_STRING) {
3302 // Populate the current QueryFrame with information
3303 // about the core class
3304 core_class = jsonObjectGetString( join_hash );
3306 if( add_query_core( NULL, core_class ) ) {
3308 osrfAppSessionStatus(
3310 OSRF_STATUS_INTERNALSERVERERROR,
3311 "osrfMethodException",
3313 "Unable to look up core class"
3321 "%s: FROM clause is unexpected JSON type: %s",
3323 json_type( join_hash->type )
3326 osrfAppSessionStatus(
3328 OSRF_STATUS_INTERNALSERVERERROR,
3329 "osrfMethodException",
3331 "Ill-formed FROM clause in JSON query"
3336 // Build the join clause, if any, while filling out the list
3337 // of joined classes in the current QueryFrame.
3338 char* join_clause = NULL;
3339 if( join_hash && ! from_function ) {
3341 join_clause = searchJOIN( join_hash, &curr_query->core );
3342 if( ! join_clause ) {
3344 osrfAppSessionStatus(
3346 OSRF_STATUS_INTERNALSERVERERROR,
3347 "osrfMethodException",
3349 "Unable to construct JOIN clause(s)"
3355 // For in case we don't get a select list
3356 jsonObject* defaultselhash = NULL;
3358 // if there is no select list, build a default select list ...
3359 if (!selhash && !from_function) {
3360 jsonObject* default_list = defaultSelectList( core_class );
3361 if( ! default_list ) {
3363 osrfAppSessionStatus(
3365 OSRF_STATUS_INTERNALSERVERERROR,
3366 "osrfMethodException",
3368 "Unable to build default SELECT clause in JSON query"
3370 free( join_clause );
3375 selhash = defaultselhash = jsonNewObjectType(JSON_HASH);
3376 jsonObjectSetKey( selhash, core_class, default_list );
3379 // The SELECT clause can be encoded only by a hash
3380 if( !from_function && selhash->type != JSON_HASH ) {
3383 "%s: Expected JSON_HASH for SELECT clause; found %s",
3385 json_type( selhash->type )
3389 osrfAppSessionStatus(
3391 OSRF_STATUS_INTERNALSERVERERROR,
3392 "osrfMethodException",
3394 "Malformed SELECT clause in JSON query"
3396 free( join_clause );
3400 // If you see a null or wild card specifier for the core class, or an
3401 // empty array, replace it with a default SELECT list
3402 tmp_const = jsonObjectGetKeyConst( selhash, core_class );
3404 int default_needed = 0; // boolean
3405 if( JSON_STRING == tmp_const->type
3406 && !strcmp( "*", jsonObjectGetString( tmp_const ) ))
3408 else if( JSON_NULL == tmp_const->type )
3411 if( default_needed ) {
3412 // Build a default SELECT list
3413 jsonObject* default_list = defaultSelectList( core_class );
3414 if( ! default_list ) {
3416 osrfAppSessionStatus(
3418 OSRF_STATUS_INTERNALSERVERERROR,
3419 "osrfMethodException",
3421 "Can't build default SELECT clause in JSON query"
3423 free( join_clause );
3428 jsonObjectSetKey( selhash, core_class, default_list );
3432 // temp buffers for the SELECT list and GROUP BY clause
3433 growing_buffer* select_buf = buffer_init(128);
3434 growing_buffer* group_buf = buffer_init(128);
3436 int aggregate_found = 0; // boolean
3438 // Build a select list
3439 if(from_function) // From a function we select everything
3440 OSRF_BUFFER_ADD_CHAR( select_buf, '*' );
3443 // Build the SELECT list as SQL
3447 jsonIterator* selclass_itr = jsonNewIterator( selhash );
3448 while ( (selclass = jsonIteratorNext( selclass_itr )) ) { // For each class
3450 const char* cname = selclass_itr->key;
3452 // Make sure the target relation is in the FROM clause.
3454 // At this point join_hash is a step down from the join_hash we
3455 // received as a parameter. If the original was a JSON_STRING,
3456 // then json_hash is now NULL. If the original was a JSON_HASH,
3457 // then json_hash is now the first (and only) entry in it,
3458 // denoting the core class. We've already excluded the
3459 // possibility that the original was a JSON_ARRAY, because in
3460 // that case from_function would be non-NULL, and we wouldn't
3463 // If the current table alias isn't in scope, bail out
3464 ClassInfo* class_info = search_alias( cname );
3465 if( ! class_info ) {
3468 "%s: SELECT clause references class not in FROM clause: \"%s\"",
3473 osrfAppSessionStatus(
3475 OSRF_STATUS_INTERNALSERVERERROR,
3476 "osrfMethodException",
3478 "Selected class not in FROM clause in JSON query"
3480 jsonIteratorFree( selclass_itr );
3481 buffer_free( select_buf );
3482 buffer_free( group_buf );
3483 if( defaultselhash ) jsonObjectFree( defaultselhash );
3484 free( join_clause );
3488 if( selclass->type != JSON_ARRAY ) {
3491 "%s: Malformed SELECT list for class \"%s\"; not an array",
3496 osrfAppSessionStatus(
3498 OSRF_STATUS_INTERNALSERVERERROR,
3499 "osrfMethodException",
3501 "Selected class not in FROM clause in JSON query"
3504 jsonIteratorFree( selclass_itr );
3505 buffer_free( select_buf );
3506 buffer_free( group_buf );
3507 if( defaultselhash ) jsonObjectFree( defaultselhash );
3508 free( join_clause );
3512 // Look up some attributes of the current class
3513 osrfHash* idlClass = class_info->class_def;
3514 osrfHash* class_field_set = class_info->fields;
3515 const char* class_pkey = osrfHashGet( idlClass, "primarykey" );
3516 const char* class_tname = osrfHashGet( idlClass, "tablename" );
3518 if( 0 == selclass->size ) {
3521 "%s: No columns selected from \"%s\"",
3527 // stitch together the column list for the current table alias...
3528 unsigned long field_idx = 0;
3529 jsonObject* selfield = NULL;
3530 while((selfield = jsonObjectGetIndex( selclass, field_idx++ ) )) {
3532 // If we need a separator comma, add one
3536 OSRF_BUFFER_ADD_CHAR( select_buf, ',' );
3539 // if the field specification is a string, add it to the list
3540 if (selfield->type == JSON_STRING) {
3542 // Look up the field in the IDL
3543 const char* col_name = jsonObjectGetString( selfield );
3544 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3546 // No such field in current class
3549 "%s: Selected column \"%s\" not defined in IDL for class \"%s\"",
3555 osrfAppSessionStatus(
3557 OSRF_STATUS_INTERNALSERVERERROR,
3558 "osrfMethodException",
3560 "Selected column not defined in JSON query"
3562 jsonIteratorFree( selclass_itr );
3563 buffer_free( select_buf );
3564 buffer_free( group_buf );
3565 if( defaultselhash ) jsonObjectFree( defaultselhash );
3566 free( join_clause );
3568 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3569 // Virtual field not allowed
3572 "%s: Selected column \"%s\" for class \"%s\" is virtual",
3578 osrfAppSessionStatus(
3580 OSRF_STATUS_INTERNALSERVERERROR,
3581 "osrfMethodException",
3583 "Selected column may not be virtual in JSON query"
3585 jsonIteratorFree( selclass_itr );
3586 buffer_free( select_buf );
3587 buffer_free( group_buf );
3588 if( defaultselhash ) jsonObjectFree( defaultselhash );
3589 free( join_clause );
3595 if (flags & DISABLE_I18N)
3598 i18n = osrfHashGet(field_def, "i18n");
3600 if( str_is_true( i18n ) ) {
3601 buffer_fadd( select_buf,
3602 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3603 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, col_name );
3605 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3608 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, col_name );
3611 // ... but it could be an object, in which case we check for a Field Transform
3612 } else if (selfield->type == JSON_HASH) {
3614 const char* col_name = jsonObjectGetString( jsonObjectGetKeyConst( selfield, "column" ) );
3616 // Get the field definition from the IDL
3617 osrfHash* field_def = osrfHashGet( class_field_set, col_name );
3619 // No such field in current class
3622 "%s: Selected column \"%s\" is not defined in IDL for class \"%s\"",
3628 osrfAppSessionStatus(
3630 OSRF_STATUS_INTERNALSERVERERROR,
3631 "osrfMethodException",
3633 "Selected column is not defined in JSON query"
3635 jsonIteratorFree( selclass_itr );
3636 buffer_free( select_buf );
3637 buffer_free( group_buf );
3638 if( defaultselhash ) jsonObjectFree( defaultselhash );
3639 free( join_clause );
3641 } else if ( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3642 // No such field in current class
3645 "%s: Selected column \"%s\" is virtual for class \"%s\"",
3651 osrfAppSessionStatus(
3653 OSRF_STATUS_INTERNALSERVERERROR,
3654 "osrfMethodException",
3656 "Selected column is virtual in JSON query"
3658 jsonIteratorFree( selclass_itr );
3659 buffer_free( select_buf );
3660 buffer_free( group_buf );
3661 if( defaultselhash ) jsonObjectFree( defaultselhash );
3662 free( join_clause );
3666 // Decide what to use as a column alias
3668 if ((tmp_const = jsonObjectGetKeyConst( selfield, "alias" ))) {
3669 _alias = jsonObjectGetString( tmp_const );
3670 } else { // Use field name as the alias
3674 if (jsonObjectGetKeyConst( selfield, "transform" )) {
3675 char* transform_str = searchFieldTransform(class_info->alias, field_def, selfield);
3676 if( transform_str ) {
3677 buffer_fadd(select_buf, " %s AS \"%s\"", transform_str, _alias);
3678 free(transform_str);
3681 osrfAppSessionStatus(
3683 OSRF_STATUS_INTERNALSERVERERROR,
3684 "osrfMethodException",
3686 "Unable to generate transform function in JSON query"
3688 jsonIteratorFree( selclass_itr );
3689 buffer_free( select_buf );
3690 buffer_free( group_buf );
3691 if( defaultselhash ) jsonObjectFree( defaultselhash );
3692 free( join_clause );
3699 if (flags & DISABLE_I18N)
3702 i18n = osrfHashGet(field_def, "i18n");
3704 if( str_is_true( i18n ) ) {
3705 buffer_fadd( select_buf,
3706 " oils_i18n_xlate('%s', '%s', '%s', '%s', \"%s\".%s::TEXT, '%s') AS \"%s\"",
3707 class_tname, cname, col_name, class_pkey, cname, class_pkey, locale, _alias);
3709 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3712 buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, col_name, _alias);
3719 "%s: Selected item is unexpected JSON type: %s",
3721 json_type( selfield->type )
3724 osrfAppSessionStatus(
3726 OSRF_STATUS_INTERNALSERVERERROR,
3727 "osrfMethodException",
3729 "Ill-formed SELECT item in JSON query"
3731 jsonIteratorFree( selclass_itr );
3732 buffer_free( select_buf );
3733 buffer_free( group_buf );
3734 if( defaultselhash ) jsonObjectFree( defaultselhash );
3735 free( join_clause );
3739 const jsonObject* agg_obj = jsonObjectGetKey( selfield, "aggregate" );
3740 if( obj_is_true( agg_obj ) )
3741 aggregate_found = 1;
3743 // Append a comma (except for the first one)
3744 // and add the column to a GROUP BY clause
3748 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3750 buffer_fadd(group_buf, " %d", sel_pos);
3754 if (is_agg->size || (flags & SELECT_DISTINCT)) {
3756 const jsonObject* aggregate_obj = jsonObjectGetKey( selfield, "aggregate" );
3757 if ( ! obj_is_true( aggregate_obj ) ) {
3761 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3764 buffer_fadd(group_buf, " %d", sel_pos);
3767 } else if (is_agg = jsonObjectGetKey( selfield, "having" )) {
3771 OSRF_BUFFER_ADD_CHAR( group_buf, ',' );
3774 _column = searchFieldTransform(class_info->alias, field, selfield);
3775 OSRF_BUFFER_ADD_CHAR(group_buf, ' ');
3776 OSRF_BUFFER_ADD(group_buf, _column);
3777 _column = searchFieldTransform(class_info->alias, field, selfield);
3784 } // end while -- iterating across SELECT columns
3786 } // end while -- iterating across classes
3788 jsonIteratorFree(selclass_itr);
3792 char* col_list = buffer_release(select_buf);
3794 // Make sure the SELECT list isn't empty. This can happen, for example,
3795 // if we try to build a default SELECT clause from a non-core table.
3798 osrfLogError(OSRF_LOG_MARK, "%s: SELECT clause is empty", MODULENAME );
3800 osrfAppSessionStatus(
3802 OSRF_STATUS_INTERNALSERVERERROR,
3803 "osrfMethodException",
3805 "SELECT list is empty"
3808 buffer_free( group_buf );
3809 if( defaultselhash ) jsonObjectFree( defaultselhash );
3810 free( join_clause );
3815 if (from_function) table = searchValueTransform(join_hash);
3816 else table = strdup( curr_query->core.source_def );
3820 osrfAppSessionStatus(
3822 OSRF_STATUS_INTERNALSERVERERROR,
3823 "osrfMethodException",
3825 "Unable to identify table for core class"
3828 buffer_free( group_buf );
3829 if( defaultselhash ) jsonObjectFree( defaultselhash );
3830 free( join_clause );
3834 // Put it all together
3835 growing_buffer* sql_buf = buffer_init(128);
3836 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, table, core_class );
3840 // Append the join clause, if any
3842 buffer_add(sql_buf, join_clause);
3846 char* order_by_list = NULL;
3847 char* having_buf = NULL;
3849 if (!from_function) {
3851 // Build a WHERE clause, if there is one
3852 if ( search_hash ) {
3853 buffer_add(sql_buf, " WHERE ");
3855 // and it's on the WHERE clause
3856 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
3859 osrfAppSessionStatus(
3861 OSRF_STATUS_INTERNALSERVERERROR,
3862 "osrfMethodException",
3864 "Severe query error in WHERE predicate -- see error log for more details"
3867 buffer_free(group_buf);
3868 buffer_free(sql_buf);
3869 if (defaultselhash) jsonObjectFree(defaultselhash);
3873 buffer_add(sql_buf, pred);
3877 // Build a HAVING clause, if there is one
3878 if ( having_hash ) {
3880 // and it's on the the WHERE clause
3881 having_buf = searchWHERE( having_hash, &curr_query->core, AND_OP_JOIN, ctx );
3883 if( ! having_buf ) {
3885 osrfAppSessionStatus(
3887 OSRF_STATUS_INTERNALSERVERERROR,
3888 "osrfMethodException",
3890 "Severe query error in HAVING predicate -- see error log for more details"
3893 buffer_free(group_buf);
3894 buffer_free(sql_buf);
3895 if (defaultselhash) jsonObjectFree(defaultselhash);
3900 growing_buffer* order_buf = NULL; // to collect ORDER BY list
3902 // Build an ORDER BY clause, if there is one
3903 if( NULL == order_hash )
3904 ; // No ORDER BY? do nothing
3905 else if( JSON_ARRAY == order_hash->type ) {
3906 // Array of field specifications, each specification being a
3907 // hash to define the class, field, and other details
3909 jsonObject* order_spec;
3910 while( (order_spec = jsonObjectGetIndex( order_hash, order_idx++ ) ) ) {
3912 if( JSON_HASH != order_spec->type ) {
3913 osrfLogError(OSRF_LOG_MARK,
3914 "%s: Malformed field specification in ORDER BY clause; expected JSON_HASH, found %s",
3915 MODULENAME, json_type( order_spec->type ) );
3917 osrfAppSessionStatus(
3919 OSRF_STATUS_INTERNALSERVERERROR,
3920 "osrfMethodException",
3922 "Malformed ORDER BY clause -- see error log for more details"
3924 buffer_free( order_buf );
3926 buffer_free(group_buf);
3927 buffer_free(sql_buf);
3928 if (defaultselhash) jsonObjectFree(defaultselhash);
3932 const char* class_alias =
3933 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "class" ) );
3935 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "field" ) );
3938 OSRF_BUFFER_ADD(order_buf, ", ");
3940 order_buf = buffer_init(128);
3942 if( !field || !class_alias ) {
3943 osrfLogError(OSRF_LOG_MARK,
3944 "%s: Missing class or field name in field specification of ORDER BY clause",
3947 osrfAppSessionStatus(
3949 OSRF_STATUS_INTERNALSERVERERROR,
3950 "osrfMethodException",
3952 "Malformed ORDER BY clause -- see error log for more details"
3954 buffer_free( order_buf );
3956 buffer_free(group_buf);
3957 buffer_free(sql_buf);
3958 if (defaultselhash) jsonObjectFree(defaultselhash);
3962 ClassInfo* order_class_info = search_alias( class_alias );
3963 if( ! order_class_info ) {
3964 osrfLogError(OSRF_LOG_MARK, "%s: ORDER BY clause references class \"%s\" "
3965 "not in FROM clause", MODULENAME, class_alias );
3967 osrfAppSessionStatus(
3969 OSRF_STATUS_INTERNALSERVERERROR,
3970 "osrfMethodException",
3972 "Invalid class referenced in ORDER BY clause -- see error log for more details"
3975 buffer_free(group_buf);
3976 buffer_free(sql_buf);
3977 if (defaultselhash) jsonObjectFree(defaultselhash);
3981 osrfHash* field_def = osrfHashGet( order_class_info->fields, field );
3983 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\".%s referenced in ORDER BY clause",
3984 MODULENAME, class_alias, field );
3986 osrfAppSessionStatus(
3988 OSRF_STATUS_INTERNALSERVERERROR,
3989 "osrfMethodException",
3991 "Invalid field referenced in ORDER BY clause -- see error log for more details"
3994 buffer_free(group_buf);
3995 buffer_free(sql_buf);
3996 if (defaultselhash) jsonObjectFree(defaultselhash);
3998 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
3999 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4000 MODULENAME, field );
4002 osrfAppSessionStatus(
4004 OSRF_STATUS_INTERNALSERVERERROR,
4005 "osrfMethodException",
4007 "Virtual field in ORDER BY clause -- see error log for more details"
4009 buffer_free( order_buf );
4011 buffer_free(group_buf);
4012 buffer_free(sql_buf);
4013 if (defaultselhash) jsonObjectFree(defaultselhash);
4017 if( jsonObjectGetKeyConst( order_spec, "transform" ) ) {
4018 char* transform_str = searchFieldTransform( class_alias, field_def, order_spec );
4019 if( ! transform_str ) {
4021 osrfAppSessionStatus(
4023 OSRF_STATUS_INTERNALSERVERERROR,
4024 "osrfMethodException",
4026 "Severe query error in ORDER BY clause -- see error log for more details"
4028 buffer_free( order_buf );
4030 buffer_free(group_buf);
4031 buffer_free(sql_buf);
4032 if (defaultselhash) jsonObjectFree(defaultselhash);
4036 OSRF_BUFFER_ADD( order_buf, transform_str );
4037 free( transform_str );
4040 buffer_fadd( order_buf, "\"%s\".%s", class_alias, field );
4042 const char* direction =
4043 jsonObjectGetString( jsonObjectGetKeyConst( order_spec, "direction" ) );
4045 if( direction[ 0 ] || 'D' == direction[ 0 ] )
4046 OSRF_BUFFER_ADD( order_buf, " DESC" );
4048 OSRF_BUFFER_ADD( order_buf, " ASC" );
4051 } else if( JSON_HASH == order_hash->type ) {
4052 // This hash is keyed on class alias. Each class has either
4053 // an array of field names or a hash keyed on field name.
4054 jsonIterator* class_itr = jsonNewIterator( order_hash );
4055 while ( (snode = jsonIteratorNext( class_itr )) ) {
4057 ClassInfo* order_class_info = search_alias( class_itr->key );
4058 if( ! order_class_info ) {
4059 osrfLogError(OSRF_LOG_MARK, "%s: Invalid class \"%s\" referenced in ORDER BY clause",
4060 MODULENAME, class_itr->key );
4062 osrfAppSessionStatus(
4064 OSRF_STATUS_INTERNALSERVERERROR,
4065 "osrfMethodException",
4067 "Invalid class referenced in ORDER BY clause -- see error log for more details"
4069 jsonIteratorFree( class_itr );
4070 buffer_free( order_buf );
4072 buffer_free(group_buf);
4073 buffer_free(sql_buf);
4074 if (defaultselhash) jsonObjectFree(defaultselhash);
4078 osrfHash* field_list_def = order_class_info->fields;
4080 if ( snode->type == JSON_HASH ) {
4082 // Hash is keyed on field names from the current class. For each field
4083 // there is another layer of hash to define the sorting details, if any,
4084 // or a string to indicate direction of sorting.
4085 jsonIterator* order_itr = jsonNewIterator( snode );
4086 while ( (onode = jsonIteratorNext( order_itr )) ) {
4088 osrfHash* field_def = osrfHashGet( field_list_def, order_itr->key );
4090 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4091 MODULENAME, order_itr->key );
4093 osrfAppSessionStatus(
4095 OSRF_STATUS_INTERNALSERVERERROR,
4096 "osrfMethodException",
4098 "Invalid field in ORDER BY clause -- see error log for more details"
4100 jsonIteratorFree( order_itr );
4101 jsonIteratorFree( class_itr );
4102 buffer_free( order_buf );
4104 buffer_free(group_buf);
4105 buffer_free(sql_buf);
4106 if (defaultselhash) jsonObjectFree(defaultselhash);
4108 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4109 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4110 MODULENAME, order_itr->key );
4112 osrfAppSessionStatus(
4114 OSRF_STATUS_INTERNALSERVERERROR,
4115 "osrfMethodException",
4117 "Virtual field in ORDER BY clause -- see error log for more details"
4119 jsonIteratorFree( order_itr );
4120 jsonIteratorFree( class_itr );
4121 buffer_free( order_buf );
4123 buffer_free(group_buf);
4124 buffer_free(sql_buf);
4125 if (defaultselhash) jsonObjectFree(defaultselhash);
4129 const char* direction = NULL;
4130 if ( onode->type == JSON_HASH ) {
4131 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4132 string = searchFieldTransform(
4134 osrfHashGet( field_list_def, order_itr->key ),
4138 if( ctx ) osrfAppSessionStatus(
4140 OSRF_STATUS_INTERNALSERVERERROR,
4141 "osrfMethodException",
4143 "Severe query error in ORDER BY clause -- see error log for more details"
4145 jsonIteratorFree( order_itr );
4146 jsonIteratorFree( class_itr );
4148 buffer_free(group_buf);
4149 buffer_free(order_buf);
4150 buffer_free(sql_buf);
4151 if (defaultselhash) jsonObjectFree(defaultselhash);
4155 growing_buffer* field_buf = buffer_init(16);
4156 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4157 string = buffer_release(field_buf);
4160 if ( (tmp_const = jsonObjectGetKeyConst( onode, "direction" )) ) {
4161 const char* dir = jsonObjectGetString(tmp_const);
4162 if (!strncasecmp(dir, "d", 1)) {
4163 direction = " DESC";
4169 } else if ( JSON_NULL == onode->type || JSON_ARRAY == onode->type ) {
4170 osrfLogError( OSRF_LOG_MARK,
4171 "%s: Expected JSON_STRING in ORDER BY clause; found %s",
4172 MODULENAME, json_type( onode->type ) );
4174 osrfAppSessionStatus(
4176 OSRF_STATUS_INTERNALSERVERERROR,
4177 "osrfMethodException",
4179 "Malformed 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 string = strdup(order_itr->key);
4192 const char* dir = jsonObjectGetString(onode);
4193 if (!strncasecmp(dir, "d", 1)) {
4194 direction = " DESC";
4201 OSRF_BUFFER_ADD(order_buf, ", ");
4203 order_buf = buffer_init(128);
4205 OSRF_BUFFER_ADD(order_buf, string);
4209 OSRF_BUFFER_ADD(order_buf, direction);
4213 jsonIteratorFree(order_itr);
4215 } else if ( snode->type == JSON_ARRAY ) {
4217 // Array is a list of fields from the current class
4218 unsigned long order_idx = 0;
4219 while(( onode = jsonObjectGetIndex( snode, order_idx++ ) )) {
4221 const char* _f = jsonObjectGetString( onode );
4223 osrfHash* field_def = osrfHashGet( field_list_def, _f );
4225 osrfLogError(OSRF_LOG_MARK, "%s: Invalid field \"%s\" in ORDER BY clause",
4228 osrfAppSessionStatus(
4230 OSRF_STATUS_INTERNALSERVERERROR,
4231 "osrfMethodException",
4233 "Invalid field in ORDER BY clause -- see error log for more details"
4235 jsonIteratorFree( class_itr );
4236 buffer_free( order_buf );
4238 buffer_free(group_buf);
4239 buffer_free(sql_buf);
4240 if (defaultselhash) jsonObjectFree(defaultselhash);
4242 } else if( str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4243 osrfLogError(OSRF_LOG_MARK, "%s: Virtual field \"%s\" in ORDER BY clause",
4246 osrfAppSessionStatus(
4248 OSRF_STATUS_INTERNALSERVERERROR,
4249 "osrfMethodException",
4251 "Virtual field in ORDER BY clause -- see error log for more details"
4253 jsonIteratorFree( class_itr );
4254 buffer_free( order_buf );
4256 buffer_free(group_buf);
4257 buffer_free(sql_buf);
4258 if (defaultselhash) jsonObjectFree(defaultselhash);
4263 OSRF_BUFFER_ADD(order_buf, ", ");
4265 order_buf = buffer_init(128);
4267 buffer_fadd( order_buf, "\"%s\".%s", class_itr->key, _f);
4271 // IT'S THE OOOOOOOOOOOLD STYLE!
4273 osrfLogError(OSRF_LOG_MARK,
4274 "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME);
4276 osrfAppSessionStatus(
4278 OSRF_STATUS_INTERNALSERVERERROR,
4279 "osrfMethodException",
4281 "Severe query error -- see error log for more details"
4286 buffer_free(group_buf);
4287 buffer_free(order_buf);
4288 buffer_free(sql_buf);
4289 if (defaultselhash) jsonObjectFree(defaultselhash);
4290 jsonIteratorFree(class_itr);
4294 jsonIteratorFree( class_itr );
4296 osrfLogError(OSRF_LOG_MARK,
4297 "%s: Malformed ORDER BY clause; expected JSON_HASH or JSON_ARRAY, found %s",
4298 MODULENAME, json_type( order_hash->type ) );
4300 osrfAppSessionStatus(
4302 OSRF_STATUS_INTERNALSERVERERROR,
4303 "osrfMethodException",
4305 "Malformed ORDER BY clause -- see error log for more details"
4307 buffer_free( order_buf );
4309 buffer_free(group_buf);
4310 buffer_free(sql_buf);
4311 if (defaultselhash) jsonObjectFree(defaultselhash);
4316 order_by_list = buffer_release( order_buf );
4320 string = buffer_release(group_buf);
4322 if ( *string && ( aggregate_found || (flags & SELECT_DISTINCT) ) ) {
4323 OSRF_BUFFER_ADD( sql_buf, " GROUP BY " );
4324 OSRF_BUFFER_ADD( sql_buf, string );
4329 if( having_buf && *having_buf ) {
4330 OSRF_BUFFER_ADD( sql_buf, " HAVING " );
4331 OSRF_BUFFER_ADD( sql_buf, having_buf );
4335 if( order_by_list ) {
4337 if ( *order_by_list ) {
4338 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4339 OSRF_BUFFER_ADD( sql_buf, order_by_list );
4342 free( order_by_list );
4346 const char* str = jsonObjectGetString(limit);
4347 buffer_fadd( sql_buf, " LIMIT %d", atoi(str) );
4351 const char* str = jsonObjectGetString(offset);
4352 buffer_fadd( sql_buf, " OFFSET %d", atoi(str) );
4355 if (!(flags & SUBSELECT)) OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4357 if (defaultselhash) jsonObjectFree(defaultselhash);
4359 return buffer_release(sql_buf);
4361 } // end of SELECT()
4363 static char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) {
4365 const char* locale = osrf_message_get_last_locale();
4367 osrfHash* fields = osrfHashGet(meta, "fields");
4368 char* core_class = osrfHashGet(meta, "classname");
4370 const jsonObject* join_hash = jsonObjectGetKeyConst( order_hash, "join" );
4372 jsonObject* node = NULL;
4373 jsonObject* snode = NULL;
4374 jsonObject* onode = NULL;
4375 const jsonObject* _tmp = NULL;
4376 jsonObject* selhash = NULL;
4377 jsonObject* defaultselhash = NULL;
4379 growing_buffer* sql_buf = buffer_init(128);
4380 growing_buffer* select_buf = buffer_init(128);
4382 if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) {
4383 defaultselhash = jsonNewObjectType(JSON_HASH);
4384 selhash = defaultselhash;
4387 // If there's no SELECT list for the core class, build one
4388 if ( !jsonObjectGetKeyConst(selhash,core_class) ) {
4389 jsonObject* field_list = jsonNewObjectType( JSON_ARRAY );
4391 // Add every non-virtual field to the field list
4392 osrfHash* field_def = NULL;
4393 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
4394 while( ( field_def = osrfHashIteratorNext( field_itr ) ) ) {
4395 if( ! str_is_true( osrfHashGet( field_def, "virtual" ) ) ) {
4396 const char* field = osrfHashIteratorKey( field_itr );
4397 jsonObjectPush( field_list, jsonNewObject( field ) );
4400 osrfHashIteratorFree( field_itr );
4401 jsonObjectSetKey( selhash, core_class, field_list );
4405 jsonIterator* class_itr = jsonNewIterator( selhash );
4406 while ( (snode = jsonIteratorNext( class_itr )) ) {
4408 const char* cname = class_itr->key;
4409 osrfHash* idlClass = osrfHashGet( oilsIDL(), cname );
4410 if (!idlClass) continue;
4412 if (strcmp(core_class,class_itr->key)) {
4413 if (!join_hash) continue;
4415 jsonObject* found = jsonObjectFindPath(join_hash, "//%s", class_itr->key);
4417 jsonObjectFree(found);
4421 jsonObjectFree(found);
4424 jsonIterator* select_itr = jsonNewIterator( snode );
4425 while ( (node = jsonIteratorNext( select_itr )) ) {
4426 const char* item_str = jsonObjectGetString( node );
4427 osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), item_str );
4428 char* fname = osrfHashGet(field, "name");
4430 if (!field) continue;
4435 OSRF_BUFFER_ADD_CHAR(select_buf, ',');
4440 const jsonObject* no_i18n_obj = jsonObjectGetKey( order_hash, "no_i18n" );
4441 if ( obj_is_true( no_i18n_obj ) ) // Suppress internationalization?
4444 i18n = osrfHashGet(field, "i18n");
4446 if( str_is_true( i18n ) ) {
4447 char* pkey = osrfHashGet(idlClass, "primarykey");
4448 char* tname = osrfHashGet(idlClass, "tablename");
4450 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);
4452 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4455 buffer_fadd(select_buf, " \"%s\".%s", cname, fname);
4459 jsonIteratorFree(select_itr);
4462 jsonIteratorFree(class_itr);
4464 char* col_list = buffer_release(select_buf);
4465 char* table = getRelation(meta);
4467 table = strdup( "(null)" );
4469 buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, table, core_class );
4473 // Clear the query stack (as a fail-safe precaution against possible
4474 // leftover garbage); then push the first query frame onto the stack.
4475 clear_query_stack();
4477 if( add_query_core( NULL, core_class ) ) {
4479 osrfAppSessionStatus(
4481 OSRF_STATUS_INTERNALSERVERERROR,
4482 "osrfMethodException",
4484 "Unable to build query frame for core class"
4490 char* join_clause = searchJOIN( join_hash, &curr_query->core );
4491 OSRF_BUFFER_ADD_CHAR(sql_buf, ' ');
4492 OSRF_BUFFER_ADD(sql_buf, join_clause);
4496 osrfLogDebug(OSRF_LOG_MARK, "%s pre-predicate SQL = %s",
4497 MODULENAME, OSRF_BUFFER_C_STR(sql_buf));
4499 OSRF_BUFFER_ADD(sql_buf, " WHERE ");
4501 char* pred = searchWHERE( search_hash, &curr_query->core, AND_OP_JOIN, ctx );
4503 osrfAppSessionStatus(
4505 OSRF_STATUS_INTERNALSERVERERROR,
4506 "osrfMethodException",
4508 "Severe query error -- see error log for more details"
4510 buffer_free(sql_buf);
4511 if(defaultselhash) jsonObjectFree(defaultselhash);
4512 clear_query_stack();
4515 buffer_add(sql_buf, pred);
4520 char* string = NULL;
4521 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "order_by" )) ){
4523 growing_buffer* order_buf = buffer_init(128);
4526 jsonIterator* class_itr = jsonNewIterator( _tmp );
4527 while ( (snode = jsonIteratorNext( class_itr )) ) {
4529 if (!jsonObjectGetKeyConst(selhash,class_itr->key))
4532 if ( snode->type == JSON_HASH ) {
4534 jsonIterator* order_itr = jsonNewIterator( snode );
4535 while ( (onode = jsonIteratorNext( order_itr )) ) {
4537 osrfHash* field_def = oilsIDLFindPath( "/%s/fields/%s",
4538 class_itr->key, order_itr->key );
4542 char* direction = NULL;
4543 if ( onode->type == JSON_HASH ) {
4544 if ( jsonObjectGetKeyConst( onode, "transform" ) ) {
4545 string = searchFieldTransform( class_itr->key, field_def, onode );
4547 osrfAppSessionStatus(
4549 OSRF_STATUS_INTERNALSERVERERROR,
4550 "osrfMethodException",
4552 "Severe query error in ORDER BY clause -- see error log for more details"
4554 jsonIteratorFree( order_itr );
4555 jsonIteratorFree( class_itr );
4556 buffer_free( order_buf );
4557 buffer_free( sql_buf );
4558 if( defaultselhash ) jsonObjectFree( defaultselhash );
4559 clear_query_stack();
4563 growing_buffer* field_buf = buffer_init(16);
4564 buffer_fadd(field_buf, "\"%s\".%s", class_itr->key, order_itr->key);
4565 string = buffer_release(field_buf);
4568 if ( (_tmp = jsonObjectGetKeyConst( onode, "direction" )) ) {
4569 const char* dir = jsonObjectGetString(_tmp);
4570 if (!strncasecmp(dir, "d", 1)) {
4571 direction = " DESC";
4578 string = strdup(order_itr->key);
4579 const char* dir = jsonObjectGetString(onode);
4580 if (!strncasecmp(dir, "d", 1)) {
4581 direction = " DESC";
4590 buffer_add(order_buf, ", ");
4593 buffer_add(order_buf, string);
4597 buffer_add(order_buf, direction);
4602 jsonIteratorFree(order_itr);
4605 const char* str = jsonObjectGetString(snode);
4606 buffer_add(order_buf, str);
4612 jsonIteratorFree(class_itr);
4614 string = buffer_release(order_buf);
4617 OSRF_BUFFER_ADD( sql_buf, " ORDER BY " );
4618 OSRF_BUFFER_ADD( sql_buf, string );
4624 if ( (_tmp = jsonObjectGetKeyConst( order_hash, "limit" )) ){
4625 const char* str = jsonObjectGetString(_tmp);
4633 _tmp = jsonObjectGetKeyConst( order_hash, "offset" );
4635 const char* str = jsonObjectGetString(_tmp);
4644 if (defaultselhash) jsonObjectFree(defaultselhash);
4645 clear_query_stack();
4647 OSRF_BUFFER_ADD_CHAR(sql_buf, ';');
4648 return buffer_release(sql_buf);
4651 int doJSONSearch ( osrfMethodContext* ctx ) {
4652 if(osrfMethodVerifyContext( ctx )) {
4653 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
4657 osrfLogDebug(OSRF_LOG_MARK, "Received query request");
4662 dbhandle = writehandle;
4664 jsonObject* hash = jsonObjectGetIndex(ctx->params, 0);
4668 if ( obj_is_true( jsonObjectGetKey( hash, "distinct" ) ) )
4669 flags |= SELECT_DISTINCT;
4671 if ( obj_is_true( jsonObjectGetKey( hash, "no_i18n" ) ) )
4672 flags |= DISABLE_I18N;
4674 osrfLogDebug(OSRF_LOG_MARK, "Building SQL ...");
4675 clear_query_stack(); // a possibly needless precaution
4676 char* sql = buildQuery( ctx, hash, flags );
4677 clear_query_stack();
4684 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4685 dbi_result result = dbi_conn_query(dbhandle, sql);
4688 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4690 if (dbi_result_first_row(result)) {
4691 /* JSONify the result */
4692 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4695 jsonObject* return_val = oilsMakeJSONFromResult( result );
4696 osrfAppRespond( ctx, return_val );
4697 jsonObjectFree( return_val );
4698 } while (dbi_result_next_row(result));
4701 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
4704 osrfAppRespondComplete( ctx, NULL );
4706 /* clean up the query */
4707 dbi_result_free(result);
4711 osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql);
4712 osrfAppSessionStatus(
4714 OSRF_STATUS_INTERNALSERVERERROR,
4715 "osrfMethodException",
4717 "Severe query error -- see error log for more details"
4725 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta,
4726 jsonObject* where_hash, jsonObject* query_hash, int* err ) {
4729 dbhandle = writehandle;
4731 osrfHash* links = osrfHashGet(meta, "links");
4732 osrfHash* fields = osrfHashGet(meta, "fields");
4733 char* core_class = osrfHashGet(meta, "classname");
4734 char* pkey = osrfHashGet(meta, "primarykey");
4736 const jsonObject* _tmp;
4739 char* sql = buildSELECT( where_hash, query_hash, meta, ctx );
4741 osrfLogDebug(OSRF_LOG_MARK, "Problem building query, returning NULL");
4746 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
4748 dbi_result result = dbi_conn_query(dbhandle, sql);
4749 if( NULL == result ) {
4750 osrfLogError(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]",
4751 MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
4752 osrfAppSessionStatus(
4754 OSRF_STATUS_INTERNALSERVERERROR,
4755 "osrfMethodException",
4757 "Severe query error -- see error log for more details"
4764 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
4767 jsonObject* res_list = jsonNewObjectType(JSON_ARRAY);
4768 osrfHash* dedup = osrfNewHash();
4770 if (dbi_result_first_row(result)) {
4771 /* JSONify the result */
4772 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
4774 obj = oilsMakeFieldmapperFromResult( result, meta );
4775 char* pkey_val = oilsFMGetString( obj, pkey );
4776 if ( osrfHashGet( dedup, pkey_val ) ) {
4777 jsonObjectFree(obj);
4780 osrfHashSet( dedup, pkey_val, pkey_val );
4781 jsonObjectPush(res_list, obj);
4783 } while (dbi_result_next_row(result));
4785 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s",
4789 osrfHashFree(dedup);
4790 /* clean up the query */
4791 dbi_result_free(result);
4794 if (res_list->size && query_hash) {
4795 _tmp = jsonObjectGetKeyConst( query_hash, "flesh" );
4797 int x = (int)jsonObjectGetNumber(_tmp);
4798 if (x == -1 || x > max_flesh_depth) x = max_flesh_depth;
4800 const jsonObject* temp_blob;
4801 if ((temp_blob = jsonObjectGetKeyConst( query_hash, "flesh_fields" )) && x > 0) {
4803 jsonObject* flesh_blob = jsonObjectClone( temp_blob );
4804 const jsonObject* flesh_fields = jsonObjectGetKeyConst( flesh_blob, core_class );
4806 osrfStringArray* link_fields = NULL;
4809 if (flesh_fields->size == 1) {
4810 const char* _t = jsonObjectGetString( jsonObjectGetIndex( flesh_fields, 0 ) );
4811 if (!strcmp(_t,"*")) link_fields = osrfHashKeys( links );
4816 link_fields = osrfNewStringArray(1);
4817 jsonIterator* _i = jsonNewIterator( flesh_fields );
4818 while ((_f = jsonIteratorNext( _i ))) {
4819 osrfStringArrayAdd( link_fields, jsonObjectGetString( _f ) );
4821 jsonIteratorFree(_i);
4826 unsigned long res_idx = 0;
4827 while ((cur = jsonObjectGetIndex( res_list, res_idx++ ) )) {
4830 const char* link_field;
4832 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
4834 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
4836 osrfHash* kid_link = osrfHashGet(links, link_field);
4837 if (!kid_link) continue;
4839 osrfHash* field = osrfHashGet(fields, link_field);
4840 if (!field) continue;
4842 osrfHash* value_field = field;
4844 osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class"));
4845 if (!kid_idl) continue;
4847 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4848 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4851 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
4852 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
4855 osrfStringArray* link_map = osrfHashGet( kid_link, "map" );
4857 if (link_map->size > 0) {
4858 jsonObject* _kid_key = jsonNewObjectType(JSON_ARRAY);
4861 jsonNewObject( osrfStringArrayGetString( link_map, 0 ) )
4866 osrfHashGet(kid_link, "class"),
4873 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
4874 osrfHashGet(kid_link, "field"),
4875 osrfHashGet(kid_link, "class"),
4876 osrfHashGet(kid_link, "key"),
4877 osrfHashGet(kid_link, "reltype")
4880 const char* search_key = jsonObjectGetString(
4883 atoi( osrfHashGet(value_field, "array_position") )
4888 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
4892 osrfLogDebug(OSRF_LOG_MARK, "Creating param objects...");
4894 // construct WHERE clause
4895 jsonObject* where_clause = jsonNewObjectType(JSON_HASH);
4898 osrfHashGet(kid_link, "key"),
4899 jsonNewObject( search_key )
4902 // construct the rest of the query
4903 jsonObject* rest_of_query = jsonNewObjectType(JSON_HASH);
4904 jsonObjectSetKey( rest_of_query, "flesh",
4905 jsonNewNumberObject( (double)(x - 1 + link_map->size) )
4909 jsonObjectSetKey( rest_of_query, "flesh_fields", jsonObjectClone(flesh_blob) );
4911 if (jsonObjectGetKeyConst(query_hash, "order_by")) {
4912 jsonObjectSetKey( rest_of_query, "order_by",
4913 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "order_by"))
4917 if (jsonObjectGetKeyConst(query_hash, "select")) {
4918 jsonObjectSetKey( rest_of_query, "select",
4919 jsonObjectClone(jsonObjectGetKeyConst(query_hash, "select"))
4923 jsonObject* kids = doFieldmapperSearch( ctx, kid_idl,
4924 where_clause, rest_of_query, err);
4926 jsonObjectFree( where_clause );
4927 jsonObjectFree( rest_of_query );
4930 osrfStringArrayFree(link_fields);
4931 jsonObjectFree(res_list);
4932 jsonObjectFree(flesh_blob);
4936 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
4938 jsonObject* X = NULL;
4939 if ( link_map->size > 0 && kids->size > 0 ) {
4941 kids = jsonNewObjectType(JSON_ARRAY);
4943 jsonObject* _k_node;
4944 unsigned long res_idx = 0;
4945 while ((_k_node = jsonObjectGetIndex( X, res_idx++ ) )) {
4951 (unsigned long)atoi(
4957 osrfHashGet(kid_link, "class")
4961 osrfStringArrayGetString( link_map, 0 )
4969 } // end while loop traversing X
4972 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" )) || !(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) {
4973 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4976 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4977 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
4981 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
4982 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
4985 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
4986 jsonObjectClone( kids )
4991 jsonObjectFree(kids);
4995 jsonObjectFree( kids );
4997 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
4998 osrfLogDebug(OSRF_LOG_MARK, "%s", jsonObjectToJSON(cur));
5001 } // end while loop traversing res_list
5002 jsonObjectFree( flesh_blob );
5003 osrfStringArrayFree(link_fields);
5012 static jsonObject* doUpdate(osrfMethodContext* ctx, int* err ) {
5014 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5016 jsonObject* target = jsonObjectGetIndex( ctx->params, 1 );
5018 jsonObject* target = jsonObjectGetIndex( ctx->params, 0 );
5021 if (!verifyObjectClass(ctx, target)) {
5026 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
5027 osrfAppSessionStatus(
5029 OSRF_STATUS_BADREQUEST,
5030 "osrfMethodException",
5032 "No active transaction -- required for UPDATE"
5038 // The following test is harmless but redundant. If a class is
5039 // readonly, we don't register an update method for it.
5040 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5041 osrfAppSessionStatus(
5043 OSRF_STATUS_BADREQUEST,
5044 "osrfMethodException",
5046 "Cannot UPDATE readonly class"
5052 dbhandle = writehandle;
5054 char* trans_id = osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" );
5056 // Set the last_xact_id
5057 int index = oilsIDL_ntop( target->classname, "last_xact_id" );
5059 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
5060 trans_id, target->classname, index);
5061 jsonObjectSetIndex(target, index, jsonNewObject(trans_id));
5064 char* pkey = osrfHashGet(meta, "primarykey");
5065 osrfHash* fields = osrfHashGet(meta, "fields");
5067 char* id = oilsFMGetString( target, pkey );
5071 "%s updating %s object with %s = %s",
5073 osrfHashGet(meta, "fieldmapper"),
5078 growing_buffer* sql = buffer_init(128);
5079 buffer_fadd(sql,"UPDATE %s SET", osrfHashGet(meta, "tablename"));
5082 osrfHash* field_def = NULL;
5083 osrfHashIterator* field_itr = osrfNewHashIterator( fields );
5084 while( (field_def = osrfHashIteratorNext( field_itr ) ) ) {
5086 // Skip virtual fields, and the primary key
5087 if( str_is_true( osrfHashGet( field_def, "virtual") ) )
5090 const char* field_name = osrfHashIteratorKey( field_itr );
5091 if( ! strcmp( field_name, pkey ) )
5094 const jsonObject* field_object = oilsFMGetObject( target, field_name );
5096 int value_is_numeric = 0; // boolean
5098 if (field_object && field_object->classname) {
5099 value = oilsFMGetString(
5101 (char*)oilsIDLFindPath("/%s/primarykey", field_object->classname)
5103 } else if( field_object && JSON_BOOL == field_object->type ) {
5104 if( jsonBoolIsTrue( field_object ) )
5105 value = strdup( "t" );
5107 value = strdup( "f" );
5109 value = jsonObjectToSimpleString( field_object );
5110 if( field_object && JSON_NUMBER == field_object->type )
5111 value_is_numeric = 1;
5114 osrfLogDebug( OSRF_LOG_MARK, "Updating %s object with %s = %s",
5115 osrfHashGet(meta, "fieldmapper"), field_name, value);
5117 if (!field_object || field_object->type == JSON_NULL) {
5118 if ( !(!( strcmp( osrfHashGet(meta, "classname"), "au" ) )
5119 && !( strcmp( field_name, "passwd" ) )) ) { // arg at the special case!
5120 if (first) first = 0;
5121 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5122 buffer_fadd( sql, " %s = NULL", field_name );
5125 } else if ( value_is_numeric || !strcmp( get_primitive( field_def ), "number") ) {
5126 if (first) first = 0;
5127 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5129 const char* numtype = get_datatype( field_def );
5130 if ( !strncmp( numtype, "INT", 3 ) ) {
5131 buffer_fadd( sql, " %s = %ld", field_name, atol(value) );
5132 } else if ( !strcmp( numtype, "NUMERIC" ) ) {
5133 buffer_fadd( sql, " %s = %f", field_name, atof(value) );
5135 // Must really be intended as a string, so quote it
5136 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5137 buffer_fadd( sql, " %s = %s", field_name, value );
5139 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5140 osrfAppSessionStatus(
5142 OSRF_STATUS_INTERNALSERVERERROR,
5143 "osrfMethodException",
5145 "Error quoting string -- please see the error log for more details"
5149 osrfHashIteratorFree( field_itr );
5156 osrfLogDebug( OSRF_LOG_MARK, "%s is of type %s", field_name, numtype );
5159 if ( dbi_conn_quote_string(dbhandle, &value) ) {
5160 if (first) first = 0;
5161 else OSRF_BUFFER_ADD_CHAR(sql, ',');
5162 buffer_fadd( sql, " %s = %s", field_name, value );
5165 osrfLogError(OSRF_LOG_MARK, "%s: Error quoting string [%s]", MODULENAME, value);
5166 osrfAppSessionStatus(
5168 OSRF_STATUS_INTERNALSERVERERROR,
5169 "osrfMethodException",
5171 "Error quoting string -- please see the error log for more details"
5175 osrfHashIteratorFree( field_itr );
5186 osrfHashIteratorFree( field_itr );
5188 jsonObject* obj = jsonNewObject(id);
5190 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5191 dbi_conn_quote_string(dbhandle, &id);
5193 buffer_fadd( sql, " WHERE %s = %s;", pkey, id );
5195 char* query = buffer_release(sql);
5196 osrfLogDebug(OSRF_LOG_MARK, "%s: Update SQL [%s]", MODULENAME, query);
5198 dbi_result result = dbi_conn_query(dbhandle, query);
5202 jsonObjectFree(obj);
5203 obj = jsonNewObject(NULL);
5206 "%s ERROR updating %s object with %s = %s",
5208 osrfHashGet(meta, "fieldmapper"),
5219 static jsonObject* doDelete(osrfMethodContext* ctx, int* err ) {
5221 osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
5223 if (!osrfHashGet( (osrfHash*)ctx->session->userData, "xact_id" )) {
5224 osrfAppSessionStatus(
5226 OSRF_STATUS_BADREQUEST,
5227 "osrfMethodException",
5229 "No active transaction -- required for DELETE"
5235 // The following test is harmless but redundant. If a class is
5236 // readonly, we don't register a delete method for it.
5237 if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
5238 osrfAppSessionStatus(
5240 OSRF_STATUS_BADREQUEST,
5241 "osrfMethodException",
5243 "Cannot DELETE readonly class"
5249 dbhandle = writehandle;
5253 char* pkey = osrfHashGet(meta, "primarykey");
5261 if (jsonObjectGetIndex(ctx->params, _obj_pos)->classname) {
5262 if (!verifyObjectClass(ctx, jsonObjectGetIndex( ctx->params, _obj_pos ))) {
5267 id = oilsFMGetString( jsonObjectGetIndex(ctx->params, _obj_pos), pkey );
5270 if (!verifyObjectPCRUD( ctx, NULL )) {
5275 id = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, _obj_pos));
5280 "%s deleting %s object with %s = %s",
5282 osrfHashGet(meta, "fieldmapper"),
5287 obj = jsonNewObject(id);
5289 if ( strcmp( get_primitive( osrfHashGet( osrfHashGet(meta, "fields"), pkey ) ), "number" ) )
5290 dbi_conn_quote_string(writehandle, &id);
5292 dbi_result result = dbi_conn_queryf(writehandle, "DELETE FROM %s WHERE %s = %s;", osrfHashGet(meta, "tablename"), pkey, id);
5295 jsonObjectFree(obj);
5296 obj = jsonNewObject(NULL);
5299 "%s ERROR deleting %s object with %s = %s",
5301 osrfHashGet(meta, "fieldmapper"),
5314 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) {
5315 if(!(result && meta)) return jsonNULL;
5317 jsonObject* object = jsonNewObject(NULL);
5318 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
5320 osrfHash* fields = osrfHashGet(meta, "fields");
5322 osrfLogInternal(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
5326 char dt_string[256];
5330 int columnIndex = 1;
5332 unsigned short type;
5333 const char* columnName;
5335 /* cycle through the column list */
5336 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5338 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5340 fmIndex = -1; // reset the position
5342 /* determine the field type and storage attributes */
5343 type = dbi_result_get_field_type_idx(result, columnIndex);
5344 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5346 /* fetch the fieldmapper index */
5347 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
5349 if ( str_is_true( osrfHashGet(_f, "virtual") ) )
5352 const char* pos = (char*)osrfHashGet(_f, "array_position");
5353 if ( !pos ) continue;
5355 fmIndex = atoi( pos );
5356 osrfLogInternal(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
5361 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5362 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
5367 case DBI_TYPE_INTEGER :
5369 if( attr & DBI_INTEGER_SIZE8 )
5370 jsonObjectSetIndex( object, fmIndex,
5371 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)));
5373 jsonObjectSetIndex( object, fmIndex,
5374 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)));
5378 case DBI_TYPE_DECIMAL :
5379 jsonObjectSetIndex( object, fmIndex,
5380 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)));
5383 case DBI_TYPE_STRING :
5389 jsonNewObject( dbi_result_get_string_idx(result, columnIndex) )
5394 case DBI_TYPE_DATETIME :
5396 memset(dt_string, '\0', sizeof(dt_string));
5397 memset(&gmdt, '\0', sizeof(gmdt));
5399 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5402 if (!(attr & DBI_DATETIME_DATE)) {
5403 gmtime_r( &_tmp_dt, &gmdt );
5404 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5405 } else if (!(attr & DBI_DATETIME_TIME)) {
5406 localtime_r( &_tmp_dt, &gmdt );
5407 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5409 localtime_r( &_tmp_dt, &gmdt );
5410 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5413 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
5417 case DBI_TYPE_BINARY :
5418 osrfLogError( OSRF_LOG_MARK,
5419 "Can't do binary at column %s : index %d", columnName, columnIndex);
5428 static jsonObject* oilsMakeJSONFromResult( dbi_result result ) {
5429 if(!result) return jsonNULL;
5431 jsonObject* object = jsonNewObject(NULL);
5434 char dt_string[256];
5438 int columnIndex = 1;
5440 unsigned short type;
5441 const char* columnName;
5443 /* cycle through the column list */
5444 while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
5446 osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
5448 fmIndex = -1; // reset the position
5450 /* determine the field type and storage attributes */
5451 type = dbi_result_get_field_type_idx(result, columnIndex);
5452 attr = dbi_result_get_field_attribs_idx(result, columnIndex);
5454 if (dbi_result_field_is_null_idx(result, columnIndex)) {
5455 jsonObjectSetKey( object, columnName, jsonNewObject(NULL) );
5460 case DBI_TYPE_INTEGER :
5462 if( attr & DBI_INTEGER_SIZE8 )
5463 jsonObjectSetKey( object, columnName,
5464 jsonNewNumberObject(dbi_result_get_longlong_idx(result, columnIndex)) );
5466 jsonObjectSetKey( object, columnName,
5467 jsonNewNumberObject(dbi_result_get_int_idx(result, columnIndex)) );
5470 case DBI_TYPE_DECIMAL :
5471 jsonObjectSetKey( object, columnName,
5472 jsonNewNumberObject(dbi_result_get_double_idx(result, columnIndex)) );
5475 case DBI_TYPE_STRING :
5476 jsonObjectSetKey( object, columnName,
5477 jsonNewObject(dbi_result_get_string_idx(result, columnIndex)) );
5480 case DBI_TYPE_DATETIME :
5482 memset(dt_string, '\0', sizeof(dt_string));
5483 memset(&gmdt, '\0', sizeof(gmdt));
5485 _tmp_dt = dbi_result_get_datetime_idx(result, columnIndex);
5488 if (!(attr & DBI_DATETIME_DATE)) {
5489 gmtime_r( &_tmp_dt, &gmdt );
5490 strftime(dt_string, sizeof(dt_string), "%T", &gmdt);
5491 } else if (!(attr & DBI_DATETIME_TIME)) {
5492 localtime_r( &_tmp_dt, &gmdt );
5493 strftime(dt_string, sizeof(dt_string), "%F", &gmdt);
5495 localtime_r( &_tmp_dt, &gmdt );
5496 strftime(dt_string, sizeof(dt_string), "%FT%T%z", &gmdt);
5499 jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) );
5502 case DBI_TYPE_BINARY :
5503 osrfLogError( OSRF_LOG_MARK,
5504 "Can't do binary at column %s : index %d", columnName, columnIndex );
5508 } // end while loop traversing result
5513 // Interpret a string as true or false
5514 static int str_is_true( const char* str ) {
5515 if( NULL == str || strcasecmp( str, "true" ) )
5521 // Interpret a jsonObject as true or false
5522 static int obj_is_true( const jsonObject* obj ) {
5525 else switch( obj->type )
5533 if( strcasecmp( obj->value.s, "true" ) )
5537 case JSON_NUMBER : // Support 1/0 for perl's sake
5538 if( jsonObjectGetNumber( obj ) == 1.0 )
5547 // Translate a numeric code into a text string identifying a type of
5548 // jsonObject. To be used for building error messages.
5549 static const char* json_type( int code ) {
5555 return "JSON_ARRAY";
5557 return "JSON_STRING";
5559 return "JSON_NUMBER";
5565 return "(unrecognized)";
5569 // Extract the "primitive" attribute from an IDL field definition.
5570 // If we haven't initialized the app, then we must be running in
5571 // some kind of testbed. In that case, default to "string".
5572 static const char* get_primitive( osrfHash* field ) {
5573 const char* s = osrfHashGet( field, "primitive" );
5575 if( child_initialized )
5578 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5580 osrfHashGet( field, "name" )
5588 // Extract the "datatype" attribute from an IDL field definition.
5589 // If we haven't initialized the app, then we must be running in
5590 // some kind of testbed. In that case, default to to NUMERIC,
5591 // since we look at the datatype only for numbers.
5592 static const char* get_datatype( osrfHash* field ) {
5593 const char* s = osrfHashGet( field, "datatype" );
5595 if( child_initialized )
5598 "%s ERROR No \"datatype\" attribute for field \"%s\"",
5600 osrfHashGet( field, "name" )
5609 If the input string is potentially a valid SQL identifier, return 1.
5612 Purpose: to prevent certain kinds of SQL injection. To that end we
5613 don't necessarily need to follow all the rules exactly, such as requiring
5614 that the first character not be a digit.
5616 We allow leading and trailing white space. In between, we do not allow
5617 punctuation (except for underscores and dollar signs), control
5618 characters, or embedded white space.
5620 More pedantically we should allow quoted identifiers containing arbitrary
5621 characters, but for the foreseeable future such quoted identifiers are not
5622 likely to be an issue.
5624 static int is_identifier( const char* s) {
5628 // Skip leading white space
5629 while( isspace( (unsigned char) *s ) )
5633 return 0; // Nothing but white space? Not okay.
5635 // Check each character until we reach white space or
5636 // end-of-string. Letters, digits, underscores, and
5637 // dollar signs are okay. With the exception of periods
5638 // (as in schema.identifier), control characters and other
5639 // punctuation characters are not okay. Anything else
5640 // is okay -- it could for example be part of a multibyte
5641 // UTF8 character such as a letter with diacritical marks,
5642 // and those are allowed.
5644 if( isalnum( (unsigned char) *s )
5648 ; // Fine; keep going
5649 else if( ispunct( (unsigned char) *s )
5650 || iscntrl( (unsigned char) *s ) )
5653 } while( *s && ! isspace( (unsigned char) *s ) );
5655 // If we found any white space in the above loop,
5656 // the rest had better be all white space.
5658 while( isspace( (unsigned char) *s ) )
5662 return 0; // White space was embedded within non-white space
5668 Determine whether to accept a character string as a comparison operator.
5669 Return 1 if it's good, or 0 if it's bad.
5671 We don't validate it for real. We just make sure that it doesn't contain
5672 any semicolons or white space (with special exceptions for a few specific
5673 operators). The idea is to block certain kinds of SQL injection. If it
5674 has no semicolons or white space but it's still not a valid operator, then
5675 the database will complain.
5677 Another approach would be to compare the string against a short list of
5678 approved operators. We don't do that because we want to allow custom
5679 operators like ">100*", which would be difficult or impossible to
5680 express otherwise in a JSON query.
5682 static int is_good_operator( const char* op ) {
5683 if( !op ) return 0; // Sanity check
5687 if( isspace( (unsigned char) *s ) ) {
5688 // Special exceptions for SIMILAR TO, IS DISTINCT FROM,
5689 // and IS NOT DISTINCT FROM.
5690 if( !strcasecmp( op, "similar to" ) )
5692 else if( !strcasecmp( op, "is distinct from" ) )
5694 else if( !strcasecmp( op, "is not distinct from" ) )
5699 else if( ';' == *s )
5706 /* ----------------------------------------------------------------------------------
5707 The following machinery supports a stack of query frames for use by SELECT().
5709 A query frame caches information about one level of a SELECT query. When we enter
5710 a subquery, we push another query frame onto the stack, and pop it off when we leave.
5712 The query frame stores information about the core class, and about any joined classes
5715 The main purpose is to map table aliases to classes and tables, so that a query can
5716 join to the same table more than once. A secondary goal is to reduce the number of
5717 lookups in the IDL by caching the results.
5718 ----------------------------------------------------------------------------------*/
5720 #define STATIC_CLASS_INFO_COUNT 3
5722 static ClassInfo static_class_info[ STATIC_CLASS_INFO_COUNT ];
5724 /* ---------------------------------------------------------------------------
5725 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5727 ---------------------------------------------------------------------------*/
5728 static ClassInfo* allocate_class_info( void ) {
5729 // In order to reduce the number of mallocs and frees, we return a static
5730 // instance of ClassInfo, if we can find one that we're not already using.
5731 // We rely on the fact that the compiler will implicitly initialize the
5732 // static instances so that in_use == 0.
5735 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5736 if( ! static_class_info[ i ].in_use ) {
5737 static_class_info[ i ].in_use = 1;
5738 return static_class_info + i;
5742 // The static ones are all in use. Malloc one.
5744 return safe_malloc( sizeof( ClassInfo ) );
5747 /* --------------------------------------------------------------------------
5748 Free any malloc'd memory owned by a ClassInfo; return it to a pristine state
5749 ---------------------------------------------------------------------------*/
5750 static void clear_class_info( ClassInfo* info ) {
5755 // Free any malloc'd strings
5757 if( info->alias != info->alias_store )
5758 free( info->alias );
5760 if( info->class_name != info->class_name_store )
5761 free( info->class_name );
5763 free( info->source_def );
5765 info->alias = info->class_name = info->source_def = NULL;
5769 /* --------------------------------------------------------------------------
5770 Deallocate a ClassInfo and everything it owns
5771 ---------------------------------------------------------------------------*/
5772 static void free_class_info( ClassInfo* info ) {
5777 clear_class_info( info );
5779 // If it's one of the static instances, just mark it as not in use
5782 for( i = 0; i < STATIC_CLASS_INFO_COUNT; ++i ) {
5783 if( info == static_class_info + i ) {
5784 static_class_info[ i ].in_use = 0;
5789 // Otherwise it must have been malloc'd, so free it
5794 /* --------------------------------------------------------------------------
5795 Populate an already-allocated ClassInfo. Return 0 if successful, 1 if not.
5796 ---------------------------------------------------------------------------*/
5797 static int build_class_info( ClassInfo* info, const char* alias, const char* class ) {
5800 osrfLogError( OSRF_LOG_MARK,
5801 "%s ERROR: No ClassInfo available to populate", MODULENAME );
5802 info->alias = info->class_name = info->source_def = NULL;
5803 info->class_def = info->fields = info->links = NULL;
5808 osrfLogError( OSRF_LOG_MARK,
5809 "%s ERROR: No class name provided for lookup", MODULENAME );
5810 info->alias = info->class_name = info->source_def = NULL;
5811 info->class_def = info->fields = info->links = NULL;
5815 // Alias defaults to class name if not supplied
5816 if( ! alias || ! alias[ 0 ] )
5819 // Look up class info in the IDL
5820 osrfHash* class_def = osrfHashGet( oilsIDL(), class );
5822 osrfLogError( OSRF_LOG_MARK,
5823 "%s ERROR: Class %s not defined in IDL", MODULENAME, class );
5824 info->alias = info->class_name = info->source_def = NULL;
5825 info->class_def = info->fields = info->links = NULL;
5827 } else if( str_is_true( osrfHashGet( class_def, "virtual" ) ) ) {
5828 osrfLogError( OSRF_LOG_MARK,
5829 "%s ERROR: Class %s is defined as virtual", MODULENAME, class );
5830 info->alias = info->class_name = info->source_def = NULL;
5831 info->class_def = info->fields = info->links = NULL;
5835 osrfHash* links = osrfHashGet( class_def, "links" );
5837 osrfLogError( OSRF_LOG_MARK,
5838 "%s ERROR: No links defined in IDL for class %s", MODULENAME, class );
5839 info->alias = info->class_name = info->source_def = NULL;
5840 info->class_def = info->fields = info->links = NULL;
5844 osrfHash* fields = osrfHashGet( class_def, "fields" );
5846 osrfLogError( OSRF_LOG_MARK,
5847 "%s ERROR: No fields defined in IDL for class %s", MODULENAME, class );
5848 info->alias = info->class_name = info->source_def = NULL;
5849 info->class_def = info->fields = info->links = NULL;
5853 char* source_def = getRelation( class_def );
5857 // We got everything we need, so populate the ClassInfo
5858 if( strlen( alias ) > ALIAS_STORE_SIZE )
5859 info->alias = strdup( alias );
5861 strcpy( info->alias_store, alias );
5862 info->alias = info->alias_store;
5865 if( strlen( class ) > CLASS_NAME_STORE_SIZE )
5866 info->class_name = strdup( class );
5868 strcpy( info->class_name_store, class );
5869 info->class_name = info->class_name_store;
5872 info->source_def = source_def;
5874 info->class_def = class_def;
5875 info->links = links;
5876 info->fields = fields;
5881 #define STATIC_FRAME_COUNT 3
5883 static QueryFrame static_frame[ STATIC_FRAME_COUNT ];
5885 /* ---------------------------------------------------------------------------
5886 Allocate a ClassInfo as raw memory. Except for the in_use flag, we don't
5888 ---------------------------------------------------------------------------*/
5889 static QueryFrame* allocate_frame( void ) {
5890 // In order to reduce the number of mallocs and frees, we return a static
5891 // instance of QueryFrame, if we can find one that we're not already using.
5892 // We rely on the fact that the compiler will implicitly initialize the
5893 // static instances so that in_use == 0.
5896 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5897 if( ! static_frame[ i ].in_use ) {
5898 static_frame[ i ].in_use = 1;
5899 return static_frame + i;
5903 // The static ones are all in use. Malloc one.
5905 return safe_malloc( sizeof( QueryFrame ) );
5908 /* --------------------------------------------------------------------------
5909 Free a QueryFrame, and all the memory it owns.
5910 ---------------------------------------------------------------------------*/
5911 static void free_query_frame( QueryFrame* frame ) {
5916 clear_class_info( &frame->core );
5918 // Free the join list
5920 ClassInfo* info = frame->join_list;
5923 free_class_info( info );
5927 frame->join_list = NULL;
5930 // If the frame is a static instance, just mark it as unused
5932 for( i = 0; i < STATIC_FRAME_COUNT; ++i ) {
5933 if( frame == static_frame + i ) {
5934 static_frame[ i ].in_use = 0;
5939 // Otherwise it must have been malloc'd, so free it
5944 /* --------------------------------------------------------------------------
5945 Search a given QueryFrame for a specified alias. If you find it, return
5946 a pointer to the corresponding ClassInfo. Otherwise return NULL.
5947 ---------------------------------------------------------------------------*/
5948 static ClassInfo* search_alias_in_frame( QueryFrame* frame, const char* target ) {
5949 if( ! frame || ! target ) {
5953 ClassInfo* found_class = NULL;
5955 if( !strcmp( target, frame->core.alias ) )
5956 return &(frame->core);
5958 ClassInfo* curr_class = frame->join_list;
5959 while( curr_class ) {
5960 if( strcmp( target, curr_class->alias ) )
5961 curr_class = curr_class->next;
5963 found_class = curr_class;
5972 /* --------------------------------------------------------------------------
5973 Push a new (blank) QueryFrame onto the stack.
5974 ---------------------------------------------------------------------------*/
5975 static void push_query_frame( void ) {
5976 QueryFrame* frame = allocate_frame();
5977 frame->join_list = NULL;
5978 frame->next = curr_query;
5980 // Initialize the ClassInfo for the core class
5981 ClassInfo* core = &frame->core;
5982 core->alias = core->class_name = core->source_def = NULL;
5983 core->class_def = core->fields = core->links = NULL;
5988 /* --------------------------------------------------------------------------
5989 Pop a QueryFrame off the stack and destroy it
5990 ---------------------------------------------------------------------------*/
5991 static void pop_query_frame( void ) {
5996 QueryFrame* popped = curr_query;
5997 curr_query = popped->next;
5999 free_query_frame( popped );
6002 /* --------------------------------------------------------------------------
6003 Populate the ClassInfo for the core class. Return 0 if successful, 1 if not.
6004 ---------------------------------------------------------------------------*/
6005 static int add_query_core( const char* alias, const char* class_name ) {
6008 if( ! curr_query ) {
6009 osrfLogError( OSRF_LOG_MARK,
6010 "%s ERROR: No QueryFrame available for class %s", MODULENAME, class_name );
6012 } else if( curr_query->core.alias ) {
6013 osrfLogError( OSRF_LOG_MARK,
6014 "%s ERROR: Core class %s already populated as %s",
6015 MODULENAME, curr_query->core.class_name, curr_query->core.alias );
6019 build_class_info( &curr_query->core, alias, class_name );
6020 if( curr_query->core.alias )
6023 osrfLogError( OSRF_LOG_MARK,
6024 "%s ERROR: Unable to look up core class %s", MODULENAME, class_name );
6029 /* --------------------------------------------------------------------------
6030 Search the current QueryFrame for a specified alias. If you find it,
6031 return a pointer to the corresponding ClassInfo. Otherwise return NULL.
6032 ---------------------------------------------------------------------------*/
6033 static ClassInfo* search_alias( const char* target ) {
6034 return search_alias_in_frame( curr_query, target );
6037 /* --------------------------------------------------------------------------
6038 Search all levels of query for a specified alias, starting with the
6039 current query. If you find it, return a pointer to the corresponding
6040 ClassInfo. Otherwise return NULL.
6041 ---------------------------------------------------------------------------*/
6042 static ClassInfo* search_all_alias( const char* target ) {
6043 ClassInfo* found_class = NULL;
6044 QueryFrame* curr_frame = curr_query;
6046 while( curr_frame ) {
6047 if(( found_class = search_alias_in_frame( curr_frame, target ) ))
6050 curr_frame = curr_frame->next;
6056 /* --------------------------------------------------------------------------
6057 Add a class to the list of classes joined to the current query.
6058 ---------------------------------------------------------------------------*/
6059 static ClassInfo* add_joined_class( const char* alias, const char* classname ) {
6061 if( ! classname || ! *classname ) { // sanity check
6062 osrfLogError( OSRF_LOG_MARK, "Can't join a class with no class name" );
6069 const ClassInfo* conflict = search_alias( alias );
6071 osrfLogError( OSRF_LOG_MARK,
6072 "%s ERROR: Table alias \"%s\" conflicts with class \"%s\"",
6073 MODULENAME, alias, conflict->class_name );
6077 ClassInfo* info = allocate_class_info();
6079 if( build_class_info( info, alias, classname ) ) {
6080 free_class_info( info );
6084 // Add the new ClassInfo to the join list of the current QueryFrame
6085 info->next = curr_query->join_list;
6086 curr_query->join_list = info;
6091 /* --------------------------------------------------------------------------
6092 Destroy all nodes on the query stack.
6093 ---------------------------------------------------------------------------*/
6094 static void clear_query_stack( void ) {